A simple implementation of boolean expression evaluation. Used to check whether a struct or map variable satisfies the conditions of a boolean expression.
Fields are represented using dot notation, supporting unlimited nesting, e.g. a.b, where a represents the a field of the current evaluation object, whose type could be a struct or map, and a.b represents the b field of the struct a or the value corresponding to the key b of the map a. Each segment of the dot-separated field name can use either the field name from the type definition or the corresponding bexpr tag field name. For example, for the following structure:
type Foo struct {
a string `bexpr:"aa"`
b int `bexpr:"bb"`
c Bar `bexpr:"cc"`
}
type Bar struct {
d int `bexpr:dd`
}
"a == "hello" and c.d > 10" is equivalent to "aa=="hello" and cc.dd>10".
- Logical operators AND, OR, NOT
- Comparison operators <, <=, >, >=, ==, !=
- Match operator
":"represents a general match. Depending on the expression and data type differences, it can represent substring, wildcard, regular expression, etc. general matching modes or string type set matching.
(1) General match pattern
For example,string_field:xxx means string value matching,other data type fields such as int_field: xxx means equality comparison.
Regular expressions also supported, with match string separated by '/', e.g. foo:/^abc.*[a-z]+/ or wildcard matching, foo:"a*b?"
(2) string set match patterng "a b c": items inside "" are separated by spaces, meaning all must be matched.
[a,b,c]: items inside [] are separated by commas, meaning matching any one is sufficient. - _EXISTS_操作
_EXISTS_:field means whether field exists. Applicable to determining whether a map key exists, a struct field exists, supports nesting.
-
EvaluateSimpleExpr
// Used to evaluate expressions where all operands are fixed true/false values. // e.g. "true OR false AND (true AND false)" func EvaluateSimpleExpr(expr string) (bool, error)
-
EvalFieldValueCondition
// EvaluateFieldValueExpr applies the expression expr on a struct/map or struct/map pointer v and evaluates // whether v satisfies the condition represented by expr. // Supported expressions are similar to: field:value AND NOT field>1 OR (_EXISTS_:field AND field:"a b c") // StringMatchType: Specifies the match strategy for string fields, can be word segmentation, substring match, case-insensitive word segmentation, case-insensitive substring match. func EvaluateFieldValueExpr(v interface{}, mt StringMatchType, expr string) (bool, error)
-
Extension mode
// ParseExpr parse boolean expression and return a parser tree func ParseExpr(expr string) (*TreeNode, error) type EvalFunc func(n *TreeNode) (bool, error) func Evaluate(expr string, f EvalFunc) (bool, error)
You can implement a custom EvalFunc to perform personalized evaluation on the parser tree after expr is parsed.
f := foo{a: "This is just a test.", b: true, c: 10, d: 1, e: 2, f: -3,
g: 4, h: 5, i: 6, j: 7, k: 8, l: 9, m: 0, n: 3.2, o: 4.5}
f.q = make(map[string]bar)
f.q["a"] = bar{}
parser.EvaluateFieldValueExpr(&f, StrCaseMatchTerm, "_EXISTS_:q.a AND d > 0 OR (e < -1 OR o < 3.2)")
parser.EvaluateFieldValueExpr(&f, StrCaseMatchTerm, "_EXISTS_:q.a AND d > 0 AND NOT (e < -1 OR o < 3.2)")
parser.EvaluateFieldValueExpr(f, StrCaseMatchTerm, "_EXISTS_:q.a AND (a:\"is a\")")
// match any token a or b, but expr error, missing )
parser.EvaluateFieldValueExpr(f, StrCaseMatchTerm, "_EXISTS_:q.a AND (a:[is ha]")
Split into two steps, first parse the parser tree, then apply a custom evaluation function to the tree.
// build tree
parserNode, err := parser.ParseExpr(f.cfg.Expr)
if err != nil {
log.Errorf("parse expr failed: %+v", err)
return err
}
Pass in a custom evaluation function, here we still directly use the evaluation function from parser, but replace the function for obtaining fields. The default function for obtaining fields uses reflection.
// GetRecordFieldValue return reflect value of LogRecord field
func GetRecordFieldValue(v interface{}, field string) reflect.Value {
rec, _ := v.(*unilog.LogRecord)
// Based on the specific type of rec, return the reflect value corresponding to the given field name, omitted here...
// If the corresponding field is not found
return reflect.Value{}
}
b, e := parser.EvalNode(parserNode, func(n *parser.TreeNode) (bool, error) {
return parser.EvalFieldValueCondition(rec, parser.GetFieldValueFunc(GetRecordFieldValue), f.matchType, n)
})