Skip to content

Latest commit

 

History

History
290 lines (246 loc) · 7.2 KB

File metadata and controls

290 lines (246 loc) · 7.2 KB

Table of Contents generated with DocToc

expr-lang/expr

Expr表达式引擎是一个针对Go语言设计的动态配置解决方案,它以简单的语法和强大的性能特性著称。 Expr表达式引擎的核心是安全、快速和直观,很适合用于处理诸如访问控制、数据过滤和资源管理等场景。在Go语言中应用Expr,可以极大地提升应用程序处理动态规则的能力。 不同于其他语言的解释器或脚本引擎,Expr采用了静态类型检查,并且生成字节码来执行,因此它能同时保证性能和安全性

使用

内置函数

var Builtins = []*Function{
	{
		Name:      "all",
		Predicate: true,
		Types:     types(new(func([]any, func(any) bool) bool)),
	},
	{
		Name:      "none",
		Predicate: true,
		Types:     types(new(func([]any, func(any) bool) bool)),
	},
	// ...
}

array 相关

  • 函数 all 可以用来检验集合中的元素是否全部满足给定的条件。它接受两个参数,第一个参数是集合,第二个参数是条件表达式。
// 检查所有 tweets 的 Content 长度是否小于 240
code := `all(tweets, len(.Content) < 240)`
  • any 函数用来检测集合中是否有任一元素满足条件。
// 检查是否有任一 tweet 的 Content 长度大于 240
code := `any(tweets, len(.Content) > 240)`
  • one 函数用于确认集合中只有一个元素满足条件。
// 检查是否只有一个 tweet 包含了特定关键词
code := `one(tweets, contains(.Content, "关键词"))`

Predicate

filter(0..9, {# % 2 == 0})

源码分析

(⎈|test-ctx:istio-system)➜  [email protected] tree -L 2 .        
.
├── LICENSE
├── README.md
├── SECURITY.md
├── ast
│   ├── dump.go
│   ├── node.go
│   ├── print.go
│   ├── print_test.go
│   ├── visitor.go
│   └── visitor_test.go
├── bench_test.go
├── builtin
│   ├── builtin.go
│   ├── builtin_test.go
│   ├── function.go
│   ├── lib.go
│   ├── utils.go
│   └── validation.go
├── checker
│   ├── checker.go
│   ├── checker_test.go
│   ├── info.go
│   ├── info_test.go
│   └── types.go
├── compiler
│   ├── compiler.go
│   └── compiler_test.go
├── conf
│   ├── config.go
│   └── types_table.go
├── docgen
│   ├── README.md
│   ├── docgen.go
│   ├── docgen_test.go
│   └── markdown.go
├── docs
│   ├── configuration.md
│   ├── environment.md
│   ├── functions.md
│   ├── getting-started.md
│   ├── language-definition.md
│   ├── patch.md
│   └── visitor.md
├── expr.go
├── expr_test.go
├── file
│   ├── error.go
│   ├── location.go
│   ├── source.go
│   └── source_test.go
├── go.mod
├── internal
│   ├── deref
│   ├── difflib
│   ├── spew
│   └── testify
├── optimizer
│   ├── const_expr.go
│   ├── filter_first.go
│   ├── filter_last.go
│   ├── filter_len.go
│   ├── filter_map.go
│   ├── fold.go
│   ├── in_array.go
│   ├── in_range.go
│   ├── optimizer.go
│   ├── optimizer_test.go
│   ├── predicate_combination.go
│   ├── sum_array.go
│   ├── sum_array_test.go
│   ├── sum_map.go
│   └── sum_map_test.go
├── parser
│   ├── lexer
│   ├── operator
│   ├── parser.go
│   ├── parser_test.go
│   └── utils
├── patcher
│   ├── operator_override.go
│   ├── value
│   ├── with_context.go
│   ├── with_context_test.go
│   ├── with_timezone.go
│   └── with_timezone_test.go
├── test
│   ├── coredns
│   ├── crowdsec
│   ├── deref
│   ├── fuzz
│   ├── gen
│   ├── interface_method
│   ├── mock
│   ├── operator
│   ├── patch
│   ├── pipes
│   ├── playground
│   └── time
├── testdata
│   ├── crash.txt
│   ├── crowdsec.json
│   └── examples.txt
└── vm
    ├── debug.go
    ├── debug_off.go
    ├── debug_test.go
    ├── func_types
    ├── func_types[generated].go
    ├── opcodes.go
    ├── program.go
    ├── program_test.go
    ├── runtime
    ├── utils.go
    ├── vm.go
    └── vm_test.go

37 directories, 78 files
// 解析并编译成字节码
func Compile(input string, ops ...Option) (*vm.Program, error) {
	// 初始化配置:包括内置函数
	config := conf.CreateNew()
	for _, op := range ops {
		op(config)
	}
	// 关闭指定的内置函数
	for name := range config.Disabled {
		delete(config.Builtins, name)
	}
	config.Check()

	// 根据传入的内容生成解析后的 ats 树
	tree, err := checker.ParseCheck(input, config)
	if err != nil {
		return nil, err
	}

	if config.Optimize {
		err = optimizer.Optimize(&tree.Node, config)
		if err != nil {
			var fileError *file.Error
			if errors.As(err, &fileError) {
				return nil, fileError.Bind(tree.Source)
			}
			return nil, err
		}
	}

	program, err := compiler.Compile(tree, config)
	if err != nil {
		return nil, err
	}

	return program, nil
}

第三方使用- argo-rollout

// https://github.com/argoproj/argo-rollouts/blob/ff3471a2fc3ccb90dbb1f370d7e399ff3064043a/utils/evaluate/evaluate.go
func EvalCondition(resultValue interface{}, condition string) (bool, error) {
	var err error

	env := map[string]interface{}{
		"result":  valueFromPointer(resultValue),
		"asInt":   asInt,
		"asFloat": asFloat,
		"isNaN":   math.IsNaN,
		"isInf":   isInf,
		"isNil":   isNilFunc(resultValue),
		"default": defaultFunc(resultValue),
	}

	unwrapFileErr := func(e error) error {
		if fileErr, ok := err.(*file.Error); ok {
			e = errors.New(fileErr.Message)
		}
		return e
	}

	program, err := expr.Compile(condition, expr.Env(env))
	if err != nil {
		return false, unwrapFileErr(err)
	}

	output, err := expr.Run(program, env)
	if err != nil {
		return false, unwrapFileErr(err)
	}

	switch val := output.(type) {
	case bool:
		return val, nil
	default:
		return false, fmt.Errorf("expected bool, but got %T", val)
	}
}

参考