github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/internal/core/adt/simplify.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt
    16  
    17  import (
    18  	"github.com/cockroachdb/apd/v2"
    19  )
    20  
    21  // SimplifyBounds collapses bounds if possible. The bound values must be
    22  // concrete. It returns nil if the bound values cannot be collapsed.
    23  //
    24  // k represents additional type constraints, such as `int`.
    25  func SimplifyBounds(ctx *OpContext, k Kind, x, y *BoundValue) Value {
    26  	xv := x.Value
    27  	yv := y.Value
    28  
    29  	cmp, xCat := opInfo(x.Op)
    30  	_, yCat := opInfo(y.Op)
    31  
    32  	// k := x.Kind() & y.Kind()
    33  
    34  	switch {
    35  	case xCat == yCat:
    36  		switch x.Op {
    37  		// NOTE: EqualOp should not happen, but include it defensively.
    38  		// Maybe an API would use it, for instance.
    39  		case EqualOp, NotEqualOp, MatchOp, NotMatchOp:
    40  			if test(ctx, EqualOp, xv, yv) {
    41  				return x
    42  			}
    43  			return nil // keep both bounds
    44  		}
    45  
    46  		// xCat == yCat && x.Op != NotEqualOp
    47  		// > a & >= b
    48  		//    > a   if a >= b
    49  		//    >= b  if a <  b
    50  		// > a & > b
    51  		//    > a   if a >= b
    52  		//    > b   if a <  b
    53  		// >= a & > b
    54  		//    >= a   if a > b
    55  		//    > b    if a <= b
    56  		// >= a & >= b
    57  		//    >= a   if a > b
    58  		//    >= b   if a <= b
    59  		// inverse is true as well.
    60  
    61  		// Tighten bound.
    62  		if test(ctx, cmp, xv, yv) {
    63  			return x
    64  		}
    65  		return y
    66  
    67  	case xCat == -yCat:
    68  		if xCat == -1 {
    69  			x, y = y, x
    70  		}
    71  		a, aOK := xv.(*Num)
    72  		b, bOK := yv.(*Num)
    73  
    74  		if !aOK || !bOK {
    75  			break
    76  		}
    77  
    78  		var d, lo, hi apd.Decimal
    79  		lo.Set(&a.X)
    80  		hi.Set(&b.X)
    81  		if k&FloatKind == 0 {
    82  			// Readjust bounds for integers.
    83  			if x.Op == GreaterEqualOp {
    84  				// >=3.4  ==>  >=4
    85  				_, _ = apdCtx.Ceil(&lo, &a.X)
    86  			} else {
    87  				// >3.4  ==>  >3
    88  				_, _ = apdCtx.Floor(&lo, &a.X)
    89  			}
    90  			if y.Op == LessEqualOp {
    91  				// <=2.3  ==>  <= 2
    92  				_, _ = apdCtx.Floor(&hi, &b.X)
    93  			} else {
    94  				// <2.3   ==>  < 3
    95  				_, _ = apdCtx.Ceil(&hi, &b.X)
    96  			}
    97  		}
    98  
    99  		cond, err := apd.BaseContext.Sub(&d, &hi, &lo)
   100  		if cond.Inexact() || err != nil {
   101  			break
   102  		}
   103  
   104  		// attempt simplification
   105  		// numbers
   106  		// >=a & <=b
   107  		//     a   if a == b
   108  		//     _|_ if a < b
   109  		// >=a & <b
   110  		//     _|_ if b <= a
   111  		// >a  & <=b
   112  		//     _|_ if b <= a
   113  		// >a  & <b
   114  		//     _|_ if b <= a
   115  
   116  		// integers
   117  		// >=a & <=b
   118  		//     a   if b-a == 0
   119  		//     _|_ if a < b
   120  		// >=a & <b
   121  		//     a   if b-a == 1
   122  		//     _|_ if b <= a
   123  		// >a  & <=b
   124  		//     b   if b-a == 1
   125  		//     _|_ if b <= a
   126  		// >a  & <b
   127  		//     a+1 if b-a == 2
   128  		//     _|_ if b <= a
   129  
   130  		switch diff, err := d.Int64(); {
   131  		case diff == 1:
   132  			if k&FloatKind == 0 {
   133  				if x.Op == GreaterEqualOp && y.Op == LessThanOp {
   134  					return ctx.newNum(&lo, k&NumKind, x, y)
   135  				}
   136  				if x.Op == GreaterThanOp && y.Op == LessEqualOp {
   137  					return ctx.newNum(&hi, k&NumKind, x, y)
   138  				}
   139  			}
   140  
   141  		case diff == 2:
   142  			if k&FloatKind == 0 && x.Op == GreaterThanOp && y.Op == LessThanOp {
   143  				_, _ = apd.BaseContext.Add(&d, d.SetInt64(1), &lo)
   144  				return ctx.newNum(&d, k&NumKind, x, y)
   145  
   146  			}
   147  
   148  		case diff == 0 && err == nil:
   149  			if x.Op == GreaterEqualOp && y.Op == LessEqualOp {
   150  				return ctx.newNum(&lo, k&NumKind, x, y)
   151  			}
   152  			fallthrough
   153  
   154  		case d.Negative:
   155  			return ctx.NewErrf("incompatible bounds %v and %v", x, y)
   156  		}
   157  
   158  	case x.Op == NotEqualOp:
   159  		if !test(ctx, y.Op, xv, yv) {
   160  			return y
   161  		}
   162  
   163  	case y.Op == NotEqualOp:
   164  		if !test(ctx, x.Op, yv, xv) {
   165  			return x
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  func opInfo(op Op) (cmp Op, norm int) {
   172  	switch op {
   173  	case GreaterThanOp:
   174  		return GreaterEqualOp, 1
   175  	case GreaterEqualOp:
   176  		return GreaterThanOp, 1
   177  	case LessThanOp:
   178  		return LessEqualOp, -1
   179  	case LessEqualOp:
   180  		return LessThanOp, -1
   181  	case NotEqualOp:
   182  		return NotEqualOp, 0
   183  	case MatchOp:
   184  		return MatchOp, 2
   185  	case NotMatchOp:
   186  		return NotMatchOp, 3
   187  	}
   188  	panic("cue: unreachable")
   189  }
   190  
   191  func test(ctx *OpContext, op Op, a, b Value) bool {
   192  	if b, ok := BinOp(ctx, op, a, b).(*Bool); ok {
   193  		return b.B
   194  	}
   195  	return false
   196  }
   197  
   198  // SimplifyValidator simplifies non-bound validators.
   199  //
   200  // Currently this only checks for pure equality. In the future this can be used
   201  // to simplify certain builtin validators analogously to how we simplify bounds
   202  // now.
   203  func SimplifyValidator(ctx *OpContext, v, w Validator) Validator {
   204  	switch x := v.(type) {
   205  	case *BuiltinValidator:
   206  		switch y := w.(type) {
   207  		case *BuiltinValidator:
   208  			if x == y {
   209  				return x
   210  			}
   211  			if x.Builtin != y.Builtin || len(x.Args) != len(y.Args) {
   212  				return nil
   213  			}
   214  			for i, a := range x.Args {
   215  				if !Equal(ctx, a, y.Args[i], CheckStructural) {
   216  					return nil
   217  				}
   218  			}
   219  			return x
   220  		}
   221  	}
   222  	return nil
   223  }