golang基于s-expression动态表达式求值插件库evaluator的使用

Golang基于S-expression动态表达式求值插件库evaluator的使用

Build Status Coverage Status GoDoc Go Report Card

动态求值表达式是一个常见需求,这就是我们创建这个库的原因。

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类型。例如,float 100.0等于int 100,但不等于string "100"

  • string
    `'"引起来的字符串被视为string类型。您可以通过后面提到的类型转换函数将string类型转换为任何其他定义的类型

  • function或variable
    没有引号的字符串被视为functionvariable类型,这取决于该函数是否存在。例如在表达式(age birthdate)中,agebirthdate都没有引号。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的时间类型

注意:表达式中可以使用操作符或函数

如何使用自定义函数

您可以按照以下步骤编写自己的函数:

  1. 实现您的函数
  2. 注册到函数中
  3. 使用它

下面是一个示例:

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

1 回复

更多关于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

注意事项

  1. evaluator库使用S-Expression语法,所有表达式必须用括号包围
  2. 变量和函数名区分大小写
  3. 默认情况下,数字会被解析为float64类型
  4. 对于复杂业务逻辑,建议使用自定义函数而非复杂表达式

evaluator库提供了一种灵活的方式来动态求值表达式,特别适合规则引擎、条件过滤等需要动态执行表达式的场景。通过合理使用自定义函数和变量,可以实现强大的动态逻辑处理能力。

回到顶部