布尔表达式简介
这个库用于评估像 variable1 >= 2 && variable2 == "Value"
这样的字符串表达式。这些变量通过一个字典 [String : String]
提供,表示变量及其值。评估字符串表达式的复杂性为 O(n)
替代方案
来自 Nick Lockwood 的 Expression 和 Lázló Teveli 的 Eval 都是不错的选择。Expression 是一个现成的框架,而 Lázló Teveli 对他的框架进行了大量定制。布尔表达式评估的目标是专注于布尔表达式,当不需要其他表达式评估时。因此,该框架在自定义方面稍微简单一些。
使用方法
Cocoa Pods
要将库添加到您的项目中,请将 pod 添加到 podfile 中:pod 'BooleanExpressionEvaluation'
Swift 包管理器
将包添加到你的 Package.swift 文件中的依赖项
let package = Package (
...
dependencies: [
.package(url: "https://github.com/ABridoux/BooleanExpressionEvaluation.git", from: "1.0.0")
],
...
)
或者,简单使用 Xcode 菜单 文件 > Swift 包 > 添加包依赖 并复制粘贴 git URL: https://github.com/ABridoux/BooleanExpressionEvaluation.git
然后在你的文件中导入 BooleanExpressionEvaluation
import BooleanExpressionEvaluation
评估字符串
要评估一个字符串,创建一个 Expression
实例,将字符串作为初始化参数传入。注意,初始化可能抛出错误,因为字符串表达式可能包含不正确的元素。然后你可以调用表达式的 evaluate()
函数。这个函数也可能抛出错误,因为表达式可能有语法错误。
例如
(请注意,从 Swift 5.0 开始使用原始字符串语法 #""#
允许使用双引号而不需要用 \
转义)
let variables = ["userAge": "15", "userName": "Morty"]
let stringExpression = #"userAge > 10 && userName == "Morty""#
let expression: Expression
do {
expression = try Expression(stringExpression)
} catch {
// handle errors such as invalid elements in the expression
return
}
do {
let result = try expression.evaluate(with: variables)
print("The user is allowed to travel across dimensions: \(result)")
} catch {
// handle errors such as incorrect grammar in the expression
}
你还可以在同一个 do{} catch{}
声明中创建 Expression
并评估它
let variables = ["userAge": "15", "userName": "Morty"]
let stringExpression = #"userAge > 10 && userName == "Morty""#
do {
let result = try Expression(stringExpression).evaluate(with: variables)
} catch {
// handle errors such as invalid elements or incorrect grammar in the expression
return
}
最后,一个简单的用例是在整个项目中实现 Expression
的扩展,当你总是使用单个来源的变量评估它们时,比如使用一个 VariablesManager
单例。
extension Expression {
func evaluate() throws -> Bool {
return try evaluate(with: VariablesManager.shared.variables)
}
}
操作符
表达式中有两种类型的操作符可用:比较操作符,用于比较变量和另一个操作数,以及逻辑操作符,用于比较布尔运算数。
默认比较操作符
==
表示 相等!=
表示 不等>
表示 大于>=
表示 大于等于<
表示 小于<=
表示 小于等于<:
表示包含。如果左操作数包含右操作数,则结果为真。左操作数必须用逗号分隔填充值。例如:如果变量Ducks
的值为 "Riri, Fifi, Loulou",则比较Ducks <: "Riri"
评估为真。~=
用于 hasPrefix=~
用于 hasSuffix
默认逻辑运算符
&&
用于 and||
用于 or
自定义运算符
您可以在 Operator
结构的扩展中定义自定义运算符。然后将其添加到 Operator.models
集合中。相同的规则也适用于 LogicOperator
结构。
例如,您可以定义 hasPrefix
和 hasSuffix
运算符(注意这些运算符已经存在,并且它们的符号是任意选择的)
extension Operator {
static var hasPrefix: Operator { Operator("~=") { (lhs, rhs) -> Bool? in
guard let lhs = lhs as? String, let rhs = rhs as? String else { return nil }
return lhs.hasPrefix(rhs)
}}
static var hasSuffix: Operator { Operator("=~") { (lhs, rhs) -> Bool? in
guard let lhs = lhs as? String, let rhs = rhs as? String else { return nil }
return lhs.hasSuffix(rhs)
}}
}
然后,在您的应用程序设置中
Operator.models.insert(.hasPrefix)
Operator.models.insert(.hasSuffix)
最后,您可以简单地添加一个运算符
Operator.models.insert(Operator("~=") { (lhs, rhs) in
guard let lhs = lhs as? String, let rhs = rhs as? String else { return nil }
return lhs.hasSuffix(rhs)
})
如果您想移除默认运算符,可以通过调用适当的 Operator.removeDefault[Operator_Name]()
方法。您也可以通过更新 Operator.models
集合以包括具有相同描述的运算符来直接覆盖默认运算符的行为。
注意
由于目前无法在结构体中将闭包签名限定为协议,而不指定结构体中的泛型类型,因此我们不允许仅在运算符 evaluate
闭包中使用 Comparable
操作数。不过,现在本框架中只允许字符串、布尔和双精度值作为操作数。此外,您可能想要使用类似 count
这样的运算符来比较双精度值和字符串。如果两个操作数可以比较相同的类型,则这将是行不通的。
操作数
您可以使用比较运算符比较一个变量和一个操作数。有四种类型的操作数。
String
,只能用双引号引用Number
,包括所有数值,包括浮点数Boolean
,可以写成 true 或 falseVariables
,必须以字母(小写或大写)开头,可以包含连字符-
和下划线_
。您可以比较两个变量。请注意,布尔变量可以直接写出来,不加==
,以评估其状态是否为true
。
给定的以下变量,这里有一些例子
变量
- "isUserAWizard": "true"
- "userName": "Gandalf"
- "userAge": "400"
- "fellowship": "甘道夫、佛罗多、山姆、阿拉贡、金利、莱戈拉斯、博罗米尔、梅里、皮平"
- "hobbit": "比尔博"
- "passphrase": "你不能通过!"
表达式
isUserAWizard == true && hobbit == "Bilbo"
→ trueuserAge >= 400 || userName != "Saruman"
→ truefellowship <: hobbit
→ falseuserAge < 400 && userName == "Gandalf"
→ false(userAge < 400 && userName == "Gandalf") || fellowship <: "Aragorn"
→ trueisUserAWizard && passphrase == "You shall not pass!"
→ true
变量
变量作为 表达式
的参数提供,以 [String: String]
字典形式。当使用比较运算符比较两个操作数时,其中一个操作数至少是变量。否则,比较表达式的结果已知,表达式无需评估。
表达式
有一个有用的属性 variables
,它是一个包含所有相关变量名称的数组。
匹配变量的默认正则表达式是 [a-zA-Z]{1}[a-zA-Z0-9_-]+
。您可以选择在初始化表达式时提供其他正则表达式。
let expression = try? Expression("#variable >= 2", variablesRegexPattern: "[a-zA-Z#]{1}[a-zA-Z0-9#]+")
如果您始终使用相同的正则表达式,您应该考虑编写 Expression
的扩展来添加具有此默认表达式的初始化器。因此,在我们的最后一个示例中
extension Expression {
init(stringExpression: String) throws {
try self.init(stringExpression, variablesRegexPattern: "[a-zA-Z#]{1}[a-zA-Z0-9#]+")
}
}
可编码
Expression
实现了 Codable
协议,并以 String
格式进行编码/解码。因此,您可以尝试将 String
值解码为表达式。编码它将返回一个 String
,描述它为字面量布尔表达式。
关于内部逻辑的详细信息
ExpressionElement
表示表达式中元素,例如变量、操作符或数字。有四个嵌套的Enum
来分组表达式的不同元素
- 如
>
或#=
之类的ComparisonOperator
用于评估变量与其他操作数的比较 - 用于评估两个布尔值的
LogicOperator
括号
Operands
,它们始终关联一个值,如双精度浮点数、布尔值、字符串或变量
Expression
类似于ExpressionElement
的数组,尽管它是一个实现了Collection
协议的struct
。
BooleanExpressionTokenizator
将包含比较表达式的表达式转换为只包含逻辑运算符、布尔操作数和括号的布尔表达式
ExpressionEvaluator
使用BooleanExpressionTokenizator
获取布尔表达式的不同元素并评估它。每次遇到开括号时,向expressionResults
数组中的数组添加一个新的数组。当遇到闭括号时,最后创建的数组被缩减为一个布尔值,然后将其注入到前一个数组中。然后删除最后创建的数组。