1. 介绍
Expr表达式引擎是一个针对Go语言设计的动态配置解决方案,它以简单的语法和强大的性能特性著称。Expr表达式引擎的核心是安全、快速和直观,很适合用于处理诸如访问控制、数据过滤和资源管理等场景。在Go语言中应用Expr,可以极大地提升应用程序处理动态规则的能力。不同于其他语言的解释器或脚本引擎,Expr采用了静态类型检查,并且生成字节码来执行,因此它能同时保证性能和安全性。
1. 安装Expr
可以通过Go语言的包管理工具go get
来安装Expr表达式引擎:
go get github.com/expr-lang/expr
这条命令会将Expr的库文件下载并安装到你的Go工程中。这样你就可以在你的Go代码中引入和使用Expr了。
3. 快速开始
3.1 编译和运行基本表达式
让我们从一个最基础的例子开始,编写一个简单的表达式,编译这个表达式,然后运行它并获取结果。
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// 编译一个基础的加法表达式
program, err := expr.Compile(`2 + 2`)
if err != nil {
panic(err)
}
// 运行编译后的表达式,并没有传入环境,因为这里不需要使用任何变量
output, err := expr.Run(program, nil)
if err != nil {
panic(err)
}
// 打印结果
fmt.Println(output) // 输出 4
}
在这个例子中,表达式2 + 2
被编译成能运行的字节码,然后执行这段字节码并输出结果。
3.2 使用变量的表达式
接下来,我们创建一个包含变量的环境,编写使用这些变量的表达式,编译并运行这个表达式。
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// 创建一个包含变量的环境
env := map[string]interface{}{
"foo": 100,
"bar": 200,
}
// 编译一个使用环境中变量的表达式
program, err := expr.Compile(`foo + bar`, expr.Env(env))
if err != nil {
panic(err)
}
// 运行表达式
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
// 打印结果
fmt.Println(output) // 输出 300
}
在这个例子中,环境env
包含了变量foo
和bar
。表达式foo + bar
在编译时会从环境中推断foo
和bar
的类型,并在运行时使用这些变量的值来评估表达式结果。
4. Expr语法详解
4.1 变量和字面量
Expr表达式引擎能够处理常见的数据类型字面量,包括数字、字符串和布尔值。字面量是直接在代码中写出的数据值,比如42
、"hello"
和true
都是字面量。
数字
在Expr中,您可以直接写出整数和浮点数:
42 // 表示整数 42
3.14 // 表示浮点数 3.14
字符串
字符串字面量使用双引号"
或者反引号``包裹,例如:
"hello, world" // 双引号包裹的字符串,支持转义字符
`hello, world` // 反引号包裹的字符串,保持字符串格式不变,不支持转义
布尔值
布尔值只有true
和false
两种,代表逻辑上的真和假:
true // 布尔真值
false // 布尔假值
变量
Expr还允许在环境中定义变量,然后在表达式中引用这些变量。例如:
env := map[string]interface{}{
"age": 25,
"name": "Alice",
}
在表达式中就可以引用age
和name
:
age > 18 // 检查age是否大于18
name == "Alice" // 判断name是否等于"Alice"
4.2 运算符
Expr表达式引擎支持多种运算符,包含数学运算符、逻辑运算符、比较运算符及集合运算符等。
数学和逻辑运算符
数学运算符包括加(+
)、减(-
)、乘(*
)、除(/
)和取模(%
)。逻辑运算符包括逻辑与(&&
)、逻辑或(||
)和逻辑非(!
),例如:
2 + 2 // 计算结果为4
7 % 3 // 结果为1
!true // 结果为false
age >= 18 && name == "Alice" // 检查age是否不小于18且name是否等于"Alice"
比较运算符
比较运算符有相等(==
)、不等(!=
)、小于(<
)、小于等于(<=
)、大于(>
)和大于等于(>=
),用于比较两个值:
age == 25 // 检查age是否等于25
age != 18 // 检查age是否不等于18
age > 20 // 检查age是否大于20
集合运算符
Expr还提供了一些用于操作集合的运算符,如in
用于检查元素是否在集合中,集合可以是数组、切片或字典:
"user" in ["user", "admin"] // true,因为"user"在数组中
3 in {1: true, 2: false} // false,因为3不是字典的键
还有一些高级的集合操作函数,比如all
、any
、one
和none
,这些函数需要结合匿名函数(lambda)使用:
all(tweets, {.Len <= 240}) // 检查所有tweets的Len字段是否都不超过240
any(tweets, {.Len > 200}) // 检查是否存在tweets的Len字段超过200
成员操作符
在Expr表达式语言中,成员操作符允许我们访问Go语言中struct
的属性。这个特性让Expr可以直接操作复杂数据结构,非常地灵活实用。
使用成员操作符非常简单,只需要通过.
操作符跟上属性名即可。例如,如果有如下的struct
:
type User struct {
Name string
Age int
}
你可以这样编写表达式来访问User
结构的Name
属性:
env := map[string]interface{}{
"user": User{Name: "Alice", Age: 25},
}
code := `user.Name`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output) // 输出: Alice
处理nil值
在访问属性时,可能会遇到对象是nil
的情况。Expr提供了安全的属性访问,即使在结构体或者嵌套属性为nil
的情况下,也不会抛出运行时panic错误。
使用?.
操作符引用属性,如果对象为nil则返回nil,而不会报错。
author.User?.Name
等价表达式
author.User != nil ? author.User.Name : nil
??
操作符, 主要用于返回默认值
author.User?.Name ?? "Anonymous"
等价表达式
author.User != nil ? author.User.Name : "Anonymous"
pipe操作符
pipe操作符(|
)在Expr中用于将一个表达式的结果作为另一个表达式的参数。这类似于Unix shell中的管道操作,可以将多个功能模块串联起来,形成一个处理流水线。在Expr中,使用它可以创建更加清晰和简洁的表达式。
例如,我们有一个获取用户名称的函数和一个欢迎消息的模板:
env := map[string]interface{}{
"user": User{Name: "Bob", Age: 30},
"get_name": func(u User) string { return u.Name },
"greet_msg": "Hello, %s!",
}
code := `get_name(user) | sprintf(greet_msg)`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output) // 输出: Hello, Bob!
在这个示例中,我们首先通过get_name(user)
获取到用户的名称,然后通过pipe操作符|
把名称传递给sprintf
函数来生成最终的欢迎消息。
使用pipe操作符可以让我们的代码更加模块化,提高代码复用性,并使得表达式更加易读。
4.3 函数
Expr支持内置函数和自定义函数,使得表达式更加强大和灵活。
如何使用内置函数
内置函数像len
、all
、none
、any
等可以直接在表达式中使用。
// 内置函数示例
program, err := expr.Compile(`all(users, {.Age >= 18})`, expr.Env(env))
if err != nil {
panic(err)
}
// 注意:这里env需要包含users变量,每个用户都需要有Age属性
output, err := expr.Run(program, env)
fmt.Print(output) // 如果env中所有用户年龄都大于等于18,返回true
如何定义和使用自定义函数
在Expr中,通过在环境映射中传递函数定义来创建自定义函数。
// 自定义函数示例
env := map[string]interface{}{
"greet": func(name string) string {
return fmt.Sprintf("Hello, %s!", name)
},
}
program, err := expr.Compile(`greet("World")`, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
fmt.Print(output) // 返回 Hello, World!
在Expr中使用函数时,你可以让代码模块化并在表达式中加入复杂逻辑。通过结合变量、运算符和函数,Expr成为一个强大并易于使用的工具。记住,在构建Expr环境并运行表达式时,始终要确保类型安全。
5. 内置函数文档
Expr 表达式引擎为开发者提供了丰富的内置函数来处理各种复杂场景。下面我们将详细介绍这些内置函数及其使用方法。
all
函数 all
可以用来检验集合中的元素是否全部满足给定的条件。它接受两个参数,第一个参数是集合,第二个参数是条件表达式。
// 检查所有 tweets 的 Content 长度是否小于 240
code := `all(tweets, len(.Content) < 240)`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
any
与 all
类似,any
函数用来检测集合中是否有任一元素满足条件。
// 检查是否有任一 tweet 的 Content 长度大于 240
code := `any(tweets, len(.Content) > 240)`
none
none
函数用于检查集合中没有任何元素满足条件。
// 确保没有 tweets 是重复的
code := `none(tweets, .IsRepeated)`
one
one
函数用于确认集合中只有一个元素满足条件。
// 检查是否只有一个 tweet 包含了特定关键词
code := `one(tweets, contains(.Content, "关键词"))`
filter
filter
函数可以根据条件表达式筛选出满足条件的集合元素。
// 筛选出所有被标记为优先的 tweets
code := `filter(tweets, .IsPriority)`
map
map
函数用于将集合中的元素按照指定方法进行转换。
// 将所有 tweets 的发布时间格式化
code := `map(tweets, {.PublishTime: Format(.Date)})`
len
len
函数用于返回集合或字符串的长度。
// 获取用户名长度
code := `len(username)`
contains
contains
函数用于检查字符串是否包含子字符串或者集合是否包含特定元素。
// 检查用户名是否含有非法字符
code := `contains(username, "非法字符")`
以上只是 Expr 表达式引擎内置函数的一部分。通过这些功能强大的函数,您可以更加灵活和高效地处理数据和逻辑。更详细的函数列表和使用说明,可查阅Expr 官方文档。