使用YACC和Ragel构建的GO表达式计算引擎

726 阅读2分钟

代码已开源:github.com/StrangeYear…

goeval是一个使用YACC和Ragel,基于状态机模式构建的GO语言表达式计算引擎,可适用于一些动态条件判断场景。

对于相同表达式进行多次判断的场景,这个库可能不适合。因为每次解析都会进行判定,没有缓存表达式。对于动态表达式判断场景,性能优于其他开源的go表达式引擎库。

支持的运算

  • 修饰符: +、-、/、*、%
  • 比较器: >、>=、<、<=、==、!=、=~、!~、in
  • 逻辑运算: ||、&&
  • 数字常量: 12345.678、123(转为float64)
  • 字符串常量: "foo"、'bar'、`baz`(支持转义"、'、`)
  • 布尔常量: true、false
  • 括号控制求值顺序: ( )
  • 数组常量或嵌套变量: [1, 2, 'foo', bar](bar为变量)
  • 前缀: ! - ~
  • 三元表达式: 5 > 1 ? 4 : 3 (返回值4)
  • 空值默认值: foo ?? 'bar' (当foo变量为空时,会使用'bar'作为返回值)

使用方式

  • 基础表达式: 10 > 0
  • 参数表达式: foo > 0
  • 特殊参数表达式(包含空格及比较符号等): $.["foo bar"] > 0$.["response-time"] > 0
  • 嵌套参数表达式: foo.bar > 0foo.bar[0] > 0foo["bar"] > 0$.["foo bar"].$.["foo baz"] > 0
  • GJSON表达式: $["response-time"] > 0$["data.items.0"] < 0gjson
  • 带参数的算术表达式: (requests_made * requests_succeeded / 100) >= 90
  • 字符串比较: http_response_body == "service is ok"
  • 日期比较表达式: now() > date(“2022-05-01 23:59:59”)
  • 时间戳比较表达式: date(1651467728) > date("2022-05-01 23:59:59")
  • 字符长度计算表达式:strlen("someReallyLongInputString") <= 16

自定义方法

示例:计算最大值

func TestFunc(t *testing.T) {
	val, err := Full(
		WithFunc("max", func(args ...interface{}) (interface{}, error) {
			if len(args) == 0 {
				return 0, nil
			}
			max := func(a, b float64) float64 {
				if a > b {
					return a
				}
				return b
			}
			var val float64
			val, ok := args[0].(float64)
			if !ok {
				return 0, fmt.Errorf("max() expects number arguments")
			}
			for i, arg := range args {
				if i == 0 {
					continue
				}
				arg, ok := arg.(float64)
				if !ok {
					return 0, fmt.Errorf("max() expects number arguments")
				}
				val = max(val, arg)
			}
			return val, nil
		}),
	).EvalFloat(`max(1,2,3,a)`, map[string]interface{}{"a": float64(6)})
	if err != nil {
		t.Fatalf("Eval() error = %v", err)
	}
	t.Log(val == 6)
	// output: true
}

性能测试

goos: darwin
goarch: amd64
pkg: github.com/StrangeYear/goeval
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkEvaluationSingle-16                 	 2371440	       508.2 ns/op
BenchmarkEvaluationNumericLiteral-16         	  979606	      1178 ns/op
BenchmarkEvaluationLiteralModifiers-16       	  604010	      1903 ns/op
BenchmarkEvaluationParameter-16              	 1344694	       885.3 ns/op
BenchmarkEvaluationParameters-16             	  712880	      1594 ns/op
BenchmarkEvaluationParametersModifiers-16    	  478978	      2434 ns/op
BenchmarkComplexExpression-16                	  143646	      8187 ns/op
BenchmarkRegexExpression-16                  	  451497	      2595 ns/op
BenchmarkFunc-16                             	  268068	      4476 ns/op
BenchmarkJSON-16                             	  460448	      2557 ns/op
BenchmarkNested-16                           	  520017	      2287 ns/op
BenchmarkSpecial-16                          	 1323009	       908.7 ns/op
PASS