一架梯子,一头程序猿,仰望星空!

Golang表达式引擎(Expr)

快速入门Expr引擎,探索其在Go环境中动态处理表达式的能力。从安装、编译表达式到进阶语法应用,一步步解锁Expr的核心特性和安全、高效编程实践。

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包含了变量foobar。表达式foo + bar在编译时会从环境中推断foobar的类型,并在运行时使用这些变量的值来评估表达式结果。

4. Expr语法详解

4.1 变量和字面量

Expr表达式引擎能够处理常见的数据类型字面量,包括数字、字符串和布尔值。字面量是直接在代码中写出的数据值,比如42"hello"true都是字面量。

数字

在Expr中,您可以直接写出整数和浮点数:

42      // 表示整数 42
3.14    // 表示浮点数 3.14

字符串

字符串字面量使用双引号"或者反引号``包裹,例如:

"hello, world" // 双引号包裹的字符串,支持转义字符
`hello, world` // 反引号包裹的字符串,保持字符串格式不变,不支持转义

布尔值

布尔值只有truefalse两种,代表逻辑上的真和假:

true   // 布尔真值
false  // 布尔假值

变量

Expr还允许在环境中定义变量,然后在表达式中引用这些变量。例如:

env := map[string]interface{}{
    "age": 25,
    "name": "Alice",
}

在表达式中就可以引用agename

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不是字典的键

还有一些高级的集合操作函数,比如allanyonenone,这些函数需要结合匿名函数(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支持内置函数和自定义函数,使得表达式更加强大和灵活。

如何使用内置函数

内置函数像lenallnoneany等可以直接在表达式中使用。

// 内置函数示例
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 官方文档


章节目录