{ Eval }
依赖管理器
👨🏻💻 关于
Eval 是一个轻量级的解释器框架,由 Swift 编写,为
它可以在运行时评估表达式,包括您定义的运算符和数据类型。
- | |
该框架目前支持两种不同的执行模式。
- 强类型表达式:像编程语言一样。
- 模板语言:在任意字符串环境中评估表达式。
让我们看看一些简单的示例。
表达式的表述(以及在运行时评估)非常简单,例如:
5 in 1...3
评估为false
布尔类型。'Eval' starts with 'E'
评估为true
布尔类型。'b' in ['a','c','d']
评估为false
布尔类型。x < 2 ? 'a' : 'b'
根据输入变量x
的整数值,评估为"a"
或"b"
字符串类型。Date(2018, 12, 13).format('yyyy-MM-dd')
评估为"2018-12-13"
字符串。'hello'.length
评估为5
整数。now
评估为Date()
。
模板,例如:
{% if name != nil %}Hello{% else %}Bye{% endif %} {{ name|default('user') }}!
其输出为Hello Adam!
或Bye User!
。Sequence: {% for i in 1...5 %}{{ 2 * i }} {% endfor %}
结果为2 4 6 8 10
。
等等……这些表达式的结果取决于内容,由评估确定。它可以返回任何类型,比如函数返回的 (String, [Double], Date,甚至您自己的自定义类型。)
您可以在下面的示例部分找到各种使用方法。
🏃🏻 状态
- 库实现
- API 最终化
- Swift Package Manager 支持
- 初始文档
- 示例项目(模板引擎)
- CocoaPods 支持
- CI
- 代码测试覆盖率
- v1.0
- 全面详细文档
- 贡献指南
- 更多示例项目
- 调试辅助工具
- v1.1
这个项目目前处于一个非常早期阶段,我仍在处理所有开源相关任务,如启动 CI、创建美观的文档页面、管理稳定性相关的行政任务。
请持续关注更新!
📈 开始使用
为了使表达式生效,您需要创建一个解释器实例,提供您想支持的数据类型和表达式,以及可能的一些输入变量——如果您需要的话。
let interpreter = TypedInterpreter(dataTypes: [number, string, boolean, array, date],
functions: [multipication, addition, ternary],
context: Context(variables: ["x": 2.0]))
随后用字符串表达式调用它。
let result = interpreter.evaluate("2 * x + 1") as? Double
🤓 简单示例
让我们查看一个相当复杂的例子,并从头开始构建!让我们实现一种可以解析以下表达式的语言:
x != 0 ? 5 * x : pi + 1
这里有一个三元运算符 ?:
,我们将需要。同时,支持数字字面(0
、5
和 1
)和布尔类型(true/false
)。还有一个不等于运算符 !=
和一个 pi
常量。不要忘了加法 +
和乘法 *
操作符!
首先,这是数据类型。
let numberLiteral = Literal { value,_ in Double(value) } //Converts every number literal, if it can be represented with a Double instance
let piConstant = Literal("pi", convertsTo: Double.pi)
let number = DataType(type: Double.self, literals: [numberLiteral, piConstant]) { String(describing: $0) }
let trueLiteral = Literal("true", convertsTo: true)
let falseLiteral = Literal("false", convertsTo: false)
let boolean = DataType(type: Bool.self, literals: [trueLiteral, falseLiteral]) { $0 ? "true" : "false" }
(最后一个参数,通过一个块表示,告诉框架如何将此类数据作为字符串用于调试消息或其他目的)
现在,让我们构建运算符。
let multiplication = Function<Double>(Variable<Double>("lhs") + Keyword("*") + Variable<Double>("rhs")) { arguments in
guard let lhs = arguments["lhs"] as? Double, let rhs = arguments["rhs"] as? Double else { return nil }
return lhs * rhs
}
let addition = Function<Double>(Variable<Double>("lhs") + Keyword("+") + Variable<Double>("rhs")) { arguments in
guard let lhs = arguments["lhs"] as? Double, let rhs = arguments["rhs"] as? Double else { return nil }
return lhs + rhs
}
let notEquals = Function<Bool>(Variable<Double>("lhs") + Keyword("!=") + Variable<Double>("rhs")) { arguments in
guard let lhs = arguments["lhs"] as? Double, let rhs = arguments["rhs"] as? Double else { return nil }
return lhs != rhs
}
let ternary = Function<Any>(Variable<Bool>("condition") + Keyword("?") + Variable<Any>("true") + Keyword(":") + Variable<Any>("false")) { arguments in
guard let condition = arguments["condition"] as? Bool else { return nil }
if condition {
return arguments["true"]
} else {
return arguments["false"]
}
}
看起来一切就绪。让我们评估我们的表达式!
let interpreter = TypedInterpreter(dataTypes: [number, boolean],
functions: [multipication, addition, notEquals, ternary])
let result : Double = interpreter.evaluate("x != 0 ? 5 * x : pi + 1", context: Context(variables: ["x": 3.0]))
XCTAssertEqual(result, 15.0) //Pass!
现在,我们已经有了运算符和数据类型,我们还可以使用这些数据类型评估任何东西。
interpreter.evaluate("3 != 4") as Bool
interpreter.evaluate("2 + 1.5 * 6") as Double
(由于乘法在数组中定义得较早,其优先级也更高,符合预期)interpreter.evaluate("true ? 1 : 2.5") 转换为 Double
如你所见,使用简单的构建块构建自定义语言非常简单且直观。只需要几个自定义的数据类型和函数,可能性就无穷无尽。运算符、函数、字符串、数组、日期...
框架的座右铭:构建你自己的(迷你)语言!
⚡️ 安装
你有几种方法可以将库包含在你的应用中。
- Swift 包管理器
- CocoaPods
- Carthage
- 手动
Swift 包管理器
只需将以下行添加到您的依赖关系中
.package(url: "https://github.com/tevelee/Eval.git", from: "1.5.0"),
然后在您的目标中按名称引用它
targets: [
.target(name: "MyAwesomeApp", dependencies: ["Eval"]),
]
最后,运行集成命令
swift package resolve
CocoaPods
只需将以下行添加到您的 Podfile
pod 'Eval', '~> 1.5.0'
然后安装新的依赖
pod install
Carthage
只需将以下行添加到您的 Cartfile
github "tevelee/Eval" >= 1.5.0
然后安装新的依赖
carthage update
手动
(不推荐!请使用包管理器来保持依赖关系最新。)
克隆存储库内容并将文件复制到您应用中的新目标中。
⁉️ 它是如何工作的?
解释器本身不会定义任何东西也不能独立处理输入字符串。它所做的就是识别模式。
通过创建数据类型,您向框架提供字面量,它可以将其解释为元素或表达式的结果。这些类型被转换为真实的 Swift 类型。
通过定义函数,您向框架提供识别的模式。函数也是具有类型的,它们以其评估结果返回 Swift 类型。函数只是由关键字和变量组成,没有别的。
- 关键字是静态字符串,不应被解释为数据(如
if
或{
,}
)。 - 另一方面,变量是有类型的值,递归评估。例如,如果一个变量识别到某个模式,则递归评估其主体,直到找到上下文变量或任何给定的数据类型的字面量。
函数也有代码块,它将已识别的变量以键值字典形式提供作为参数,你可以对它们做任何事情:打印它们、转换它们、修改或将它们分配给上下文变量。
比如上面的加法函数,由每边两个变量组成,中间是+
关键字。它还需要一个代码块,其中两边都提供了[String:Any]
,这样闭包可以获取占位符的值并将它们相加。
这个解决方案有一个有趣的方面:与传统的本地解释器或编译器不同,它是从顶部到底部识别模式的。这意味着它查看输入字符串,你的表达式,并按优先级顺序识别模式,并且递归地越来越深入,直到遇到最基本的表达式。
然而,传统的解释器是逐个字符解析表达式,然后将结果发送给词法分析器,即词法分析器,然后构建一个抽象语法树(这是高度可优化的),最后将其转换为二进制(编译器)或在运行时评估它(解释器),一句话:自下而上。
这两种解决方案可以以各种方式比较。两个主要区别在于易用性和性能。这个版本的解释器提供了一个轻松的方式来定义模式、类型等,但代价也很高!它无法像传统的编译器那样优化地解析,因为它没有内部的表达式图(AST),但仍然以一种可接受的方式运行良好。在定义方面,这个框架提供了易于理解的语言元素的方式,但传统的一个真的很落后,因为词法分析器通常是一个丑陋的、难于理解的有限状态机,或者是正则表达式,这 baked INTO 解释器代码本身。
💡 动机
我还有一个项目,在这个项目中,我使用简短的模板生成Objective-C和Swift模型对象以及大量的工具。这个项目目前不能用Swift来做,因为没有足够强大的模板语言来创建我的模板。最终我使用了一个名为Twig的第三方PHP框架。(我最终使用了第三方PHP框架)。所以,最后,我创建了Swift的版本!
结果,让它稍微通用的地方使得整个事情在整个真正强大且灵活地用于不同的用例。
模式匹配是有的,但我很快意识到,我还需要表达式,以便在打印、if/while语句进行评估等。首先,我看到了一个名为Nick Lockwood制作的优秀库Expression,它可以评估数字表达式。不幸的是,我想要的更多,定义字符串、日期、数组以及更多类型的表达式,所以我使用了现有的模式匹配解决方案来带来这种功能。
在我发现这个通解的强大功能后,情况变得积极起来。整个事情简直让人惊叹,语言功能可以在几秒钟内定义,我想把这一发现与世界分享,所以,这就是它 :)
📚 示例
我包含了一些用例,这些用例在处理事物的方式上带来了显著的改进——至少在我以前的项目中是这样。
模板语言
我能够使用这个框架和没有任何其他工具的情况下,创建一个完整的模板语言。它几乎可以与我所提到的那些相媲美(Twig)。这是所有例子中最先进的一个!
我创建了一个标准库,包含了所有你能想象的运算符。借助辅助函数,每个运算符都是一个简单的一行代码的添加。添加了重要的数据类型,如数组、字符串、数字、布尔值、日期等,还有一些函数,使其更加出色。:查看灵感的来源!
这些一起,为我的模型对象生成项目做出了优秀的补充,并且对服务器端 Swift 开发来说也**真的非常有用**!
富文本解析器
我还创建了一个小例子,使用类似于 XML 的标签解析简单的表达式中的富文本字符串,例如粗体、斜体、下划线、彩色等。
仅使用几个运算符,这个解决方案可以提供基本的 API 中的富文本字符串,否则这些字符串很难管理。
我的连接项目是一个 iOS 应用程序,使用 Spotify 的 HUB 框架,现在我可以为我视图模型提供丰富的文本,并从 JSON 字符串结果中解析它们。
颜色解析器
颜色解析器也被我曾提到的 BFF(Backend For Frontend,不是#ffddee
、red
或 rgba(1,0.5,0.4,1)
)的 Swift Color 对象。我在存储库中也包括了这个基本示例。
🙋 贡献
任何人都非常欢迎为 Eval 做出贡献!这甚至可以是文档或代码中的补充,通过 提出一个 issue 或以 pull request 的形式。这两者对我来说都同样 valuable!我很乐意帮助任何人!
如果您需要帮助或想要报告错误 - 请提交一个问题。请提供尽可能多的信息;示例代码也能使我在帮助您时更加容易。查看更多贡献指南以获取更多信息。
我收集了一些用例,对于想要提升项目吸引力的人来说,这些都是极佳的开始任务的机会!
👀 详情
请查看https://tevelee.github.io/Eval以获取更详细的文档页面!
👤 作者
我是Laszlo Teveli,软件工程师,iOS布道者。在空闲时间,我喜欢从事我的爱好项目并将它们开源。
随时通过tevelee [at] gmail [dot] com
或Twitter上的@tevelee
联系我。
⚖️ 许可证
Eval遵循Apache 2.0许可规则。有关更多信息,请参阅LICENSE文件。