internal/core/adt: move decimal logic into one place

Or at least a start.

This prepares for the new numbering system, where
1 and 1.0 can be used interchangeably.

Change-Id: Ic311be58d59f75dc533b1bcda2ceb5bda7915056
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7881
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/context.go b/cue/context.go
index 1afa832..33d213d 100644
--- a/cue/context.go
+++ b/cue/context.go
@@ -18,28 +18,18 @@
 	"cuelang.org/go/internal/core/adt"
 	"cuelang.org/go/internal/core/debug"
 	"cuelang.org/go/internal/core/eval"
-	"github.com/cockroachdb/apd/v2"
 )
 
 // context manages evaluation state.
 type context struct {
 	opCtx *adt.OpContext
-	*apd.Context
 	*index
 }
 
-var baseContext apd.Context
-
-func init() {
-	baseContext = apd.BaseContext
-	baseContext.Precision = 24
-}
-
 // newContext returns a new evaluation context.
 func (idx *index) newContext() *context {
 	c := &context{
-		Context: &baseContext,
-		index:   idx,
+		index: idx,
 	}
 	if idx != nil {
 		c.opCtx = eval.NewContext(idx.Runtime, nil)
diff --git a/internal/core/adt/binop.go b/internal/core/adt/binop.go
index 98eab96..4c87a49 100644
--- a/internal/core/adt/binop.go
+++ b/internal/core/adt/binop.go
@@ -16,19 +16,9 @@
 
 import (
 	"bytes"
-	"math/big"
 	"strings"
-
-	"github.com/cockroachdb/apd/v2"
 )
 
-var apdCtx apd.Context
-
-func init() {
-	apdCtx = apd.BaseContext
-	apdCtx.Precision = 24
-}
-
 // BinOp handles all operations except AndOp and OrOp. This includes processing
 // unary comparators such as '<4' and '=~"foo"'.
 //
@@ -169,7 +159,7 @@
 	case AddOp:
 		switch {
 		case leftKind&NumKind != 0 && rightKind&NumKind != 0:
-			return numOp(c, apdCtx.Add, left, right, AddOp)
+			return c.Add(c.Num(left, op), c.Num(right, op))
 
 		case leftKind == StringKind && rightKind == StringKind:
 			return c.NewString(c.StringValue(left) + c.StringValue(right))
@@ -217,13 +207,13 @@
 		}
 
 	case SubtractOp:
-		return numOp(c, apdCtx.Sub, left, right, op)
+		return c.Sub(c.Num(left, op), c.Num(right, op))
 
 	case MultiplyOp:
 		switch {
 		// float
 		case leftKind&NumKind != 0 && rightKind&NumKind != 0:
-			return numOp(c, apdCtx.Mul, left, right, op)
+			return c.Mul(c.Num(left, op), c.Num(right, op))
 
 		case leftKind == StringKind && rightKind == IntKind:
 			const as = "string multiplication"
@@ -275,47 +265,27 @@
 
 	case FloatQuotientOp:
 		if leftKind&NumKind != 0 && rightKind&NumKind != 0 {
-			v := numOp(c, apdCtx.Quo, left, right, op)
-			if n, ok := v.(*Num); ok {
-				n.K = FloatKind
-			}
-			return v
+			return c.Quo(c.Num(left, op), c.Num(right, op))
 		}
 
 	case IntDivideOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.Num(right, op)
-			if y.X.IsZero() {
-				return c.NewErrf("division by zero")
-			}
-			return intOp(c, (*big.Int).Div, c.Num(left, op), y)
+			return c.IntDiv(c.Num(left, op), c.Num(right, op))
 		}
 
 	case IntModuloOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.Num(right, op)
-			if y.X.IsZero() {
-				return c.NewErrf("division by zero")
-			}
-			return intOp(c, (*big.Int).Mod, c.Num(left, op), y)
+			return c.IntMod(c.Num(left, op), c.Num(right, op))
 		}
 
 	case IntQuotientOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.Num(right, op)
-			if y.X.IsZero() {
-				return c.NewErrf("division by zero")
-			}
-			return intOp(c, (*big.Int).Quo, c.Num(left, op), y)
+			return c.IntQuo(c.Num(left, op), c.Num(right, op))
 		}
 
 	case IntRemainderOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.Num(right, op)
-			if y.X.IsZero() {
-				return c.NewErrf("division by zero")
-			}
-			return intOp(c, (*big.Int).Rem, c.Num(left, op), y)
+			return c.IntRem(c.Num(left, op), c.Num(right, op))
 		}
 	}
 
@@ -341,45 +311,3 @@
 	}
 	return c.newBool(result)
 }
-
-type numFunc func(z, x, y *apd.Decimal) (apd.Condition, error)
-
-func numOp(c *OpContext, fn numFunc, a, b Value, op Op) Value {
-	var d apd.Decimal
-	x := c.Num(a, op)
-	y := c.Num(b, op)
-	cond, err := fn(&d, &x.X, &y.X)
-	if err != nil {
-		return c.NewErrf("failed arithmetic: %v", err)
-	}
-	if cond.DivisionByZero() {
-		return c.NewErrf("division by zero")
-	}
-	k := x.Kind() & y.Kind()
-	if k == 0 {
-		k = FloatKind
-	}
-	return c.NewNum(&d, k)
-}
-
-type intFunc func(z, x, y *big.Int) *big.Int
-
-func intOp(c *OpContext, fn intFunc, a, b *Num) Value {
-	var d apd.Decimal
-
-	var x, y apd.Decimal
-	_, _ = apdCtx.RoundToIntegralValue(&x, &a.X)
-	if x.Negative {
-		x.Coeff.Neg(&x.Coeff)
-	}
-	_, _ = apdCtx.RoundToIntegralValue(&y, &b.X)
-	if y.Negative {
-		y.Coeff.Neg(&y.Coeff)
-	}
-	fn(&d.Coeff, &x.Coeff, &y.Coeff)
-	if d.Coeff.Sign() < 0 {
-		d.Coeff.Neg(&d.Coeff)
-		d.Negative = true
-	}
-	return c.NewNum(&d, IntKind)
-}
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index bac1a27..54a4f03 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -976,9 +976,9 @@
 	}
 }
 
-// NewNum creates a new number of the given kind. It reports an error value
+// newNum creates a new number of the given kind. It reports an error value
 // instead if any error occurred.
-func (c *OpContext) NewNum(d *apd.Decimal, k Kind, sources ...Node) Value {
+func (c *OpContext) newNum(d *apd.Decimal, k Kind, sources ...Node) Value {
 	if c.HasErr() {
 		return c.Err()
 	}
diff --git a/internal/core/adt/decimal.go b/internal/core/adt/decimal.go
new file mode 100644
index 0000000..e7eba38
--- /dev/null
+++ b/internal/core/adt/decimal.go
@@ -0,0 +1,131 @@
+// 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 adt
+
+import (
+	"math/big"
+
+	"github.com/cockroachdb/apd/v2"
+)
+
+var apdCtx apd.Context
+
+func init() {
+	apdCtx = apd.BaseContext
+	apdCtx.Precision = 24
+}
+
+func (n *Num) Impl() *apd.Decimal {
+	return &n.X
+}
+
+func (n *Num) Negative() bool {
+	return n.X.Negative
+}
+
+func (a *Num) Cmp(b *Num) int {
+	return a.X.Cmp(&b.X)
+}
+
+func (c *OpContext) Add(a, b *Num) Value {
+	return numOp(c, apdCtx.Add, a, b)
+}
+
+func (c *OpContext) Sub(a, b *Num) Value {
+	return numOp(c, apdCtx.Sub, a, b)
+}
+
+func (c *OpContext) Mul(a, b *Num) Value {
+	return numOp(c, apdCtx.Mul, a, b)
+}
+
+func (c *OpContext) Quo(a, b *Num) Value {
+	v := numOp(c, apdCtx.Quo, a, b)
+	if n, ok := v.(*Num); ok {
+		n.K = FloatKind
+	}
+	return v
+}
+
+func (c *OpContext) Pow(a, b *Num) Value {
+	return numOp(c, apdCtx.Pow, a, b)
+}
+
+type numFunc func(z, x, y *apd.Decimal) (apd.Condition, error)
+
+func numOp(c *OpContext, fn numFunc, x, y *Num) Value {
+	var d apd.Decimal
+
+	cond, err := fn(&d, &x.X, &y.X)
+
+	if err != nil {
+		return c.NewErrf("failed arithmetic: %v", err)
+	}
+
+	if cond.DivisionByZero() {
+		return c.NewErrf("division by zero")
+	}
+
+	k := x.Kind() & y.Kind()
+	if k == 0 {
+		k = FloatKind
+	}
+	return c.newNum(&d, k)
+}
+
+func (c *OpContext) IntDiv(a, b *Num) Value {
+	return intDivOp(c, (*big.Int).Div, a, b)
+}
+
+func (c *OpContext) IntMod(a, b *Num) Value {
+	return intDivOp(c, (*big.Int).Mod, a, b)
+}
+
+func (c *OpContext) IntQuo(a, b *Num) Value {
+	return intDivOp(c, (*big.Int).Quo, a, b)
+}
+
+func (c *OpContext) IntRem(a, b *Num) Value {
+	return intDivOp(c, (*big.Int).Rem, a, b)
+}
+
+type intFunc func(z, x, y *big.Int) *big.Int
+
+func intDivOp(c *OpContext, fn intFunc, a, b *Num) Value {
+	if b.X.IsZero() {
+		return c.NewErrf("division by zero")
+	}
+
+	var x, y apd.Decimal
+	_, _ = apdCtx.RoundToIntegralValue(&x, &a.X)
+	if x.Negative {
+		x.Coeff.Neg(&x.Coeff)
+	}
+	_, _ = apdCtx.RoundToIntegralValue(&y, &b.X)
+	if y.Negative {
+		y.Coeff.Neg(&y.Coeff)
+	}
+
+	var d apd.Decimal
+
+	fn(&d.Coeff, &x.Coeff, &y.Coeff)
+
+	if d.Coeff.Sign() < 0 {
+		d.Coeff.Neg(&d.Coeff)
+		d.Negative = true
+	}
+
+	return c.newNum(&d, IntKind)
+}
diff --git a/internal/core/adt/simplify.go b/internal/core/adt/simplify.go
index 2fd6480..bd5edc3 100644
--- a/internal/core/adt/simplify.go
+++ b/internal/core/adt/simplify.go
@@ -82,17 +82,17 @@
 			// Readjust bounds for integers.
 			if x.Op == GreaterEqualOp {
 				// >=3.4  ==>  >=4
-				_, _ = apd.BaseContext.Ceil(&lo, &a.X)
+				_, _ = apdCtx.Ceil(&lo, &a.X)
 			} else {
 				// >3.4  ==>  >3
-				_, _ = apd.BaseContext.Floor(&lo, &a.X)
+				_, _ = apdCtx.Floor(&lo, &a.X)
 			}
 			if y.Op == LessEqualOp {
 				// <=2.3  ==>  <= 2
-				_, _ = apd.BaseContext.Floor(&hi, &b.X)
+				_, _ = apdCtx.Floor(&hi, &b.X)
 			} else {
 				// <2.3   ==>  < 3
-				_, _ = apd.BaseContext.Ceil(&hi, &b.X)
+				_, _ = apdCtx.Ceil(&hi, &b.X)
 			}
 		}
 
@@ -133,23 +133,23 @@
 		case diff == 1:
 			if k&FloatKind == 0 {
 				if x.Op == GreaterEqualOp && y.Op == LessThanOp {
-					return ctx.NewNum(&lo, k&NumKind, x, y)
+					return ctx.newNum(&lo, k&NumKind, x, y)
 				}
 				if x.Op == GreaterThanOp && y.Op == LessEqualOp {
-					return ctx.NewNum(&hi, k&NumKind, x, y)
+					return ctx.newNum(&hi, k&NumKind, x, y)
 				}
 			}
 
 		case diff == 2:
 			if k&FloatKind == 0 && x.Op == GreaterThanOp && y.Op == LessThanOp {
 				_, _ = apd.BaseContext.Add(&d, d.SetInt64(1), &lo)
-				return ctx.NewNum(&d, k&NumKind, x, y)
+				return ctx.newNum(&d, k&NumKind, x, y)
 
 			}
 
 		case diff == 0:
 			if x.Op == GreaterEqualOp && y.Op == LessEqualOp {
-				return ctx.NewNum(&lo, k&NumKind, x, y)
+				return ctx.newNum(&lo, k&NumKind, x, y)
 			}
 			fallthrough
 
diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go
index 7a0af90..c1edc7e 100644
--- a/internal/core/compile/builtin.go
+++ b/internal/core/compile/builtin.go
@@ -15,11 +15,8 @@
 package compile
 
 import (
-	"math/big"
-
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/internal/core/adt"
-	"github.com/cockroachdb/apd/v2"
 )
 
 // This file contains predeclared builtins.
@@ -153,7 +150,7 @@
 	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
 		const name = "argument to div builtin"
 
-		return intDivOp(c, (*big.Int).Div, name, args)
+		return intDivOp(c, (*adt.OpContext).IntDiv, name, args)
 	},
 }
 
@@ -164,7 +161,7 @@
 	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
 		const name = "argument to mod builtin"
 
-		return intDivOp(c, (*big.Int).Mod, name, args)
+		return intDivOp(c, (*adt.OpContext).IntMod, name, args)
 	},
 }
 
@@ -175,7 +172,7 @@
 	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
 		const name = "argument to quo builtin"
 
-		return intDivOp(c, (*big.Int).Quo, name, args)
+		return intDivOp(c, (*adt.OpContext).IntQuo, name, args)
 	},
 }
 
@@ -186,18 +183,11 @@
 	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
 		const name = "argument to rem builtin"
 
-		return intDivOp(c, (*big.Int).Rem, name, args)
+		return intDivOp(c, (*adt.OpContext).IntRem, name, args)
 	},
 }
 
-var apdCtx apd.Context
-
-func init() {
-	apdCtx = apd.BaseContext
-	apdCtx.Precision = 24
-}
-
-type intFunc func(z, x, y *big.Int) *big.Int
+type intFunc func(c *adt.OpContext, x, y *adt.Num) adt.Value
 
 func intDivOp(c *adt.OpContext, fn intFunc, name string, args []adt.Value) adt.Value {
 	a := c.Num(args[0], name)
@@ -207,28 +197,5 @@
 		return nil
 	}
 
-	if b.X.IsZero() {
-		return c.NewErrf("division by zero")
-	}
-
-	var x, y apd.Decimal
-	_, _ = apdCtx.RoundToIntegralValue(&x, &a.X)
-	if x.Negative {
-		x.Coeff.Neg(&x.Coeff)
-	}
-	_, _ = apdCtx.RoundToIntegralValue(&y, &b.X)
-	if y.Negative {
-		y.Coeff.Neg(&y.Coeff)
-	}
-
-	var d apd.Decimal
-
-	fn(&d.Coeff, &x.Coeff, &y.Coeff)
-
-	if d.Coeff.Sign() < 0 {
-		d.Coeff.Neg(&d.Coeff)
-		d.Negative = true
-	}
-
-	return c.NewNum(&d, adt.IntKind)
+	return fn(c, a, b)
 }