布尔表达式评估 1.2.2

布尔表达式评估 1.2.2

Alexis Bridoux 负责。



  • Alexis Bridoux

Swift Package Manager Cocoa Pods

布尔表达式简介

这个库用于评估像 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 结构。

例如,您可以定义 hasPrefixhasSuffix 运算符(注意这些运算符已经存在,并且它们的符号是任意选择的)

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,可以写成 truefalse
  • Variables,必须以字母(小写或大写)开头,可以包含连字符 - 和下划线 _。您可以比较两个变量。请注意,布尔变量可以直接写出来,不加 ==,以评估其状态是否为 true

给定的以下变量,这里有一些例子

变量

  • "isUserAWizard": "true"
  • "userName": "Gandalf"
  • "userAge": "400"
  • "fellowship": "甘道夫、佛罗多、山姆、阿拉贡、金利、莱戈拉斯、博罗米尔、梅里、皮平"
  • "hobbit": "比尔博"
  • "passphrase": "你不能通过!"

表达式

  • isUserAWizard == true && hobbit == "Bilbo" → true
  • userAge >= 400 || userName != "Saruman" → true
  • fellowship <: hobbit → false
  • userAge < 400 && userName == "Gandalf" → false
  • (userAge < 400 && userName == "Gandalf") || fellowship <: "Aragorn" → true
  • isUserAWizard && 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数组中的数组添加一个新的数组。当遇到闭括号时,最后创建的数组被缩减为一个布尔值,然后将其注入到前一个数组中。然后删除最后创建的数组。