Skip to content

Commit 16e90a9

Browse files
authored
Merge pull request #17 from skyhackvip/feature/7
Feature/7
2 parents ec1dc78 + be5f750 commit 16e90a9

File tree

4 files changed

+225
-4
lines changed

4 files changed

+225
-4
lines changed

core/common.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (rule *Rule) Parse(ctx *PipelineContext, depends map[string]IFeature) (outp
4545
return
4646
}
4747

48-
var conditionRet = make(map[string]interface{}, 0)
48+
var conditionRet = make(map[string]bool, 0)
4949
for _, condition := range rule.Conditions {
5050
if feature, ok := depends[condition.Feature]; ok {
5151
rs, err := feature.Compare(condition.Operator, condition.Value)
@@ -67,7 +67,7 @@ func (rule *Rule) Parse(ctx *PipelineContext, depends map[string]IFeature) (outp
6767

6868
//rule.Decision
6969
expr := rule.Decision.Logic
70-
logicRet, err := operator.Evaluate(expr, conditionRet)
70+
logicRet, err := operator.EvaluateBoolExpr(expr, conditionRet)
7171
//某个表达式执行失败会导致最终逻辑执行失败
7272
if err != nil {
7373
return

core/conditional.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (conditional ConditionalNode) Parse(ctx *PipelineContext) (*NodeResult, err
3939
depends := ctx.GetFeatures(info.Depends)
4040
var matchBranch bool
4141
for _, branch := range conditional.Branchs { //loop all the branch
42-
var conditionRet = make(map[string]interface{}, 0)
42+
var conditionRet = make(map[string]bool, 0)
4343
for _, condition := range branch.Conditions {
4444
if feature, ok := depends[condition.Feature]; ok {
4545
rs, err := feature.Compare(condition.Operator, condition.Value)
@@ -55,7 +55,7 @@ func (conditional ConditionalNode) Parse(ctx *PipelineContext) (*NodeResult, err
5555
if len(conditionRet) == 0 { //current branch not match
5656
continue
5757
}
58-
logicRs, err := operator.Evaluate(branch.Decision.Logic, conditionRet)
58+
logicRs, err := operator.EvaluateBoolExpr(branch.Decision.Logic, conditionRet)
5959
if err != nil {
6060
continue
6161
}

internal/operator/logic.go

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package operator
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// evaluate 计算逻辑表达式的值
9+
func EvaluateBoolExpr(expr string, variables map[string]bool) (bool, error) {
10+
11+
// 将表达式拆分成一个个token
12+
tokens, err := splitExpression(expr)
13+
if err != nil {
14+
return false, err
15+
}
16+
17+
// 开始执行逻辑运算
18+
stack := make([]bool, 0)
19+
opStack := make([]string, 0)
20+
for _, token := range tokens {
21+
switch token {
22+
case "&&":
23+
for len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
24+
if len(stack) < 1 {
25+
return false, fmt.Errorf("invalid expression")
26+
}
27+
b := stack[len(stack)-1]
28+
stack = stack[:len(stack)-1]
29+
stack = append(stack, !b)
30+
opStack = opStack[:len(opStack)-1]
31+
}
32+
opStack = append(opStack, "&&")
33+
case "||":
34+
for len(opStack) > 0 && (opStack[len(opStack)-1] == "!" || opStack[len(opStack)-1] == "&&") {
35+
if len(stack) < 2 {
36+
return false, fmt.Errorf("invalid expression")
37+
}
38+
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
39+
stack = stack[:len(stack)-2]
40+
op := opStack[len(opStack)-1]
41+
opStack = opStack[:len(opStack)-1]
42+
stack = append(stack, evaluateOp(b1, b2, op))
43+
}
44+
opStack = append(opStack, "||")
45+
case "!":
46+
opStack = append(opStack, "!")
47+
case "(":
48+
opStack = append(opStack, "(")
49+
case ")":
50+
if len(opStack) < 1 {
51+
return false, fmt.Errorf("invalid expression")
52+
}
53+
for opStack[len(opStack)-1] != "(" {
54+
if len(stack) < 2 {
55+
return false, fmt.Errorf("invalid expression")
56+
}
57+
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
58+
stack = stack[:len(stack)-2]
59+
op := opStack[len(opStack)-1]
60+
opStack = opStack[:len(opStack)-1]
61+
stack = append(stack, evaluateOp(b1, b2, op))
62+
if len(opStack) == 0 {
63+
return false, fmt.Errorf("unmatched parentheses")
64+
}
65+
}
66+
opStack = opStack[:len(opStack)-1]
67+
if len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
68+
if len(stack) < 1 {
69+
return false, fmt.Errorf("invalid expression")
70+
}
71+
b := stack[len(stack)-1]
72+
stack = stack[:len(stack)-1]
73+
stack = append(stack, !b)
74+
opStack = opStack[:len(opStack)-1]
75+
}
76+
default:
77+
if v, ok := variables[token]; ok {
78+
stack = append(stack, v)
79+
if len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
80+
if len(stack) < 1 {
81+
return false, fmt.Errorf("invalid expression")
82+
}
83+
b := stack[len(stack)-1]
84+
stack = stack[:len(stack)-1]
85+
stack = append(stack, !b)
86+
opStack = opStack[:len(opStack)-1]
87+
}
88+
} else {
89+
return false, fmt.Errorf("unknown variable %s", token)
90+
}
91+
}
92+
}
93+
94+
for len(opStack) > 0 {
95+
if len(stack) < 2 {
96+
return false, fmt.Errorf("invalid expression")
97+
}
98+
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
99+
stack = stack[:len(stack)-2]
100+
op := opStack[len(opStack)-1]
101+
opStack = opStack[:len(opStack)-1]
102+
stack = append(stack, evaluateOp(b1, b2, op))
103+
}
104+
105+
if len(stack) != 1 {
106+
return false, fmt.Errorf("invalid expression")
107+
}
108+
return stack[0], nil
109+
}
110+
111+
// evaluateOp 对两个 bool 值进行逻辑运算
112+
func evaluateOp(b1, b2 bool, op string) bool {
113+
switch op {
114+
case "&&":
115+
return b1 && b2
116+
case "||":
117+
return b1 || b2
118+
default:
119+
panic("unsupported operator: " + op)
120+
}
121+
}
122+
123+
// isValid 检查表达式是否合法
124+
func isValid(expr string) bool {
125+
if len(expr) == 0 {
126+
return false
127+
}
128+
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!()+-_*%/|&,"
129+
stack := make([]rune, 0)
130+
for _, ch := range expr {
131+
if ch == '(' {
132+
stack = append(stack, ch)
133+
} else if ch == ')' {
134+
if len(stack) == 0 {
135+
return false
136+
}
137+
stack = stack[:len(stack)-1]
138+
} else if !strings.ContainsRune(allowed, ch) {
139+
return false
140+
}
141+
}
142+
return len(stack) == 0
143+
}
144+
145+
// splitExpression 将表达式拆分为token
146+
func splitExpression(expr string) ([]string, error) {
147+
expr = strings.ReplaceAll(expr, " ", "") // 去除空格
148+
if !isValid(expr) {
149+
return nil, fmt.Errorf("invalid expression")
150+
}
151+
tokens := make([]string, 0)
152+
buf := make([]rune, 0)
153+
154+
for i := 0; i < len(expr); i++ {
155+
ch := rune(expr[i])
156+
if ch == '&' && i < len(expr)-1 && rune(expr[i+1]) == '&' {
157+
if len(buf) > 0 {
158+
tokens = append(tokens, string(buf))
159+
buf = []rune{}
160+
}
161+
tokens = append(tokens, "&&")
162+
i++
163+
} else if ch == '|' && i < len(expr)-1 && rune(expr[i+1]) == '|' {
164+
if len(buf) > 0 {
165+
tokens = append(tokens, string(buf))
166+
buf = []rune{}
167+
}
168+
tokens = append(tokens, "||")
169+
i++
170+
} else if ch == '!' || ch == '(' || ch == ')' {
171+
if len(buf) > 0 {
172+
tokens = append(tokens, string(buf))
173+
buf = []rune{}
174+
}
175+
tokens = append(tokens, string(ch))
176+
} else if ch == ',' {
177+
if len(buf) > 0 {
178+
tokens = append(tokens, string(buf))
179+
buf = []rune{}
180+
}
181+
tokens = append(tokens, string(ch))
182+
} else {
183+
buf = append(buf, ch)
184+
}
185+
}
186+
if len(buf) > 0 {
187+
tokens = append(tokens, string(buf))
188+
}
189+
return tokens, nil
190+
}

internal/operator/operator_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,34 @@ func TestCompare(t *testing.T) {
4444
t.Log(Compare("EQ", []interface{}{3, 8, 7, 6, 9}, []interface{}{9, 6, 7, 3, 8}))
4545
t.Log(Compare("EQ", []interface{}{"a", "b", "d", "c", "e"}, []interface{}{"a", "b", "c", "d", "e"}))
4646
}
47+
48+
func TestBoolExpr(t *testing.T) {
49+
variables := map[string]bool{
50+
"foo": true,
51+
"bar": false,
52+
"a1": true,
53+
}
54+
expr := " foo && bar"
55+
result, err := EvaluateBoolExpr(expr, variables)
56+
t.Log(expr, result, err)
57+
t.Log(EvaluateBoolExpr("!(foo&&bar)||!a1", variables))
58+
}
59+
60+
func TestSplit(t *testing.T) {
61+
//t.Log(splitExpression("max(foo,bar)"))
62+
// t.Log(splitExpression("tmax(foo,bar)"))
63+
// t.Log(splitExpression("!max(foo,bar)"))
64+
variables := map[string]bool{
65+
"foo": true,
66+
"bar": false,
67+
}
68+
t.Log(EvaluateExpr("max(foo,bar)", variables))
69+
}
70+
71+
func TestEval(t *testing.T) {
72+
variables := map[string]interface{}{
73+
"foo": 1,
74+
"bar": 2,
75+
}
76+
t.Log(Evaluate("max(foo, bar)", variables))
77+
}

0 commit comments

Comments
 (0)