cuelang.org/go@v0.13.0/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 "bytes" 19 "strings" 20 21 "github.com/cockroachdb/apd/v3" 22 23 "cuelang.org/go/internal" 24 ) 25 26 // SimplifyBounds collapses bounds if possible. The bound values must be 27 // concrete. It returns nil if the bound values cannot be collapsed. 28 // 29 // k represents additional type constraints, such as `int`. 30 func SimplifyBounds(ctx *OpContext, k Kind, x, y *BoundValue) Value { 31 xv := x.Value 32 yv := y.Value 33 34 cmp, xCat := opInfo(x.Op) 35 _, yCat := opInfo(y.Op) 36 37 // k := x.Kind() & y.Kind() 38 39 switch { 40 case xCat == yCat: 41 switch x.Op { 42 // NOTE: EqualOp should not happen, but include it defensively. 43 // Maybe an API would use it, for instance. 44 case EqualOp, NotEqualOp, MatchOp, NotMatchOp: 45 if test(ctx, EqualOp, xv, yv) { 46 return x 47 } 48 return nil // keep both bounds 49 } 50 51 // xCat == yCat && x.Op != NotEqualOp 52 // > a & >= b 53 // > a if a >= b 54 // >= b if a < b 55 // > a & > b 56 // > a if a >= b 57 // > b if a < b 58 // >= a & > b 59 // >= a if a > b 60 // > b if a <= b 61 // >= a & >= b 62 // >= a if a > b 63 // >= b if a <= b 64 // inverse is true as well. 65 66 // Tighten bound. 67 if test(ctx, cmp, xv, yv) { 68 return x 69 } 70 return y 71 72 case xCat == -yCat && k == StringKind: 73 if xCat == -1 { 74 x, y = y, x 75 xv, yv = yv, xv 76 } 77 78 a, aOK := xv.(*String) 79 b, bOK := yv.(*String) 80 81 if !aOK || !bOK { 82 break 83 } 84 85 switch diff := strings.Compare(a.Str, b.Str); diff { 86 case -1: 87 case 0: 88 if x.Op == GreaterEqualOp && y.Op == LessEqualOp { 89 return ctx.NewString(a.Str) 90 } 91 fallthrough 92 93 case 1: 94 return ctx.NewErrf("incompatible string bounds %v and %v", y, x) 95 } 96 97 case xCat == -yCat && k == BytesKind: 98 if xCat == -1 { 99 x, y = y, x 100 xv, yv = yv, xv 101 } 102 103 a, aOK := xv.(*Bytes) 104 b, bOK := yv.(*Bytes) 105 106 if !aOK || !bOK { 107 break 108 } 109 110 switch diff := bytes.Compare(a.B, b.B); diff { 111 case -1: 112 case 0: 113 if x.Op == GreaterEqualOp && y.Op == LessEqualOp { 114 return ctx.newBytes(a.B) 115 } 116 fallthrough 117 118 case 1: 119 return ctx.NewErrf("incompatible bytes bounds %v and %v", y, x) 120 } 121 122 case xCat == -yCat: 123 if xCat == -1 { 124 x, y = y, x 125 xv, yv = yv, xv 126 } 127 a, aOK := xv.(*Num) 128 b, bOK := yv.(*Num) 129 130 if !aOK || !bOK { 131 break 132 } 133 134 var d, lo, hi apd.Decimal 135 lo.Set(&a.X) 136 hi.Set(&b.X) 137 if k&FloatKind == 0 { 138 // Readjust bounds for integers. 139 if x.Op == GreaterEqualOp { 140 // >=3.4 ==> >=4 141 _, _ = internal.BaseContext.Ceil(&lo, &a.X) 142 } else { 143 // >3.4 ==> >3 144 _, _ = internal.BaseContext.Floor(&lo, &a.X) 145 } 146 if y.Op == LessEqualOp { 147 // <=2.3 ==> <= 2 148 _, _ = internal.BaseContext.Floor(&hi, &b.X) 149 } else { 150 // <2.3 ==> < 3 151 _, _ = internal.BaseContext.Ceil(&hi, &b.X) 152 } 153 } 154 155 cond, err := internal.BaseContext.Sub(&d, &hi, &lo) 156 if cond.Inexact() || err != nil { 157 break 158 } 159 160 // attempt simplification 161 // numbers 162 // >=a & <=b 163 // a if a == b 164 // _|_ if b < a 165 // >=a & <b 166 // _|_ if b <= a 167 // >a & <=b 168 // _|_ if b <= a 169 // >a & <b 170 // _|_ if b <= a 171 172 // integers 173 // >=a & <=b 174 // a if b-a == 0 175 // _|_ if b < a 176 // >=a & <b 177 // a if b-a == 1 178 // _|_ if b <= a 179 // >a & <=b 180 // b if b-a == 1 181 // _|_ if b <= a 182 // >a & <b 183 // a+1 if b-a == 2 184 // _|_ if b <= a 185 186 if d.Negative { 187 return errIncompatibleBounds(ctx, k, x, y) 188 } 189 // [apd.Decimal.Int64] on `d = hi - lo` will error if it overflows an int64. 190 // This is pretty common with CUE bounds like int64, which expands to: 191 // 192 // >=-9_223_372_036_854_775_808 & <=9_223_372_036_854_775_807 193 // 194 // Constructing that error is unfortunate as it allocates a few times 195 // and stringifies the number too, which also has a cost. 196 // Which is entirely unnecessary, as we don't use the error value at all. 197 // If we know the integer will have more than one digit, give up early. 198 if d.NumDigits() > 1 { 199 break 200 } 201 switch diff, err := d.Int64(); { 202 case diff == 1: 203 if k&FloatKind == 0 { 204 if x.Op == GreaterEqualOp && y.Op == LessThanOp { 205 return ctx.newNum(&lo, k&NumberKind, x, y) 206 } 207 if x.Op == GreaterThanOp && y.Op == LessEqualOp { 208 return ctx.newNum(&hi, k&NumberKind, x, y) 209 } 210 if x.Op == GreaterThanOp && y.Op == LessThanOp { 211 return ctx.NewErrf("incompatible integer bounds %v and %v", x, y) 212 } 213 } 214 215 case diff == 2: 216 if k&FloatKind == 0 && x.Op == GreaterThanOp && y.Op == LessThanOp { 217 _, _ = internal.BaseContext.Add(&d, d.SetInt64(1), &lo) 218 return ctx.newNum(&d, k&NumberKind, x, y) 219 } 220 221 case diff == 0 && err == nil: 222 if x.Op == GreaterEqualOp && y.Op == LessEqualOp { 223 return ctx.newNum(&lo, k&NumberKind, x, y) 224 } 225 return errIncompatibleBounds(ctx, k, x, y) 226 } 227 228 case x.Op == NotEqualOp: 229 if !test(ctx, y.Op, xv, yv) { 230 return y 231 } 232 233 case y.Op == NotEqualOp: 234 if !test(ctx, x.Op, yv, xv) { 235 return x 236 } 237 } 238 return nil 239 } 240 241 func errIncompatibleBounds(ctx *OpContext, k Kind, x, y *BoundValue) *Bottom { 242 if k == IntKind { 243 return ctx.NewErrf("incompatible integer bounds %v and %v", y, x) 244 } else { 245 return ctx.NewErrf("incompatible number bounds %v and %v", y, x) 246 } 247 } 248 249 func opInfo(op Op) (cmp Op, norm int) { 250 switch op { 251 case GreaterThanOp: 252 return GreaterEqualOp, 1 253 case GreaterEqualOp: 254 return GreaterThanOp, 1 255 case LessThanOp: 256 return LessEqualOp, -1 257 case LessEqualOp: 258 return LessThanOp, -1 259 case NotEqualOp: 260 return NotEqualOp, 0 261 case MatchOp: 262 return MatchOp, 2 263 case NotMatchOp: 264 return NotMatchOp, 3 265 } 266 panic("cue: unreachable") 267 } 268 269 func test(ctx *OpContext, op Op, a, b Value) bool { 270 if b, ok := BinOp(ctx, op, a, b).(*Bool); ok { 271 return b.B 272 } 273 return false 274 } 275 276 // SimplifyValidator simplifies non-bound validators. 277 // 278 // Currently this only checks for pure equality. In the future this can be used 279 // to simplify certain builtin validators analogously to how we simplify bounds 280 // now. 281 func SimplifyValidator(ctx *OpContext, v, w Conjunct) (c Conjunct, ok bool) { 282 switch x := v.x.(type) { 283 case *BuiltinValidator: 284 switch y := w.x.(type) { 285 case *BuiltinValidator: 286 if x == y { 287 return v, true 288 } 289 if x.Builtin != y.Builtin || len(x.Args) != len(y.Args) { 290 return c, false 291 } 292 for i, a := range x.Args { 293 b := y.Args[i] 294 if v, ok := a.(*Vertex); ok { 295 v.Finalize(ctx) 296 } 297 if v, ok := b.(*Vertex); ok { 298 v.Finalize(ctx) 299 } 300 if !Equal(ctx, a, b, CheckStructural) { 301 return c, false 302 } 303 } 304 return v, true 305 } 306 } 307 return c, false 308 }