blob: 4c87a49612251a4024931985ca5f19f11e4a8f6a [file] [log] [blame]
// 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 (
"bytes"
"strings"
)
// BinOp handles all operations except AndOp and OrOp. This includes processing
// unary comparators such as '<4' and '=~"foo"'.
//
// BinOp returns nil if not both left and right are concrete.
func BinOp(c *OpContext, op Op, left, right Value) Value {
leftKind := left.Kind()
rightKind := right.Kind()
const msg = "non-concrete value '%v' to operation '%s'"
if left.Concreteness() > Concrete {
return &Bottom{
Code: IncompleteError,
Err: c.Newf(msg, c.Str(left), op),
}
}
if right.Concreteness() > Concrete {
return &Bottom{
Code: IncompleteError,
Err: c.Newf(msg, c.Str(right), op),
}
}
if err := CombineErrors(c.src, left, right); err != nil {
return err
}
switch op {
case EqualOp:
switch {
case leftKind == NullKind && rightKind == NullKind:
return c.newBool(true)
case leftKind == NullKind || rightKind == NullKind:
return c.newBool(false)
case leftKind == BoolKind:
return c.newBool(c.BoolValue(left) == c.BoolValue(right))
case leftKind == StringKind:
// normalize?
return cmpTonode(c, op, strings.Compare(c.StringValue(left), c.StringValue(right)))
case leftKind == BytesKind:
return cmpTonode(c, op, bytes.Compare(c.bytesValue(left, op), c.bytesValue(right, op)))
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))
case leftKind == ListKind && rightKind == ListKind:
x := c.Elems(left)
y := c.Elems(right)
if len(x) != len(y) {
return c.newBool(false)
}
for i, e := range x {
a, _ := c.Concrete(nil, e, op)
b, _ := c.Concrete(nil, y[i], op)
if !test(c, EqualOp, a, b) {
return c.newBool(false)
}
}
return c.newBool(true)
}
case NotEqualOp:
switch {
case leftKind == NullKind && rightKind == NullKind:
return c.newBool(false)
case leftKind == NullKind || rightKind == NullKind:
return c.newBool(true)
case leftKind == BoolKind:
return c.newBool(c.boolValue(left, op) != c.boolValue(right, op))
case leftKind == StringKind:
// normalize?
return cmpTonode(c, op, strings.Compare(c.StringValue(left), c.StringValue(right)))
case leftKind == BytesKind:
return cmpTonode(c, op, bytes.Compare(c.bytesValue(left, op), c.bytesValue(right, op)))
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))
case leftKind == ListKind && rightKind == ListKind:
x := c.Elems(left)
y := c.Elems(right)
if len(x) != len(y) {
return c.newBool(false)
}
for i, e := range x {
a, _ := c.Concrete(nil, e, op)
b, _ := c.Concrete(nil, y[i], op)
if !test(c, EqualOp, a, b) {
return c.newBool(true)
}
}
return c.newBool(false)
}
case LessThanOp, LessEqualOp, GreaterEqualOp, GreaterThanOp:
switch {
case leftKind == StringKind && rightKind == StringKind:
// normalize?
return cmpTonode(c, op, strings.Compare(c.stringValue(left, op), c.stringValue(right, op)))
case leftKind == BytesKind && rightKind == BytesKind:
return cmpTonode(c, op, bytes.Compare(c.bytesValue(left, op), c.bytesValue(right, op)))
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))
}
case BoolAndOp:
return c.newBool(c.boolValue(left, op) && c.boolValue(right, op))
case BoolOrOp:
return c.newBool(c.boolValue(left, op) || c.boolValue(right, op))
case MatchOp:
// if y.re == nil {
// // This really should not happen, but leave in for safety.
// b, err := Regexp.MatchString(str, x.str)
// if err != nil {
// return c.Errf(Src, "error parsing Regexp: %v", err)
// }
// return boolTonode(Src, b)
// }
return c.newBool(c.regexp(right).MatchString(c.stringValue(left, op)))
case NotMatchOp:
return c.newBool(!c.regexp(right).MatchString(c.stringValue(left, op)))
case AddOp:
switch {
case leftKind&NumKind != 0 && rightKind&NumKind != 0:
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))
case leftKind == BytesKind && rightKind == BytesKind:
ba := c.bytesValue(left, op)
bb := c.bytesValue(right, op)
b := make([]byte, len(ba)+len(bb))
copy(b, ba)
copy(b[len(ba):], bb)
return c.newBytes(b)
case leftKind == ListKind && rightKind == ListKind:
// TODO: get rid of list addition. Semantically it is somewhat
// unclear and, as it turns out, it is also hard to get right.
// Simulate addition with comprehensions now.
if err := c.Err(); err != nil {
return err
}
x := MakeIdentLabel(c, "x", "")
forClause := func(src Expr) *ForClause {
return &ForClause{
Value: x,
Src: src,
Dst: &ValueClause{&StructLit{Decls: []Decl{
&FieldReference{UpCount: 1, Label: x},
}}},
}
}
list := &ListLit{
Elems: []Elem{
forClause(left),
forClause(right),
},
}
n := &Vertex{}
n.AddConjunct(MakeRootConjunct(c.Env(0), list))
n.Finalize(c)
return n
}
case SubtractOp:
return c.Sub(c.Num(left, op), c.Num(right, op))
case MultiplyOp:
switch {
// float
case leftKind&NumKind != 0 && rightKind&NumKind != 0:
return c.Mul(c.Num(left, op), c.Num(right, op))
case leftKind == StringKind && rightKind == IntKind:
const as = "string multiplication"
return c.NewString(strings.Repeat(c.stringValue(left, as), int(c.uint64(right, as))))
case leftKind == IntKind && rightKind == StringKind:
const as = "string multiplication"
return c.NewString(strings.Repeat(c.stringValue(right, as), int(c.uint64(left, as))))
case leftKind == BytesKind && rightKind == IntKind:
const as = "bytes multiplication"
return c.newBytes(bytes.Repeat(c.bytesValue(left, as), int(c.uint64(right, as))))
case leftKind == IntKind && rightKind == BytesKind:
const as = "bytes multiplication"
return c.newBytes(bytes.Repeat(c.bytesValue(right, as), int(c.uint64(left, as))))
case leftKind == ListKind && rightKind == IntKind:
left, right = right, left
fallthrough
case leftKind == IntKind && rightKind == ListKind:
// TODO: get rid of list multiplication.
list := &ListLit{}
x := MakeIdentLabel(c, "x", "")
for i := c.uint64(left, "list multiplier"); i > 0; i-- {
list.Elems = append(list.Elems,
&ForClause{
Value: x,
Src: right,
Dst: &ValueClause{&StructLit{Decls: []Decl{
&FieldReference{UpCount: 1, Label: x},
}}},
},
)
}
if err := c.Err(); err != nil {
return err
}
n := &Vertex{}
n.AddConjunct(MakeRootConjunct(c.Env(0), list))
n.Finalize(c)
return n
}
case FloatQuotientOp:
if leftKind&NumKind != 0 && rightKind&NumKind != 0 {
return c.Quo(c.Num(left, op), c.Num(right, op))
}
case IntDivideOp:
if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
return c.IntDiv(c.Num(left, op), c.Num(right, op))
}
case IntModuloOp:
if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
return c.IntMod(c.Num(left, op), c.Num(right, op))
}
case IntQuotientOp:
if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
return c.IntQuo(c.Num(left, op), c.Num(right, op))
}
case IntRemainderOp:
if leftKind&IntKind != 0 && rightKind&IntKind != 0 {
return c.IntRem(c.Num(left, op), c.Num(right, op))
}
}
return c.NewErrf("invalid operands %s and %s to '%s' (type %s and %s)",
c.Str(left), c.Str(right), op, left.Kind(), right.Kind())
}
func cmpTonode(c *OpContext, op Op, r int) Value {
result := false
switch op {
case LessThanOp:
result = r == -1
case LessEqualOp:
result = r != 1
case EqualOp, AndOp:
result = r == 0
case NotEqualOp:
result = r != 0
case GreaterEqualOp:
result = r != -1
case GreaterThanOp:
result = r == 1
}
return c.newBool(result)
}