Skip to content

Commit

Permalink
Merge pull request #17 from skyhackvip/feature/7
Browse files Browse the repository at this point in the history
Feature/7
  • Loading branch information
skyhackvip authored Nov 29, 2023
2 parents ec1dc78 + be5f750 commit 16e90a9
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 4 deletions.
4 changes: 2 additions & 2 deletions core/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions core/conditional.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
190 changes: 190 additions & 0 deletions internal/operator/logic.go
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 31 additions & 0 deletions internal/operator/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

0 comments on commit 16e90a9

Please sign in to comment.