internal/core/eval: implement core evaluator

Does not yet implement imports and builtins.

- adds implementations for adt types
- adds eval package with higher-level evaluation
- some tweaks to compile

Change-Id: Ie91bd0bde8a03ed9957f306166042f56aebe19ce
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6280
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/eval/disjunct.go b/internal/core/eval/disjunct.go
new file mode 100644
index 0000000..db5df3c
--- /dev/null
+++ b/internal/core/eval/disjunct.go
@@ -0,0 +1,376 @@
+// 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 eval
+
+import (
+	"sort"
+
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+)
+
+// Nodes man not reenter a disjunction.
+//
+// Copy one layer deep; throw away items on failure.
+
+// DISJUNCTION ALGORITHM
+//
+// The basic concept of the algorithm is to use backtracking to find valid
+// disjunctions. The algorithm can stop if two matching disjuncts are found
+// where one does not subsume the other.
+//
+// At a later point, we can introduce a filter step to filter out possible
+// disjuncts based on, say, discriminator fields or field exclusivity (oneOf
+// fields in Protobuf).
+//
+// To understand the details of the algorithm, it is important to understand
+// some properties of disjunction.
+//
+//
+// EVALUATION OF A DISJUNCTION IS SELF CONTAINED
+//
+// In other words, fields outside of a disjunction cannot bind to values within
+// a disjunction whilst evaluating that disjunction. This allows the computation
+// of disjunctions to be isolated from side effects.
+//
+// The intuition behind this is as follows: as a disjunction is not a concrete
+// value, it is not possible to lookup a field within a disjunction if it has
+// not yet been evaluated. So if a reference within a disjunction that is needed
+// to disambiguate that disjunction refers to a field outside the scope of the
+// disjunction which, in turn, refers to a field within the disjunction, this
+// results in a cycle error. We achieve this by not removing the cycle marker of
+// the Vertex of the disjunction until the disjunction is resolved.
+//
+// Note that the following disjunct is still allowed:
+//
+//    a: 1
+//    b: a
+//
+// Even though `a` refers to the root of the disjunction, it does not _select
+// into_ the disjunction. Implementation-wise, it also doesn't have to, as the
+// respective vertex is available within the Environment. Referencing a node
+// outside the disjunction that in turn selects the disjunction root, however,
+// will result in a detected cycle.
+//
+// As usual, cycle detection should be interpreted marked as incomplete, so that
+// the referring node will not be fixed to an error prematurely.
+//
+//
+// SUBSUMPTION OF AMBIGUOUS DISJUNCTS
+//
+// A disjunction can be evaluated to a concrete value if only one disjunct
+// remains. Aside from disambiguating through unification failure, disjuncts
+// may also be disambiguated by taking the least specific of two disjuncts.
+// For instance, if a subsumes b, then the result of disjunction may be a.
+//
+//   NEW ALGORITHM NO LONGER VERIFIES SUBSUMPTION. SUBSUMPTION IS INHERENTLY
+//   IMPRECISE (DUE TO BULK OPTIONAL FIELDS). OTHER THAN THAT, FOR SCALAR VALUES
+//   IT JUST MEANS THERE IS AMBIGUITY, AND FOR STRUCTS IT CAN LEAD TO STRANGE
+//   CONSEQUENCES.
+//
+//   USE EQUALITY INSTEAD:
+//     - Undefined == error for optional fields.
+//     - So only need to check exact labels for vertices.
+
+type envDisjunct struct {
+	env         *adt.Environment
+	values      []disjunct
+	numDefaults int
+	cloneID     uint32
+	isEmbed     bool
+}
+
+type disjunct struct {
+	expr      adt.Expr
+	isDefault bool
+}
+
+func (n *nodeContext) addDisjunction(env *adt.Environment, x *adt.DisjunctionExpr, cloneID uint32, isEmbed bool) {
+	a := []disjunct{}
+
+	numDefaults := 0
+	for _, v := range x.Values {
+		isDef := v.Default // || n.hasDefaults(env, v.Val)
+		if isDef {
+			numDefaults++
+		}
+		a = append(a, disjunct{v.Val, isDef})
+	}
+
+	sort.SliceStable(a, func(i, j int) bool {
+		return !a[j].isDefault && a[i].isDefault != a[j].isDefault
+	})
+
+	n.disjunctions = append(n.disjunctions,
+		envDisjunct{env, a, numDefaults, cloneID, isEmbed})
+}
+
+func (n *nodeContext) addDisjunctionValue(env *adt.Environment, x *adt.Disjunction, cloneID uint32, isEmbed bool) {
+	a := []disjunct{}
+
+	for i, v := range x.Values {
+		a = append(a, disjunct{v, i < x.NumDefaults})
+	}
+
+	n.disjunctions = append(n.disjunctions,
+		envDisjunct{env, a, x.NumDefaults, cloneID, isEmbed})
+}
+
+func (n *nodeContext) updateResult() (isFinal bool) {
+	n.postDisjunct()
+
+	if n.hasErr() {
+		return n.isFinal
+	}
+
+	d := n.nodeShared.disjunct
+	if d == nil {
+		d = &adt.Disjunction{}
+		n.nodeShared.disjunct = d
+	}
+
+	result := *n.node
+	if result.Value == nil {
+		result.Value = n.getValidators()
+	}
+
+	for _, v := range d.Values {
+		if Equal(n.ctx, v, &result) {
+			return isFinal
+		}
+	}
+
+	p := &result
+	d.Values = append(d.Values, p)
+	if n.defaultMode == isDefault {
+		// Keep defaults sorted first.
+		i := d.NumDefaults
+		j := i + 1
+		copy(d.Values[j:], d.Values[i:])
+		d.Values[i] = p
+		d.NumDefaults = j
+	}
+
+	// return n.isFinal
+
+	switch {
+	case !n.nodeShared.hasResult():
+
+	case n.nodeShared.isDefault() && n.defaultMode != isDefault:
+		return n.isFinal
+
+	case !n.nodeShared.isDefault() && n.defaultMode == isDefault:
+
+	default:
+		if Equal(n.ctx, n.node, n.result()) {
+			return n.isFinal
+		}
+
+		// TODO: Compute fancy error message.
+		n.nodeShared.resultNode = n
+		// n.nodeShared.result.AddErr(n.ctx, &adt.Bottom{
+		// 	Code: adt.IncompleteError,
+		// 	Err:  errors.Newf(n.ctx.Pos(), "ambiguous disjunction"),
+		// })
+		n.nodeShared.result_.Arcs = nil
+		n.nodeShared.result_.Structs = nil
+		return n.isFinal // n.defaultMode == isDefault
+	}
+
+	n.nodeShared.resultNode = n
+	n.nodeShared.setResult(n.node)
+
+	return n.isFinal
+}
+
+func (n *nodeContext) tryDisjuncts() (finished bool) {
+	if !n.insertDisjuncts() || !n.updateResult() {
+		if !n.isFinal {
+			return false // More iterations to do.
+		}
+	}
+
+	if n.nodeShared.hasResult() {
+		return true // found something
+	}
+
+	if len(n.disjunctions) > 0 {
+		b := &adt.Bottom{
+			// TODO(errors): we should not make this error worse by discarding
+			// the type or error. Using IncompleteError is a compromise. But
+			// really we should keep track of the errors and return a more
+			// accurate result here.
+			Code: adt.IncompleteError,
+			Err:  errors.Newf(token.NoPos, "empty disjunction"),
+		}
+		n.node.AddErr(n.ctx, b)
+	}
+	return true
+}
+
+// TODO: add proper conjuncts for the ones used by the disjunctions to replace
+// the original source.
+//
+func (n *nodeContext) insertDisjuncts() (inserted bool) {
+	p := 0
+	inserted = true
+
+	disjunctions := []envDisjunct{}
+
+	// fmt.Println("----", debug.NodeString(n.ctx, n.node, nil))
+	for _, d := range n.disjunctions {
+		disjunctions = append(disjunctions, d)
+
+		sub := len(n.disjunctions)
+		defMode, ok := n.insertSingleDisjunct(p, d, false)
+		p++
+		if !ok {
+			inserted = false
+			break
+		}
+
+		subMode := []defaultMode{}
+		for ; sub < len(n.disjunctions); sub++ {
+			d := n.disjunctions[sub]
+			disjunctions = append(disjunctions, d)
+			mode, ok := n.insertSingleDisjunct(p, d, true)
+			p++
+			if !ok {
+				inserted = false
+				break
+			}
+			subMode = append(subMode, mode)
+		}
+		for i := len(subMode) - 1; i >= 0; i-- {
+			defMode = combineSubDefault(defMode, subMode[i])
+		}
+
+		// fmt.Println("RESMODE", defMode, combineDefault(n.defaultMode, defMode))
+
+		n.defaultMode = combineDefault(n.defaultMode, defMode)
+	}
+
+	// Find last disjunction at which there is no overflow.
+	for ; p > 0 && n.stack[p-1]+1 >= len(disjunctions[p-1].values); p-- {
+	}
+	if p > 0 {
+		// Increment a valid position and set all subsequent entries to 0.
+		n.stack[p-1]++
+		n.stack = n.stack[:p]
+	}
+	return inserted
+}
+
+func (n *nodeContext) insertSingleDisjunct(p int, d envDisjunct, isSub bool) (mode defaultMode, ok bool) {
+	if p >= len(n.stack) {
+		n.stack = append(n.stack, 0)
+	}
+
+	k := n.stack[p]
+	v := d.values[k]
+	n.isFinal = n.isFinal && k == len(d.values)-1
+	c := adt.MakeConjunct(d.env, v.expr)
+	n.addExprConjunct(c, d.cloneID, d.isEmbed)
+
+	for n.expandOne() {
+	}
+
+	switch {
+	case d.numDefaults == 0:
+		mode = maybeDefault
+	case v.isDefault:
+		mode = isDefault
+	default:
+		mode = notDefault
+	}
+
+	return mode, !n.hasErr()
+}
+
+// Default rules from spec:
+//
+// U1: (v1, d1) & v2       => (v1&v2, d1&v2)
+// U2: (v1, d1) & (v2, d2) => (v1&v2, d1&d2)
+//
+// D1: (v1, d1) | v2       => (v1|v2, d1)
+// D2: (v1, d1) | (v2, d2) => (v1|v2, d1|d2)
+//
+// M1: *v        => (v, v)
+// M2: *(v1, d1) => (v1, d1)
+// or
+// M2: *(v1, d1) => (v1, v1)
+// or
+// M2: *(v1, d1) => v1 if d1 == _|_
+// M2:              d1 otherwise
+//
+// def + maybe -> def
+// not + maybe -> def
+// not + def   -> def
+
+type defaultMode int
+
+const (
+	maybeDefault defaultMode = iota
+	notDefault
+	isDefault
+)
+
+func combineSubDefault(a, b defaultMode) defaultMode {
+	switch {
+	case a == maybeDefault && b == maybeDefault:
+		return maybeDefault
+	case a == maybeDefault && b == notDefault:
+		return notDefault
+	case a == maybeDefault && b == isDefault:
+		return isDefault
+	case a == notDefault && b == maybeDefault:
+		return notDefault
+	case a == notDefault && b == notDefault:
+		return notDefault
+	case a == notDefault && b == isDefault:
+		return isDefault
+	case a == isDefault && b == maybeDefault:
+		return isDefault
+	case a == isDefault && b == notDefault:
+		return notDefault
+	case a == isDefault && b == isDefault:
+		return isDefault
+	default:
+		panic("unreachable")
+	}
+}
+
+func combineDefault(a, b defaultMode) defaultMode {
+	if a > b {
+		a, b = b, a
+	}
+	switch {
+	case a == maybeDefault && b == maybeDefault:
+		return maybeDefault
+	case a == maybeDefault && b == notDefault:
+		return notDefault
+	case a == maybeDefault && b == isDefault:
+		return isDefault
+	case a == notDefault && b == notDefault:
+		return notDefault
+	case a == notDefault && b == isDefault:
+		return notDefault
+	case a == isDefault && b == isDefault:
+		return isDefault
+	default:
+		panic("unreachable")
+	}
+}