internal/core/compile: add integer division builtins

These are to replace the namesake operators,
which will be removed at some point.

Next step is to create a rewriter that rewrites old
uses to the new one.

Change-Id: Ib674559e40f714e24e40e6c9ed71f7eb43231389
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7784
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/builtins/intdiv.txtar b/cue/testdata/builtins/intdiv.txtar
new file mode 100644
index 0000000..d4e427d
--- /dev/null
+++ b/cue/testdata/builtins/intdiv.txtar
@@ -0,0 +1,171 @@
+-- in.cue --
+quo1:  quo(5, 2)   // 2
+quo2:  quo(5, -2)  // -2
+quo3:  quo(-5, 2)  // -2
+quo4:  quo(-5, -2) // 2
+
+quoDivByZero: quo(2, 0)
+
+quoTypeError1: quo(2.0, 1)
+quoTypeError2: quo(2, 1.0)
+
+
+rem1:  rem(5, 2)   // 1
+rem2:  rem(5, -2)  // 1
+rem3:  rem(-5, 2)  // -1
+rem4:  rem(-5, -2) // -1
+
+remDivByZero: rem(2, 0)
+
+remTypeError1: rem(2.0, 1)
+remTypeError2: rem(2, 1.0)
+
+
+div1:  div(5, 2)   // 2
+div2:  div(5, -2)  // -2
+div3:  div(-5, 2)  // -3
+div4:  div(-5, -2) // 3
+
+divDivByZero: div(2, 0)
+
+divTypeError1: div(2.0, 1)
+divTypeError2: div(2, 1.0)
+
+
+mod1:  mod(5, 2)   // 1
+mod2:  mod(5, -2)  // 1
+mod3:  mod(-5, 2)  // 1
+mod4:  mod(-5, -2) // 1
+
+modDivByZero: mod(2, 0)
+
+modTypeError1: mod(2.0, 1)
+modTypeError2: mod(2, 1.0)
+
+-- out/eval --
+Errors:
+quoDivByZero: division by zero:
+    ./in.cue:6:15
+quoTypeError1: cannot use 2.0 (type float) as int in argument 1 to quo:
+    ./in.cue:8:20
+quoTypeError2: cannot use 1.0 (type float) as int in argument 2 to quo:
+    ./in.cue:9:23
+remDivByZero: division by zero:
+    ./in.cue:17:15
+remTypeError1: cannot use 2.0 (type float) as int in argument 1 to rem:
+    ./in.cue:19:20
+remTypeError2: cannot use 1.0 (type float) as int in argument 2 to rem:
+    ./in.cue:20:23
+divDivByZero: division by zero:
+    ./in.cue:28:15
+divTypeError1: cannot use 2.0 (type float) as int in argument 1 to div:
+    ./in.cue:30:20
+divTypeError2: cannot use 1.0 (type float) as int in argument 2 to div:
+    ./in.cue:31:23
+modDivByZero: division by zero:
+    ./in.cue:39:15
+modTypeError1: cannot use 2.0 (type float) as int in argument 1 to mod:
+    ./in.cue:41:20
+modTypeError2: cannot use 1.0 (type float) as int in argument 2 to mod:
+    ./in.cue:42:23
+
+Result:
+(_|_){
+  // [eval]
+  quo1: (int){ 2 }
+  quo2: (int){ -2 }
+  quo3: (int){ -2 }
+  quo4: (int){ 2 }
+  quoDivByZero: (_|_){
+    // [eval] quoDivByZero: division by zero:
+    //     ./in.cue:6:15
+  }
+  quoTypeError1: (_|_){
+    // [eval] quoTypeError1: cannot use 2.0 (type float) as int in argument 1 to quo:
+    //     ./in.cue:8:20
+  }
+  quoTypeError2: (_|_){
+    // [eval] quoTypeError2: cannot use 1.0 (type float) as int in argument 2 to quo:
+    //     ./in.cue:9:23
+  }
+  rem1: (int){ 1 }
+  rem2: (int){ 1 }
+  rem3: (int){ -1 }
+  rem4: (int){ -1 }
+  remDivByZero: (_|_){
+    // [eval] remDivByZero: division by zero:
+    //     ./in.cue:17:15
+  }
+  remTypeError1: (_|_){
+    // [eval] remTypeError1: cannot use 2.0 (type float) as int in argument 1 to rem:
+    //     ./in.cue:19:20
+  }
+  remTypeError2: (_|_){
+    // [eval] remTypeError2: cannot use 1.0 (type float) as int in argument 2 to rem:
+    //     ./in.cue:20:23
+  }
+  div1: (int){ 2 }
+  div2: (int){ -2 }
+  div3: (int){ -3 }
+  div4: (int){ 3 }
+  divDivByZero: (_|_){
+    // [eval] divDivByZero: division by zero:
+    //     ./in.cue:28:15
+  }
+  divTypeError1: (_|_){
+    // [eval] divTypeError1: cannot use 2.0 (type float) as int in argument 1 to div:
+    //     ./in.cue:30:20
+  }
+  divTypeError2: (_|_){
+    // [eval] divTypeError2: cannot use 1.0 (type float) as int in argument 2 to div:
+    //     ./in.cue:31:23
+  }
+  mod1: (int){ 1 }
+  mod2: (int){ 1 }
+  mod3: (int){ 1 }
+  mod4: (int){ 1 }
+  modDivByZero: (_|_){
+    // [eval] modDivByZero: division by zero:
+    //     ./in.cue:39:15
+  }
+  modTypeError1: (_|_){
+    // [eval] modTypeError1: cannot use 2.0 (type float) as int in argument 1 to mod:
+    //     ./in.cue:41:20
+  }
+  modTypeError2: (_|_){
+    // [eval] modTypeError2: cannot use 1.0 (type float) as int in argument 2 to mod:
+    //     ./in.cue:42:23
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  quo1: quo(5, 2)
+  quo2: quo(5, -2)
+  quo3: quo(-5, 2)
+  quo4: quo(-5, -2)
+  quoDivByZero: quo(2, 0)
+  quoTypeError1: quo(2.0, 1)
+  quoTypeError2: quo(2, 1.0)
+  rem1: rem(5, 2)
+  rem2: rem(5, -2)
+  rem3: rem(-5, 2)
+  rem4: rem(-5, -2)
+  remDivByZero: rem(2, 0)
+  remTypeError1: rem(2.0, 1)
+  remTypeError2: rem(2, 1.0)
+  div1: div(5, 2)
+  div2: div(5, -2)
+  div3: div(-5, 2)
+  div4: div(-5, -2)
+  divDivByZero: div(2, 0)
+  divTypeError1: div(2.0, 1)
+  divTypeError2: div(2, 1.0)
+  mod1: mod(5, 2)
+  mod2: mod(5, -2)
+  mod3: mod(-5, 2)
+  mod4: mod(-5, -2)
+  modDivByZero: mod(2, 0)
+  modTypeError1: mod(2.0, 1)
+  modTypeError2: mod(2, 1.0)
+}
diff --git a/internal/core/adt/binop.go b/internal/core/adt/binop.go
index 73670c2..98eab96 100644
--- a/internal/core/adt/binop.go
+++ b/internal/core/adt/binop.go
@@ -76,7 +76,7 @@
 
 		case leftKind&NumKind != 0 && rightKind&NumKind != 0:
 			// n := c.newNum()
-			return cmpTonode(c, op, c.num(left, op).X.Cmp(&c.num(right, op).X))
+			return cmpTonode(c, op, c.Num(left, op).X.Cmp(&c.Num(right, op).X))
 
 		case leftKind == ListKind && rightKind == ListKind:
 			x := c.Elems(left)
@@ -114,7 +114,7 @@
 
 		case leftKind&NumKind != 0 && rightKind&NumKind != 0:
 			// n := c.newNum()
-			return cmpTonode(c, op, c.num(left, op).X.Cmp(&c.num(right, op).X))
+			return cmpTonode(c, op, c.Num(left, op).X.Cmp(&c.Num(right, op).X))
 
 		case leftKind == ListKind && rightKind == ListKind:
 			x := c.Elems(left)
@@ -143,7 +143,7 @@
 
 		case leftKind&NumKind != 0 && rightKind&NumKind != 0:
 			// n := c.newNum(left, right)
-			return cmpTonode(c, op, c.num(left, op).X.Cmp(&c.num(right, op).X))
+			return cmpTonode(c, op, c.Num(left, op).X.Cmp(&c.Num(right, op).X))
 		}
 
 	case BoolAndOp:
@@ -284,38 +284,38 @@
 
 	case IntDivideOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.num(right, op)
+			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 intOp(c, (*big.Int).Div, c.Num(left, op), y)
 		}
 
 	case IntModuloOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.num(right, op)
+			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 intOp(c, (*big.Int).Mod, c.Num(left, op), y)
 		}
 
 	case IntQuotientOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.num(right, op)
+			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 intOp(c, (*big.Int).Quo, c.Num(left, op), y)
 		}
 
 	case IntRemainderOp:
 		if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
-			y := c.num(right, op)
+			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 intOp(c, (*big.Int).Rem, c.Num(left, op), y)
 		}
 	}
 
@@ -346,8 +346,8 @@
 
 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)
+	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)
@@ -359,7 +359,7 @@
 	if k == 0 {
 		k = FloatKind
 	}
-	return c.newNum(&d, k)
+	return c.NewNum(&d, k)
 }
 
 type intFunc func(z, x, y *big.Int) *big.Int
@@ -381,5 +381,5 @@
 		d.Coeff.Neg(&d.Coeff)
 		d.Negative = true
 	}
-	return c.newNum(&d, IntKind)
+	return c.NewNum(&d, IntKind)
 }
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index dd63fbe..330d9fd 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -740,7 +740,7 @@
 
 var zero = &Num{K: NumKind}
 
-func (c *OpContext) num(v Value, as interface{}) *Num {
+func (c *OpContext) Num(v Value, as interface{}) *Num {
 	v = Unwrap(v)
 	if isError(v) {
 		return zero
@@ -930,7 +930,9 @@
 	}
 }
 
-func (c *OpContext) newNum(d *apd.Decimal, k Kind, sources ...Node) 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 {
 	if c.HasErr() {
 		return c.Err()
 	}
diff --git a/internal/core/adt/simplify.go b/internal/core/adt/simplify.go
index 2c2e52b..2fd6480 100644
--- a/internal/core/adt/simplify.go
+++ b/internal/core/adt/simplify.go
@@ -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 99fe450..352235f 100644
--- a/internal/core/compile/builtin.go
+++ b/internal/core/compile/builtin.go
@@ -15,8 +15,11 @@
 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.
@@ -135,3 +138,90 @@
 		return v
 	},
 }
+
+var divBuiltin = &adt.Builtin{
+	Name:   "div",
+	Params: []adt.Kind{adt.IntKind, adt.IntKind},
+	Result: adt.IntKind,
+	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+		const name = "argument to div builtin"
+
+		return intDivOp(c, (*big.Int).Div, name, args)
+	},
+}
+
+var modBuiltin = &adt.Builtin{
+	Name:   "mod",
+	Params: []adt.Kind{adt.IntKind, adt.IntKind},
+	Result: adt.IntKind,
+	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+		const name = "argument to mod builtin"
+
+		return intDivOp(c, (*big.Int).Mod, name, args)
+	},
+}
+
+var quoBuiltin = &adt.Builtin{
+	Name:   "quo",
+	Params: []adt.Kind{adt.IntKind, adt.IntKind},
+	Result: adt.IntKind,
+	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+		const name = "argument to quo builtin"
+
+		return intDivOp(c, (*big.Int).Quo, name, args)
+	},
+}
+
+var remBuiltin = &adt.Builtin{
+	Name:   "rem",
+	Params: []adt.Kind{adt.IntKind, adt.IntKind},
+	Result: adt.IntKind,
+	Func: func(c *adt.OpContext, args []adt.Value) adt.Expr {
+		const name = "argument to rem builtin"
+
+		return intDivOp(c, (*big.Int).Rem, name, args)
+	},
+}
+
+var apdCtx apd.Context
+
+func init() {
+	apdCtx = apd.BaseContext
+	apdCtx.Precision = 24
+}
+
+type intFunc func(z, x, y *big.Int) *big.Int
+
+func intDivOp(c *adt.OpContext, fn intFunc, name string, args []adt.Value) adt.Value {
+	a := c.Num(args[0], name)
+	b := c.Num(args[1], name)
+
+	if c.HasErr() {
+		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)
+}
diff --git a/internal/core/compile/predeclared.go b/internal/core/compile/predeclared.go
index 28d6b59..bc0a8b2 100644
--- a/internal/core/compile/predeclared.go
+++ b/internal/core/compile/predeclared.go
@@ -50,6 +50,14 @@
 		return andBuiltin
 	case "or", "__or":
 		return orBuiltin
+	case "div", "__div":
+		return divBuiltin
+	case "mod", "__mod":
+		return modBuiltin
+	case "quo", "__quo":
+		return quoBuiltin
+	case "rem", "__rem":
+		return remBuiltin
 	}
 
 	if r, ok := predefinedRanges[n.Name]; ok {