Add other simple arithmetic built-ins

This commit is contained in:
Torin Sandall
2016-06-14 09:14:04 -07:00
parent fa97fdd10a
commit 1f94f39231
4 changed files with 107 additions and 17 deletions
+29 -1
View File
@@ -21,7 +21,7 @@ func RegisterBuiltin(b *Builtin) {
var DefaultBuiltins = [...]*Builtin{
Equality,
GreaterThan, GreaterThanEq, LessThan, LessThanEq, NotEqual,
Plus,
Plus, Minus, Multiply, Divide, Round,
Count, Sum,
ToNumber,
}
@@ -92,6 +92,34 @@ var Plus = &Builtin{
TargetPos: []int{2},
}
// Minus subtracts the second number from the first number.
var Minus = &Builtin{
Name: Var("minus"),
NumArgs: 3,
TargetPos: []int{2},
}
// Multiply multiplies two numbers together.
var Multiply = &Builtin{
Name: Var("mul"),
NumArgs: 3,
TargetPos: []int{2},
}
// Divide divides the first number by the second number.
var Divide = &Builtin{
Name: Var("div"),
NumArgs: 3,
TargetPos: []int{2},
}
// Round rounds the number up to the nearest integer.
var Round = &Builtin{
Name: Var("round"),
NumArgs: 2,
TargetPos: []int{1},
}
/**
* Aggregates
*/
+69 -15
View File
@@ -5,34 +5,88 @@
package topdown
import (
"fmt"
"math"
"github.com/open-policy-agent/opa/ast"
"github.com/pkg/errors"
)
func evalPlus(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
type arithmeticFunc func(a, b float64) (ast.Number, error)
func arithPlus(a, b float64) (ast.Number, error) {
return ast.Number(a + b), nil
}
func arithMinus(a, b float64) (ast.Number, error) {
return ast.Number(a - b), nil
}
func arithMultiply(a, b float64) (ast.Number, error) {
return ast.Number(a * b), nil
}
func arithDivide(a, b float64) (ast.Number, error) {
if b == 0 {
return 0, fmt.Errorf("divide: by zero")
}
return ast.Number(a / b), nil
}
func arithRound(a float64) (ast.Number, error) {
return ast.Number(math.Floor(a + 0.5)), nil
}
func evalRound(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
a, err := ValueToFloat64(ops[1].Value, ctx)
if err != nil {
return errors.Wrapf(err, "plus")
return errors.Wrapf(err, "round")
}
b, err := ValueToFloat64(ops[2].Value, ctx)
if err != nil {
return errors.Wrapf(err, "plus")
}
c := ops[3].Value
r := ast.Number(a + b)
switch c := c.(type) {
r := ast.Number(math.Floor(a + 0.5))
b := ops[2].Value
switch b := b.(type) {
case ast.Var:
ctx = ctx.BindVar(c, r)
ctx = ctx.BindVar(b, r)
return iter(ctx)
default:
if r.Equal(c) {
if b.Equal(r) {
return iter(ctx)
}
return nil
}
}
func evalArithmetic(f arithmeticFunc) BuiltinFunc {
return func(ctx *Context, expr *ast.Expr, iter Iterator) error {
ops := expr.Terms.([]*ast.Term)
a, err := ValueToFloat64(ops[1].Value, ctx)
if err != nil {
return errors.Wrapf(err, "arithemtic")
}
b, err := ValueToFloat64(ops[2].Value, ctx)
if err != nil {
return errors.Wrapf(err, "arithemtic")
}
c, err := f(a, b)
if err != nil {
return err
}
cv := ops[3].Value
switch cv := cv.(type) {
case ast.Var:
ctx = ctx.BindVar(cv, c)
return iter(ctx)
default:
if cv.Equal(c) {
return iter(ctx)
}
return nil
}
}
}
+5 -1
View File
@@ -31,7 +31,11 @@ var defaultBuiltinFuncs = map[ast.Var]BuiltinFunc{
ast.LessThan.Name: evalIneq(compareLessThan),
ast.LessThanEq.Name: evalIneq(compareLessThanEq),
ast.NotEqual.Name: evalIneq(compareNotEq),
ast.Plus.Name: evalPlus,
ast.Plus.Name: evalArithmetic(arithPlus),
ast.Minus.Name: evalArithmetic(arithMinus),
ast.Multiply.Name: evalArithmetic(arithMultiply),
ast.Divide.Name: evalArithmetic(arithDivide),
ast.Round.Name: evalRound,
ast.Count.Name: evalCount,
ast.Sum.Name: evalSum,
ast.ToNumber.Name: evalToNumber,
+4
View File
@@ -604,6 +604,10 @@ func TestTopDownArithmetic(t *testing.T) {
expected interface{}
}{
{"plus", []string{"p[y] :- a[i] = x, plus(i, x, y)"}, "[1,3,5,7]"},
{"minus", []string{"p[y] :- a[i] = x, minus(i, x, y)"}, "[-1,-1,-1,-1]"},
{"multiply", []string{"p[y] :- a[i] = x, mul(i, x, y)"}, "[0,2,6,12]"},
{"divide+round", []string{"p[z] :- a[i] = x, div(i, x, y), round(y, z)"}, "[0,1,1,1]"},
{"divide+error", []string{"p[y] :- a[i] = x, div(x, i, y)"}, fmt.Errorf("divide: by zero")},
}
data := loadSmallTestData()