github.com/GuanceCloud/cliutils@v1.1.21/filter/eval.go (about) 1 // Unless explicitly stated otherwise all files in this repository are licensed 2 // under the MIT License. 3 // This product includes software developed at Guance Cloud (https://www.guance.com/). 4 // Copyright 2021-present Guance, Inc. 5 6 package filter 7 8 import ( 9 "math" 10 "reflect" 11 "regexp" 12 "strings" 13 ) 14 15 type KVs interface { 16 Get(k string) (v any, ok bool) 17 } 18 19 type tfData struct { 20 tags map[string]string 21 fields map[string]any 22 } 23 24 func (d *tfData) Get(name string) (any, bool) { 25 if v, ok := d.tags[name]; ok { 26 return v, true 27 } 28 29 if v, ok := d.fields[name]; ok { 30 return v, true 31 } 32 33 return nil, false 34 } 35 36 func newtf(tags map[string]string, fields map[string]any) *tfData { 37 return &tfData{ 38 tags: tags, 39 fields: fields, 40 } 41 } 42 43 func (p *ParenExpr) Eval(data KVs) bool { 44 if p.Param == nil { 45 return false 46 } 47 48 switch expr := p.Param.(type) { 49 case Evaluable: 50 return expr.Eval(data) 51 default: 52 log.Errorf("ParenExpr's Param should be Evaluable") 53 } 54 55 return false 56 } 57 58 func (e *BinaryExpr) Eval(data KVs) bool { 59 switch e.Op { 60 case AND: 61 for _, expr := range []Node{e.LHS, e.RHS} { 62 switch expr.(type) { 63 case Evaluable: 64 default: 65 log.Errorf("LHS and RHS should be BinaryExpr or ParenExpr") 66 return false 67 } 68 } 69 70 return e.LHS.(Evaluable).Eval(data) && e.RHS.(Evaluable).Eval(data) 71 72 case OR: // LHS/RHS should be BinaryExpr 73 74 for _, expr := range []Node{e.LHS, e.RHS} { 75 switch expr.(type) { 76 case Evaluable: 77 default: 78 log.Errorf("LHS and RHS should be BinaryExpr or ParenExpr") 79 return false 80 } 81 } 82 83 return e.LHS.(Evaluable).Eval(data) || e.RHS.(Evaluable).Eval(data) 84 85 default: 86 return e.doEval(data) 87 } 88 } 89 90 func (e *BinaryExpr) doEval(data KVs) bool { 91 switch e.Op { 92 case GTE, GT, LT, LTE, NEQ, EQ, IN, NOT_IN, MATCH, NOT_MATCH: 93 default: 94 log.Errorf("unsupported OP %s", e.Op.String()) 95 return false 96 } 97 98 return e.singleEval(data) 99 } 100 101 const float64EqualityThreshold = 1e-9 102 103 // see: https://stackoverflow.com/a/47969546/342348 104 func almostEqual(a, b float64) bool { 105 return math.Abs(a-b) <= float64EqualityThreshold 106 } 107 108 func toFloat64(f interface{}) float64 { 109 switch v := f.(type) { 110 case float32: 111 return float64(v) 112 case float64: 113 return v 114 default: 115 log.Error("should not been here") 116 return 0.0 117 } 118 } 119 120 func toInt64(i interface{}) int64 { 121 switch v := i.(type) { 122 case int: 123 return int64(v) 124 case int8: 125 return int64(v) 126 case int16: 127 return int64(v) 128 case int32: 129 return int64(v) 130 case int64: 131 return v 132 case uint: 133 return int64(v) 134 case uint8: 135 return int64(v) 136 case uint16: 137 return int64(v) 138 case uint32: 139 return int64(v) 140 default: 141 log.Error("should not been here") 142 return 0 143 } 144 } 145 146 func binEval(op ItemType, lhs, rhs interface{}) bool { 147 if _, ok := rhs.(*Regex); ok { 148 if _, isStr := lhs.(string); !isStr { 149 log.Warnf("non-string(type %s) can not match with regexp", reflect.TypeOf(lhs)) 150 return false 151 } 152 } else { // rhs are all literals 153 tl := reflect.TypeOf(lhs).String() 154 tr := reflect.TypeOf(rhs).String() 155 switch op { 156 case GTE, GT, LT, LTE, EQ, NEQ: // type conflict detecting on comparison expr 157 if _, ok := rhs.(*NilLiteral); !ok && // any type can compare to nil/null 158 tl != tr { 159 log.Warnf("type conflict %+#v(%s) <> %+#v(%s)", lhs, reflect.TypeOf(lhs), rhs, reflect.TypeOf(rhs)) 160 return false 161 } 162 163 default: 164 log.Warnf("op %s should not been here", op.String()) 165 return false 166 } 167 } 168 169 switch op { 170 case EQ: 171 switch lv := lhs.(type) { 172 case float64: 173 if f, ok := rhs.(float64); !ok { 174 return false 175 } else { 176 return almostEqual(lv, f) 177 } 178 179 case *NilLiteral: 180 if _, ok := rhs.(*NilLiteral); !ok { // nil compared to non-nil always false 181 log.Warnf("rhs %v not nil", rhs) 182 return false 183 } 184 185 return lv.String() == Nil 186 187 default: // NOTE: interface{} EQ/NEQ, see: https://stackoverflow.com/a/34246225/342348 188 switch rv := rhs.(type) { 189 case *Regex: 190 log.Debugf("lhs: %v, rhs: %v", lhs, rhs) 191 ok, err := regexp.MatchString(rv.Regex, lhs.(string)) 192 if err != nil { 193 log.Error(err) 194 } 195 196 return ok 197 198 default: 199 return lhs == rhs 200 } 201 } 202 203 case MATCH: 204 return rhs.(*Regex).Re.MatchString(lhs.(string)) 205 206 case NOT_MATCH: 207 return !rhs.(*Regex).Re.MatchString(lhs.(string)) 208 209 case NEQ: 210 _, lok := lhs.(*NilLiteral) 211 _, rok := rhs.(*NilLiteral) 212 if lok && rok { 213 return false 214 } 215 216 return lhs != rhs 217 218 case GTE, GT, LT, LTE: // rhs/lhs should be number or string 219 switch lv := lhs.(type) { 220 case int, int8, int16, int32, int64, 221 uint, uint8, uint16, uint32: 222 223 if i, ok := rhs.(int64); !ok { 224 log.Warnf("rhs not int64") 225 return false 226 } else { 227 return cmpint(op, toInt64(lv), i) 228 } 229 230 case bool: // bool not support >/>=/</<= 231 return false 232 case string: 233 if s, ok := rhs.(string); !ok { 234 return false 235 } else { 236 return cmpstr(op, lv, s) 237 } 238 case float32, float64: 239 if f, ok := rhs.(float64); !ok { 240 return false 241 } else { 242 return cmpfloat(op, toFloat64(lv), f) 243 } 244 } 245 } 246 247 return false 248 } 249 250 func cmpstr(op ItemType, l, r string) bool { 251 switch op { 252 case GTE: 253 return strings.Compare(l, r) >= 0 254 case LTE: 255 return strings.Compare(l, r) <= 0 256 case LT: 257 return strings.Compare(l, r) < 0 258 case GT: 259 return strings.Compare(l, r) > 0 260 default: 261 log.Warn("should not been here, %s %s %s", l, op.String(), r) 262 } 263 return false 264 } 265 266 func cmpint(op ItemType, l, r int64) bool { 267 switch op { 268 case GTE: 269 return l >= r 270 case LTE: 271 return l <= r 272 case LT: 273 return l < r 274 case GT: 275 return l > r 276 default: 277 log.Warn("should not been here, %d %s %d", l, op.String(), r) 278 } 279 return false 280 } 281 282 func cmpfloat(op ItemType, l, r float64) bool { 283 switch op { 284 case GTE: 285 return l >= r 286 case LTE: 287 return l <= r 288 case LT: 289 return l < r 290 case GT: 291 return l > r 292 default: 293 log.Warn("should not been here, %f %s %f", l, op.String(), r) 294 } 295 return false 296 } 297 298 func (e *BinaryExpr) singleEval(data KVs) bool { 299 if e.LHS == nil || e.RHS == nil { 300 log.Warn("LHS or RHS nil, should not been here") 301 return false 302 } 303 304 // first: fetch right-handle-symbol 305 var lit interface{} 306 var arr []interface{} 307 switch rhs := e.RHS.(type) { 308 case *StringLiteral: 309 lit = rhs.Val 310 311 case *NumberLiteral: 312 if rhs.IsInt { 313 lit = rhs.Int 314 } else { 315 lit = rhs.Float 316 } 317 318 case NodeList: 319 for _, elem := range rhs { 320 switch x := elem.(type) { 321 case *StringLiteral: 322 arr = append(arr, x.Val) 323 case *NumberLiteral: 324 if x.IsInt { 325 arr = append(arr, x.Int) 326 } else { 327 arr = append(arr, x.Float) 328 } 329 case *Regex: 330 arr = append(arr, x) 331 case *NilLiteral: 332 arr = append(arr, x) 333 case *BoolLiteral: 334 arr = append(arr, x.Val) 335 default: 336 log.Warnf("unsupported node list with type `%s'", reflect.TypeOf(elem).String()) 337 } 338 } 339 340 case *Regex: 341 lit = rhs 342 343 case *NilLiteral: 344 lit = rhs 345 346 case *BoolLiteral: 347 lit = rhs.Val 348 349 default: 350 351 log.Errorf("invalid RHS, got type `%s'", reflect.TypeOf(e.RHS).String()) 352 return false 353 } 354 355 var lhs *Identifier 356 switch left := e.LHS.(type) { // Left part can be string/bool/number/nil literal and identifier 357 case *NilLiteral: 358 return binEval(e.Op, nilVal, lit) 359 360 case *NumberLiteral: 361 if left.IsInt { 362 return binEval(e.Op, left.Int, lit) 363 } else { 364 return binEval(e.Op, left.Float, lit) 365 } 366 367 case *BoolLiteral: 368 return binEval(e.Op, left.Val, lit) 369 370 case *StringLiteral: 371 return binEval(e.Op, left.Val, lit) 372 373 case *Identifier: 374 lhs = left // we get detailed lhs value later... 375 376 default: 377 log.Errorf("unknown LHS type, expect Identifier, got `%s'", reflect.TypeOf(e.LHS).String()) 378 return false 379 } 380 381 name := lhs.Name 382 383 switch e.Op { 384 case MATCH, NOT_MATCH: 385 for _, item := range e.RHS.(NodeList) { 386 if v, ok := data.Get(name); ok { 387 switch x := v.(type) { 388 case string: 389 if binEval(e.Op, x, item) { 390 return true 391 } 392 default: 393 continue 394 } 395 } 396 } 397 return false 398 399 case IN: 400 for _, item := range arr { 401 if v, ok := data.Get(name); ok { 402 if binEval(EQ, v, item) { 403 return true 404 } 405 } else { 406 return binEval(EQ, item, nilVal) 407 } 408 } 409 return false 410 411 case NOT_IN: 412 for _, item := range arr { 413 if v, ok := data.Get(name); ok { 414 if binEval(EQ, v, item) { 415 return false 416 } 417 } else { 418 if binEval(EQ, item, nilVal) { 419 return false 420 } 421 } 422 } 423 424 return true 425 426 case GTE, GT, LT, LTE, NEQ, EQ: 427 if v, ok := data.Get(name); ok { 428 if binEval(e.Op, v, lit) { 429 return true 430 } 431 } else { // not exist in data 432 return binEval(e.Op, lit, nilVal) 433 } 434 default: 435 log.Warnf("unsupported operation %s on single-eval expr", e.Op) 436 } 437 438 return false 439 } 440 441 var ( 442 nilVal = &NilLiteral{} 443 )