该库可用于评估字符串表达式,例如 variable1 >= 2 && variable2 == "Value"
。变量由字典 [String : String]
提供,表示变量及其值。评估字符串表达式的复杂度为 O(n)。
Nick Lockwood 的 Expression 和 Lázló Teveli 的 Eval 都是有趣的替代方案。Exression 是一个即用型框架,而 Lázló Teveli 在深度定制他的框架使用方面做了大量工作。BooleanExpressionEvaluation 的目标是专注于布尔表达式,而不需要其他表达式的评估。因此,该框架在使用和定制方面复杂度稍低。
将此包添加到您的 Package.swift 文件中的依赖项
let package = Package (
...
dependencies: [
.package(url: "https://github.com/ABridoux/BooleanExpressionEvaluation.git", from: "1.0.0")
],
...
)
或者直接使用 Xcode 菜单 File > Swift Packages > Add Package Dependency 并复制/粘贴 git URL:https://github.com/ABridoux/BooleanExpressionEvaluation.git
然后在您的文件中导入 BooleanExpressionEvaluation
import BooleanExpressionEvaluation
要评估一个字符串,创建一个 Expression
,将该字符串作为 init 的参数传递。请注意,初始化可能会抛出错误,因为字符串表达式可能包含不正确的元素。然后您可以调用表达式的 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)
}
}
表达式中有两种类型的运算符可用:比较运算符,用于比较变量和另一个操作数;逻辑运算符,用于比较两个布尔操作数。
==
表示等于!=
表示不等于>
表示大于>=
表示大于或等于<
表示小于<=
表示小于或等于isIn
:如果左操作数包含在右操作数中,则结果为 true,右操作数以字符串值列表的形式提供。右操作数必须填充以逗号分隔的值。例如:如果变量 Ducks
的值为 "Riri, Fifi, Loulou",则比较 'Riri' isIn Ducks
被评估为 true。 可以使用 "" 转义逗号。hasPrefix
hasSuffix
contains
: 当左侧字符串包含右侧字符串时为 true。matches
: 当左操作数与右操作数匹配时为 true,以正则表达式的形式给出。&&
表示 与||
表示 或!
表示 非,它适用于单个布尔值和带括号的表达式。您可以在 Operator
结构的扩展中定义自定义运算符。然后将此运算符添加到 Operator.models
集合中。LogicOperator
结构也是如此。
例如,您可以定义 hasPrefix
运算符(请注意,这些运算符已经存在)。
extension Operator {
public static var hasPrefix: Operator { Operator("hasPrefix", isKeyword: true) { (lhs, rhs) in
guard let stringLhs = lhs as? String, let stringRhs = rhs as? String else {
throw ExpressionError.mismatchingType
}
return stringLhs.hasPrefix(stringRhs)
}}
}
然后,在您的应用程序设置中
Operator.models.insert(.hasPrefix)
最后,您可以直接添加一个运算符
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.removeFromModels(:)
方法。 您还可以通过使用具有相同描述的运算符更新 Operator.models
集来直接覆盖默认运算符的行为。
注意
由于目前无法将闭包签名限制为协议而不将类型指定为结构中的泛型,因此我们无法仅允许运算符 evaluate
闭包中的 Comparable
操作数。 尽管如此,现在此框架中仅允许字符串、布尔值和双精度浮点数作为操作数。 此外,您可能希望将双精度浮点数与字符串进行比较,例如使用 count
等运算符。 如果两个操作数都使用相同的类型进行比较,则这是不可能的。
您可以使用比较运算符比较变量和操作数。有四种类型的操作数。
String
:仅用双引号引起来Number
:包括所有数值,包括浮点数Boolean
:写为 true 或 falseVariables
:必须以字母(小写或大写)开头,并且可以包含连字符 -
和下划线 _
。您可以比较两个变量。请注意,可以不使用 ==
编写布尔变量来评估它们的状态是否为 true
。给定以下变量,以下是一些示例
isUserAWizard == true && hobbit == "Bilbo"
→ trueuserAge >= 400 || userName != "Saruman"
→ truefellowship isIn hobbit
→ falseuserAge < 400 && userName == "Gandalf"
→ false(userAge < 400 && userName == "Gandalf") || fellowship isIn "Aragorn"
→ trueisUserAWizard && passphrase == "You shall not pass!"
→ true变量通过 [String: String]
字典提供给 Expression
。当使用比较运算符比较两个操作数时,至少其中一个操作数必须是变量。否则,比较表达式的结果是已知的,并且不需要评估该表达式。
Expression
的一个有用属性是 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
,将其描述为文字布尔表达式。
表示表达式的一个元素,例如变量、运算符或数字。有四个嵌套的 Enum
来对表达式的不同元素进行分组
ComparisonOperator
:例如 >
或 =
用于评估变量与另一个操作数之间的比较LogicOperator
:用于评估两个布尔值的结果括号
Operands
:始终具有关联的值,例如双精度浮点数、布尔值、字符串或变量类似于 ExpressionElement
的数组,尽管它是一个实现 Collection
协议的 struct
。
将包含比较表达式的表达式转换为布尔表达式,该布尔表达式仅包含逻辑运算符、布尔操作数和括号。
使用 BooleanExpressionTokenizator
来获取布尔表达式的不同元素并对其进行评估。每次遇到左括号时,都会将一个新数组添加到 expressionResults
数组中。当遇到右括号时,最后创建的数组被简化为布尔值,然后注入到前一个数组中。然后删除最后创建的数组。