了解了下go生态内的表达式计算库,govaluate评价比较高,目前github有2300多个star

用这个库,比较容易解决某些场景下复杂规则计算的需求

相关使用参考Go 每日一库之 govaluate,以下是对该篇内容的实践与记录


demo1–基础款


使用govaluate计算表达式只需要两步:

调用NewEvaluableExpression()将表达式转为一个表达式对象(一个EvaluableExpression类型的结构体);

调用表达式对象的Evaluate方法,传入参数,返回表达式的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

func main() {

expr, err := govaluate.NewEvaluableExpression("10 >= 0")
if err != nil {
fmt.Println("syntax error:", err)
}

result, err := expr.Evaluate(nil)
if err != nil {
fmt.Println("evaluate error:", err)
}

fmt.Println(result)
}

输出:

1
true

使用govaluate计算10 >= 0的值,该表达式不需要参数,所以传给Evaluate()方法nil值。

当然这个例子并不实用。但有时并不知道需要计算的表达式的所有信息,甚至都不知道表达式的结构,这时govaluate的作用就体现出来了。


如果govaluate.NewEvaluableExpression()的表达式是不合法的,则err将不为nil,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

func main() {

// 如果NewEvaluableExpression中的参数不是合法的逻辑表达式,则err会报 Invalid token: '!>='
expr, err := govaluate.NewEvaluableExpression("10 !>= 0")
if err != nil {
fmt.Println("syntax error:", err)
}

// 同时expr为nil
fmt.Println("expr is:", expr)

// 此时需做return之类的判断。继续往下执行,调用为nil的expr的Evaluate,自然会panic: runtime error: invalid memory address or nil pointer dereference

result, err := expr.Evaluate(nil)
if err != nil {
fmt.Println("evaluate error:", err)
}

fmt.Println(result)
}

输出:

1
2
3
4
5
6
7
8
9
syntax error: Invalid token: '!>='
expr is: <nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104d463f0]

goroutine 1 [running]:
main.main()
/Users/fliter/go/src/shuang/govaluatee/a.go:17 +0x160
exit status 2


demo2–带参数


支持在表达式中使用参数,调用表达式对象的Evaluate()方法时通过map[string]interface{}类型将参数传入运算。

其中map的键为参数名,值为参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

// 省略了err的判断,正常情况需要有,否则可能panic
func main() {

expr, _ := govaluate.NewEvaluableExpression("foo > 0")
parameters := make(map[string]interface{})
parameters["foo"] = -1
result, _ := expr.Evaluate(parameters) // 即 -1 > 0
fmt.Println(result) // false

expr, _ = govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90")
parameters = make(map[string]interface{})
parameters["requests_made"] = 100
parameters["requests_succeeded"] = 80
result, _ = expr.Evaluate(parameters) // 即 80 >= 90
fmt.Println(result) // false

expr, _ = govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100")
parameters = make(map[string]interface{})
parameters["total_mem"] = 1024
parameters["mem_used"] = 512
result, _ = expr.Evaluate(parameters) // 即 512/1024 * 100
fmt.Println(result) // 50
}

输出:

1
2
3
false
false
50




demo3–特殊标识符的处理


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

// demo省略了err的判断,正常情况需要有,否则可能panic
func main() {

expr, err1 := govaluate.NewEvaluableExpression("response-time < 100")
if err1 != nil {
fmt.Println("err1 is:", err1)
}
parameters := make(map[string]interface{})
parameters["response-time"] = 80

result, err2 := expr.Evaluate(parameters)
if err2 != nil {
fmt.Println("err2 is:", err2)
}

fmt.Println(result)

}

输出:

1
2
err2 is: No parameter 'response' found.
<nil>

如果NewEvaluableExpression()中的表达式是如同a+b>100,或x-y<50等情况,又想将a+b作为一个整体参数,通过parameters一次性传入,因为Go 代码中标识符中不能出现-、+、$等符号,故而需要转义处理。

主要方式有两种:

  • 将名称用[和]包裹起来,例如[response-time];
  • 使用\将紧接着下一个的字符转义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

// demo省略了err的判断,正常情况需要有,否则可能panic
func main() {

expr, _ := govaluate.NewEvaluableExpression("[response-time] < 100")
parameters := make(map[string]interface{})
parameters["response-time"] = 80
result, _ := expr.Evaluate(parameters) // 80 < 100
fmt.Println(result) //true


//因为在字符串中\本身就需要转义,所以表达式中要使用\\。或可使用`response\-time` < 100

expr, _ = govaluate.NewEvaluableExpression("response\\-time < 100")
parameters = make(map[string]interface{})
parameters["response-time"] = 80
result, _ = expr.Evaluate(parameters)
fmt.Println(result)
}

输出:

1
2
true
true


而如果将response和time作为两个参数,则无需做处理,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

// demo省略了err的判断,正常情况需要有,否则可能panic
func main() {

expr, err1 := govaluate.NewEvaluableExpression("response-time < 100")
if err1 != nil {
fmt.Println("err1 is:", err1)
}
parameters := make(map[string]interface{})
//parameters["response-time"] = 80
parameters["response"] = 80
parameters["time"] = 10

result, err2 := expr.Evaluate(parameters)
if err2 != nil {
fmt.Println("err2 is:", err2)
}

fmt.Println(result)

}

输出:

1
true



demo4–一次“编译”,多次运行


使用带参数的表达式,可实现一个表达式的一次“编译”,多次运行。只需要调用编译返回的表达式对象的Evaluate()方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

func main() {
expr, _ := govaluate.NewEvaluableExpression("a + b")
parameters := make(map[string]interface{})
parameters["a"] = 1
parameters["b"] = 2
result, _ := expr.Evaluate(parameters)
fmt.Println(result)

parameters = make(map[string]interface{})
parameters["a"] = 10
parameters["b"] = 20
result, _ = expr.Evaluate(parameters)
fmt.Println(result)
}

输出:

1
2
3
30



demo5–使用 自定义函数


如果仅仅只能进行常规的算数和逻辑运算,govaluate的功能则会大打折扣。govaluate还提供了强大的自定义函数功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

func main() {

// 所有自定义函数需要预先定义好,存入一个map[string]govaluate.ExpressionFunction变量中,
functions := map[string]govaluate.ExpressionFunction{
"strlen": func(args ...interface{}) (interface{}, error) {
length := len(args[0].(string))
return length, nil
},
}

exprString := "strlen('shuang_length')"

// 然后调用govaluate.NewEvaluableExpressionWithFunctions()生成表达式,在表达式中就可以使用该函数了
expr, _ := govaluate.NewEvaluableExpressionWithFunctions(exprString, functions)
result, _ := expr.Evaluate(nil)
fmt.Println(result)
}

输出:

1
13


函数可以接受任意数量的参数,且可处理嵌套函数调用的问题。所以能写出类似下面这种复杂的表达式:

1
2
3
sqrt(x1 ** y1, x2 ** y2)

max(someValue, abs(anotherValue), 10 * lastValue)




demo6–访问器


处理结构体时,一般用不到




demo7–支持的操作和类型


govaluate支持的操作和类型与 Go 语言有些不同:一方面govaluate中的类型和操作不如 Go 丰富,另一方面govaluate也对一些操作进行了扩展。

算数、比较和逻辑运算:

  • + - * / & | ^ ** % >> << :加减乘除,按位与,按位或,异或,乘方,取模,左移和右移;

  • > >= < < == =! ==~ !~,其中=~为正则匹配,!~为正则不匹配;

  • || &&:逻辑或和逻辑与。

常量:

  • 数字常量,govaluate中将数字都作为 64 位浮点数处理;
  • 字符串常量,注意在govaluate中,字符串用单引号’;
  • 日期时间常量,格式与字符串相同,govaluate会尝试自动解析字符串是否是日期,只支持 RFC3339、ISO8601等有限的格式;
  • 布尔常量:true、false。

其他:

  • 圆括号可以改变计算优先级;
  • 数组定义在()中,每个元素之间用,分隔,可以支持任意的元素类型,如(1, 2, ‘foo’)。实际上在govaluate中数组是用[]interface{}来表示的;
  • 三目运算符:? :


在下面代码中,govaluate会先将2014-01-02和2014-01-01 23:59:59转为time.Time类型,然后再比较大小:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"github.com/Knetic/govaluate"
)

func main() {
expr, _ := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'")
result, _ := expr.Evaluate(nil)
fmt.Println(result)
}

输出:

1
true




demo8–错误处理


在创建表达式对象和表达式求值这两个操作中都可能产生错误:在生成表达式对象时,如果表达式有语法错误,则返回错误;表达式求值时,如果传入的参数不合法,或者某些参数缺失,或者访问结构体中不存在的字段都会报错。


demo部分代码