golang基于s-expression动态表达式求值插件库evaluator的使用
Golang基于S-expression动态表达式求值插件库evaluator的使用
动态求值表达式是一个常见需求,这就是我们创建这个库的原因。
S-expression
我们使用s-expression语法来解析和求值表达式。
在计算中,s-expressions(符号表达式)是一种用于嵌套列表(树结构)数据的表示法,最初由Lisp编程语言发明并推广,Lisp将其用于源代码和数据。
例如,普通表达式:
(gender = "female")
and
((age % 2) != 0)
对应的s-expression格式为:
(and
(= gender "female")
(!=
(% age 2)
0
)
)
表达式中的元素类型
-
number
为了方便,我们将float64、int64等视为number类型。例如,float100.0
等于int100
,但不等于string"100"
-
string
用`
、'
或"
引起来的字符串被视为string
类型。您可以通过后面提到的类型转换函数将string
类型转换为任何其他定义的类型 -
function或variable
没有引号的字符串被视为function
或variable
类型,这取决于该函数是否存在。例如在表达式(age birthdate)
中,age
和birthdate
都没有引号。age
是函数类型,因为我们注册了一个名为age
的函数,而birthdate
是变量类型。如果在求值时既没有名为birthdate
的参数也没有函数,程序将会报错
使用方法
您可以直接求值:
params := evaluator.MapParams{
"gender": "female",
}
res, err := evaluator.EvalBool(`(in gender ("female" "male"))`, params)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
// 输出: true
或者您可以重用Expression
来多次求值:
params := evaluator.MapParams{
"gender": "female",
}
exp, err := evaluator.New(`(in gender ("female" "male"))`)
if err != nil {
log.Fatal(err)
}
res, err := exp.EvalBool(params)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
// 输出: true
您可以这样写表达式
(in gender ("male", "female"))
(between now (td_time "2017-01-02 12:00:00") (td_time "2017-12-02 12:00:00"))
(ne (mod (age birthdate) 7) 5)
- 或者为了清晰使用多行:
(and
(ne os "ios")
(eq gender "male")
(beteen version (t_version "2.7.1") (t_version "2.9.1"))
)
函数
已实现的函数
操作符 | 函数 | 示例 | 描述 |
---|---|---|---|
- | in |
(in 1 (1 2)) |
也支持数组如 (in (1) ((1))) |
- | between |
(between age 18 20) |
|
- | overlap |
(overlap region (3142 1860)) |
|
& |
and |
(and (eq gender "femal") (between age 18 20)) |
|
| |
or |
||
! |
not |
||
= |
eq |
等于 | |
!= |
ne |
不等于 | |
> |
gt |
大于 | |
< |
lt |
小于 | |
>= |
ge |
大于等于 | |
<= |
le |
小于等于 | |
% |
mod |
取模 | |
+ |
- | 加 | |
- |
- | 减 | |
* |
- | 乘 | |
/ |
- | 除 | |
- | t_version |
转换为版本类型 | |
- | t_time |
(t_time "2006-01-02 15:04" "2017-09-09 12:00") |
转换为时间类型,第一个参数必须是时间格式 |
- | td_time |
(td_time "2017:09:09 12:00:00) |
转换为默认格式2006-01-02 15:04:05 的时间类型 |
- | td_date |
(in (td_date now) (td_date ("2017-01-02" "2017-02-01")) ) |
转换为默认格式2006-01-02 的时间类型 |
注意:表达式中可以使用操作符或函数
如何使用自定义函数
您可以按照以下步骤编写自己的函数:
- 实现您的函数
- 注册到函数中
- 使用它
下面是一个示例:
package main
import (
"errors"
"log"
"time"
"github.com/nullne/evaluator"
"github.com/nullne/evaluator/function"
)
// 定义您自己的函数并不要忘记注册
func age(params ...interface{}) (interface{}, error) {
if len(params) != 1 {
return nil, errors.New("only one params accepted")
}
birth, ok := params[0].(string)
if !ok {
return nil, errors.New("birth format need to be string")
}
r, err := time.Parse("2006-01-02", birth)
if err != nil {
return nil, err
}
now := time.Now()
a := r.Year() - now.Year()
if r.Month() < now.Month() {
a--
} else if r.Month() == now.Month() {
if r.Day() < now.Day() {
a--
}
}
return a, nil
}
func main() {
if err := function.Regist("age", age); err != nil {
log.Print(err)
}
exp := `(not (between (age birthdate) 18 20))`
vvf := evaluator.MapParams{
"birthdate": "1980-02-01",
}
e, err := evaluator.New(exp)
if err != nil {
log.Print(err)
}
r, err := e.Eval(vvf)
if err != nil {
log.Print(err)
}
log.Printf("expression: `%s`, wanna: %+v, got: %+v\r", exp, true, r)
}
参数
Params
接口,有一个名为Get
的方法来获取所有需要的参数MapParams
一个简单的map
实现的Params
性能测试
BenchmarkEqualString-8 3000000 473 ns/op
BenchmarkInString-8 2000000 916 ns/op
BenchmarkBetweenInt-8 3000000 467 ns/op
BenchmarkBetweenTime-8 1000000 2089 ns/op
BenchmarkOverlapInt-8 500000 2966 ns/op
BenchmarkTypeTime-8 2000000 638 ns/op
BenchmarkTypeVersion-8 3000000 539 ns/op
测试环境:MacBook Pro (Retina, 15-inch, Mid 2015), 内存: 16 GB 1600 MHz DDR3, 处理器: 2.2 GHz Intel Core i7
更多关于golang基于s-expression动态表达式求值插件库evaluator的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang基于s-expression动态表达式求值插件库evaluator的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang基于S-Expression的动态表达式求值插件库evaluator使用指南
S-Expression(S表达式)是一种简洁的数据表示方式,常用于Lisp语言中。在Golang中,evaluator库提供了基于S-Expression的动态表达式求值功能,非常适合需要动态执行表达式的场景。
安装evaluator库
首先安装evaluator库:
go get github.com/maxymania/go-expression/evaluator
基本用法
1. 简单表达式求值
package main
import (
"fmt"
"github.com/maxymania/go-expression/evaluator"
)
func main() {
// 创建新的求值环境
env := evaluator.NewEvaluator()
// 定义变量
env.Vars["x"] = 10
env.Vars["y"] = 20
// 简单表达式求值
result, err := env.Eval(`(+ x y)`)
if err != nil {
fmt.Println("求值错误:", err)
return
}
fmt.Println("结果:", result) // 输出: 结果: 30
}
2. 支持的基本操作
evaluator支持多种基本操作:
// 算术运算
result, _ := env.Eval(`(+ 1 2 3 4)`) // 10
result, _ = env.Eval(`(* 2 3 4)`) // 24
result, _ = env.Eval(`(- 10 5)`) // 5
result, _ = env.Eval(`(/ 20 4)`) // 5
// 比较运算
result, _ = env.Eval(`(> 5 3)`) // true
result, _ = env.Eval(`(<= 10 10)`) // true
result, _ = env.Eval(`(== "hello" "hello")`) // true
// 逻辑运算
result, _ = env.Eval(`(and true false)`) // false
result, _ = env.Eval(`(or true false)`) // true
result, _ = env.Eval(`(not false)`) // true
3. 自定义函数
可以注册自定义函数到求值环境中:
package main
import (
"fmt"
"github.com/maxymania/go-expression/evaluator"
)
func main() {
env := evaluator.NewEvaluator()
// 注册自定义函数
env.Funcs["square"] = func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("square函数需要一个参数")
}
num, ok := args[0].(float64)
if !ok {
return nil, fmt.Errorf("square函数参数必须是数字")
}
return num * num, nil
}
// 使用自定义函数
result, err := env.Eval(`(square 5)`)
if err != nil {
fmt.Println("求值错误:", err)
return
}
fmt.Println("结果:", result) // 输出: 结果: 25
}
4. 复杂表达式示例
package main
import (
"fmt"
"github.com/maxymania/go-expression/evaluator"
)
func main() {
env := evaluator.NewEvaluator()
// 设置变量
env.Vars["a"] = 10
env.Vars["b"] = 20
env.Vars["c"] = true
// 复杂表达式
expr := `
(if (and (> b a) c)
(+ (* a 2) (/ b 2))
(- b a))
`
result, err := env.Eval(expr)
if err != nil {
fmt.Println("求值错误:", err)
return
}
fmt.Println("结果:", result) // 输出: 结果: 30
}
高级用法
1. 错误处理
result, err := env.Eval(`(/ 1 0)`)
if err != nil {
if evalErr, ok := err.(*evaluator.EvalError); ok {
fmt.Printf("求值错误: 位置 %d:%d, 原因: %v\n",
evalErr.Pos.Line, evalErr.Pos.Col, evalErr.Err)
} else {
fmt.Println("其他错误:", err)
}
return
}
2. 预编译表达式
对于需要多次求值的表达式,可以先编译后重复使用:
// 预编译表达式
compiled, err := env.Compile(`(+ x y z)`)
if err != nil {
fmt.Println("编译错误:", err)
return
}
// 设置不同变量值多次求值
env.Vars = map[string]interface{}{
"x": 1, "y": 2, "z": 3,
}
result1, _ := compiled.Eval(env) // 6
env.Vars = map[string]interface{}{
"x": 10, "y": 20, "z": 30,
}
result2, _ := compiled.Eval(env) // 60
注意事项
- evaluator库使用S-Expression语法,所有表达式必须用括号包围
- 变量和函数名区分大小写
- 默认情况下,数字会被解析为float64类型
- 对于复杂业务逻辑,建议使用自定义函数而非复杂表达式
evaluator库提供了一种灵活的方式来动态求值表达式,特别适合规则引擎、条件过滤等需要动态执行表达式的场景。通过合理使用自定义函数和变量,可以实现强大的动态逻辑处理能力。