diff --git a/core/common.go b/core/common.go index 50dfec0..35b0f73 100644 --- a/core/common.go +++ b/core/common.go @@ -45,7 +45,7 @@ func (rule *Rule) Parse(ctx *PipelineContext, depends map[string]IFeature) (outp return } - var conditionRet = make(map[string]interface{}, 0) + var conditionRet = make(map[string]bool, 0) for _, condition := range rule.Conditions { if feature, ok := depends[condition.Feature]; ok { rs, err := feature.Compare(condition.Operator, condition.Value) @@ -67,7 +67,7 @@ func (rule *Rule) Parse(ctx *PipelineContext, depends map[string]IFeature) (outp //rule.Decision expr := rule.Decision.Logic - logicRet, err := operator.Evaluate(expr, conditionRet) + logicRet, err := operator.EvaluateBoolExpr(expr, conditionRet) //某个表达式执行失败会导致最终逻辑执行失败 if err != nil { return diff --git a/core/conditional.go b/core/conditional.go index 3176853..55b530c 100644 --- a/core/conditional.go +++ b/core/conditional.go @@ -39,7 +39,7 @@ func (conditional ConditionalNode) Parse(ctx *PipelineContext) (*NodeResult, err depends := ctx.GetFeatures(info.Depends) var matchBranch bool for _, branch := range conditional.Branchs { //loop all the branch - var conditionRet = make(map[string]interface{}, 0) + var conditionRet = make(map[string]bool, 0) for _, condition := range branch.Conditions { if feature, ok := depends[condition.Feature]; ok { rs, err := feature.Compare(condition.Operator, condition.Value) @@ -55,7 +55,7 @@ func (conditional ConditionalNode) Parse(ctx *PipelineContext) (*NodeResult, err if len(conditionRet) == 0 { //current branch not match continue } - logicRs, err := operator.Evaluate(branch.Decision.Logic, conditionRet) + logicRs, err := operator.EvaluateBoolExpr(branch.Decision.Logic, conditionRet) if err != nil { continue } diff --git a/internal/operator/logic.go b/internal/operator/logic.go new file mode 100644 index 0000000..c00cc0e --- /dev/null +++ b/internal/operator/logic.go @@ -0,0 +1,190 @@ +package operator + +import ( + "fmt" + "strings" +) + +// evaluate 计算逻辑表达式的值 +func EvaluateBoolExpr(expr string, variables map[string]bool) (bool, error) { + + // 将表达式拆分成一个个token + tokens, err := splitExpression(expr) + if err != nil { + return false, err + } + + // 开始执行逻辑运算 + stack := make([]bool, 0) + opStack := make([]string, 0) + for _, token := range tokens { + switch token { + case "&&": + for len(opStack) > 0 && opStack[len(opStack)-1] == "!" { + if len(stack) < 1 { + return false, fmt.Errorf("invalid expression") + } + b := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stack = append(stack, !b) + opStack = opStack[:len(opStack)-1] + } + opStack = append(opStack, "&&") + case "||": + for len(opStack) > 0 && (opStack[len(opStack)-1] == "!" || opStack[len(opStack)-1] == "&&") { + if len(stack) < 2 { + return false, fmt.Errorf("invalid expression") + } + b1, b2 := stack[len(stack)-2], stack[len(stack)-1] + stack = stack[:len(stack)-2] + op := opStack[len(opStack)-1] + opStack = opStack[:len(opStack)-1] + stack = append(stack, evaluateOp(b1, b2, op)) + } + opStack = append(opStack, "||") + case "!": + opStack = append(opStack, "!") + case "(": + opStack = append(opStack, "(") + case ")": + if len(opStack) < 1 { + return false, fmt.Errorf("invalid expression") + } + for opStack[len(opStack)-1] != "(" { + if len(stack) < 2 { + return false, fmt.Errorf("invalid expression") + } + b1, b2 := stack[len(stack)-2], stack[len(stack)-1] + stack = stack[:len(stack)-2] + op := opStack[len(opStack)-1] + opStack = opStack[:len(opStack)-1] + stack = append(stack, evaluateOp(b1, b2, op)) + if len(opStack) == 0 { + return false, fmt.Errorf("unmatched parentheses") + } + } + opStack = opStack[:len(opStack)-1] + if len(opStack) > 0 && opStack[len(opStack)-1] == "!" { + if len(stack) < 1 { + return false, fmt.Errorf("invalid expression") + } + b := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stack = append(stack, !b) + opStack = opStack[:len(opStack)-1] + } + default: + if v, ok := variables[token]; ok { + stack = append(stack, v) + if len(opStack) > 0 && opStack[len(opStack)-1] == "!" { + if len(stack) < 1 { + return false, fmt.Errorf("invalid expression") + } + b := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stack = append(stack, !b) + opStack = opStack[:len(opStack)-1] + } + } else { + return false, fmt.Errorf("unknown variable %s", token) + } + } + } + + for len(opStack) > 0 { + if len(stack) < 2 { + return false, fmt.Errorf("invalid expression") + } + b1, b2 := stack[len(stack)-2], stack[len(stack)-1] + stack = stack[:len(stack)-2] + op := opStack[len(opStack)-1] + opStack = opStack[:len(opStack)-1] + stack = append(stack, evaluateOp(b1, b2, op)) + } + + if len(stack) != 1 { + return false, fmt.Errorf("invalid expression") + } + return stack[0], nil +} + +// evaluateOp 对两个 bool 值进行逻辑运算 +func evaluateOp(b1, b2 bool, op string) bool { + switch op { + case "&&": + return b1 && b2 + case "||": + return b1 || b2 + default: + panic("unsupported operator: " + op) + } +} + +// isValid 检查表达式是否合法 +func isValid(expr string) bool { + if len(expr) == 0 { + return false + } + allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!()+-_*%/|&," + stack := make([]rune, 0) + for _, ch := range expr { + if ch == '(' { + stack = append(stack, ch) + } else if ch == ')' { + if len(stack) == 0 { + return false + } + stack = stack[:len(stack)-1] + } else if !strings.ContainsRune(allowed, ch) { + return false + } + } + return len(stack) == 0 +} + +// splitExpression 将表达式拆分为token +func splitExpression(expr string) ([]string, error) { + expr = strings.ReplaceAll(expr, " ", "") // 去除空格 + if !isValid(expr) { + return nil, fmt.Errorf("invalid expression") + } + tokens := make([]string, 0) + buf := make([]rune, 0) + + for i := 0; i < len(expr); i++ { + ch := rune(expr[i]) + if ch == '&' && i < len(expr)-1 && rune(expr[i+1]) == '&' { + if len(buf) > 0 { + tokens = append(tokens, string(buf)) + buf = []rune{} + } + tokens = append(tokens, "&&") + i++ + } else if ch == '|' && i < len(expr)-1 && rune(expr[i+1]) == '|' { + if len(buf) > 0 { + tokens = append(tokens, string(buf)) + buf = []rune{} + } + tokens = append(tokens, "||") + i++ + } else if ch == '!' || ch == '(' || ch == ')' { + if len(buf) > 0 { + tokens = append(tokens, string(buf)) + buf = []rune{} + } + tokens = append(tokens, string(ch)) + } else if ch == ',' { + if len(buf) > 0 { + tokens = append(tokens, string(buf)) + buf = []rune{} + } + tokens = append(tokens, string(ch)) + } else { + buf = append(buf, ch) + } + } + if len(buf) > 0 { + tokens = append(tokens, string(buf)) + } + return tokens, nil +} diff --git a/internal/operator/operator_test.go b/internal/operator/operator_test.go index 5b778ec..df14848 100644 --- a/internal/operator/operator_test.go +++ b/internal/operator/operator_test.go @@ -44,3 +44,34 @@ func TestCompare(t *testing.T) { t.Log(Compare("EQ", []interface{}{3, 8, 7, 6, 9}, []interface{}{9, 6, 7, 3, 8})) t.Log(Compare("EQ", []interface{}{"a", "b", "d", "c", "e"}, []interface{}{"a", "b", "c", "d", "e"})) } + +func TestBoolExpr(t *testing.T) { + variables := map[string]bool{ + "foo": true, + "bar": false, + "a1": true, + } + expr := " foo && bar" + result, err := EvaluateBoolExpr(expr, variables) + t.Log(expr, result, err) + t.Log(EvaluateBoolExpr("!(foo&&bar)||!a1", variables)) +} + +func TestSplit(t *testing.T) { + //t.Log(splitExpression("max(foo,bar)")) + // t.Log(splitExpression("tmax(foo,bar)")) + // t.Log(splitExpression("!max(foo,bar)")) + variables := map[string]bool{ + "foo": true, + "bar": false, + } + t.Log(EvaluateExpr("max(foo,bar)", variables)) +} + +func TestEval(t *testing.T) { + variables := map[string]interface{}{ + "foo": 1, + "bar": 2, + } + t.Log(Evaluate("max(foo, bar)", variables)) +}