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 {