Eval 是一个轻量级的解释器框架,使用 Swift 编写,适用于 📱iOS、🖥 macOS 和 🐧Linux 平台。
它在运行时评估表达式,使用您定义的运算符和数据类型。
🍏 优点 | 🍎 缺点 |
---|---|
🐥 轻量级 - 整个引擎实际上只有几百行代码 | 🤓 另一方面,创建自定义运算符和数据类型可能需要额外的几行代码 - 这取决于您的需求 |
✅ 易于使用的 API - 在几秒钟内创建新的语言元素 | ♻️ 表达式的评估结果必须是强类型的,因此您只能接受您期望的结果类型 |
🎢 有趣 - 由于它非常容易使用,因此添加(甚至是复杂的)语言功能也令人愉快 | - |
🚀 快速执行 - 我正在尽力优化。尽管如此,它还是有局限性 | 🌧 由于它是一个非常通用的概念,与原生解释器相比,某些优化无法实现 |
该框架目前支持两种不同的执行模式
让我们看几个例子
非常容易制定表达式(并在运行时评估它们),例如
5 in 1...3
评估结果为 false
Bool 类型'Eval' starts with 'E'
评估结果为 true
Bool 类型'b' in ['a','c','d']
评估结果为 false
Bool 类型x < 2 ? 'a' : 'b'
基于 x
Int 输入变量,评估结果为 "a"
或 "b"
String 类型Date(2018, 12, 13).format('yyyy-MM-dd')
评估结果为 "2018-12-13"
字符串'hello'.length
评估结果为 5
Integernow
评估结果为 Date()
以及模板,例如
{% if name != nil %}你好{% else %}再见{% endif %} {{ name|default('user') }}!
,其输出为 你好 Adam!
或 再见 User!
Sequence: {% for i in 1...5 %}{{ 2 * i }} {% endfor %}
,其输出为 2 4 6 8 10
等等... 这些表达式的结果取决于内容,由评估决定。它可以是函数返回的任何类型(String、[Double]、Date,甚至您自己的自定义类型。)
您可以在下面的示例部分找到各种使用方法。
这只是项目的早期阶段,我仍然深入参与与开源相关的各项任务,例如启动 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" }
(最后一个参数,以代码块的形式表示,告诉框架如何将这种数据类型格式化为 String 以用于调试消息或其他目的)
现在,让我们构建运算符
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") as Double
正如您所看到的,使用简单的构建块构建自定义语言非常容易和直观。 仅需几个自定义数据类型和函数,可能性是无限的。 运算符、函数、字符串、数组、日期...
该框架的座右铭:构建您自己的(迷你)语言!
您有几种选择将库包含在您的应用程序中。
只需将以下行添加到您的依赖项中
.package(url: "https://github.com/tevelee/Eval.git", from: "1.5.0"),
并在您的目标中按名称引用它
targets: [
.target(name: "MyAwesomeApp", dependencies: ["Eval"]),
]
最后,运行集成命令
swift package resolve
只需将以下行添加到您的 Podfile
pod 'Eval', '~> 1.5.0'
并安装新的依赖项
pod install
只需将以下行添加到您的 Cartfile
github "tevelee/Eval" >= 1.5.0
并安装新的依赖项
carthage update
(不推荐! 请改用包管理器来保持您的依赖项是最新的。)
克隆存储库内容并将文件复制到您应用程序中的新目标中。
解释器本身没有定义任何内容或任何处理输入字符串的方式。 它所做的只是识别模式。
通过创建数据类型,您为框架提供了字面量,框架可以将其解释为元素或表达式的结果。 这些类型被转换为真正的 Swift 类型。
通过定义函数,您为框架提供了要识别的模式。 函数也是类型化的,它们返回 Swift 类型作为其评估的结果。 函数由关键字和变量组成,仅此而已。
if
或 {
、}
)。函数也有代码块,这些代码块在键值字典参数中提供已识别的变量,您可以对它们执行任何操作:打印它们、转换它们、修改它们或将它们分配给上下文变量。
例如,上面的加法函数由两侧的两个变量和中间的 +
关键字组成。 它还需要一个代码块,其中两侧都在 [String:Any]
中给出,以便闭包可以获取占位符的值并将它们相加。
这个解决方案有一个有趣的方面:与传统的 - 原生的 - 解释器或编译器不同,这个解释器从上到下识别模式。 意思是,它查看输入字符串(您的表达式),并按优先级顺序识别模式,并递归地深入,直到遇到最基本的表达式。
然而,传统的解释器逐字符解析表达式,将结果馈送到词法分析器、标记器,然后构建抽象语法树(可以高度优化),最后将其转换为二进制文件(编译器)或在运行时评估它(解释器),总而言之:自下而上。
这两种解决方案可以在各个方面进行比较。 两个主要区别在于易用性和性能。 这个版本的解释器提供了一种轻松定义模式、类型等的方法,但这是有代价的! 它不能像传统编译器那样进行最佳解析,因为它没有表达式的内部图(AST),但仍然以远超可接受的方式执行。 从定义上来说,这个框架提供了一种易于理解的语言元素方式,但传统的框架确实落后了,因为词法分析器通常是一个丑陋的、难以理解的状态机或正则表达式,内置于解释器代码本身中。
我还有另一个项目,在该项目中,我基于非常短的模板生成带有大量实用程序的 Objective-C 和 Swift 模型对象。 这个项目目前在 Swift 中是不可能实现的,因为没有足够强大的模板语言来创建我的模板。 (我最终使用了第三方 PHP 框架,名为 Twig)。 所以最后,我为 Swift 创建了一个!
事实证明,使其在某些方面更通用,使整个事情真正有能力且灵活地用于不同的用例。
模式匹配已经存在,但很快我意识到,我也需要表达式,用于打印、在 if/while 语句中评估等等。 首先,我研究了一个优秀的库 Expression,由 Nick Lockwood 创建,它能够评估数值表达式。 不幸的是,我想要更多,定义字符串、日期、数组以及更多类型和表达式,所以我使用了我现有的模式匹配解决方案来实现此功能。
在我发现这种通用解决方案的功能后,结果非常积极。 整个事情让我大吃一惊,语言功能可以在几秒钟内定义,我想与世界分享这个发现,所以你看到了 :)
我包含了一些用例,这些用例在以前的项目中,至少在我的以前的项目中,对事物的处理方式带来了显着的改进。
我能够完全使用这个框架创建一个成熟的模板语言,仅此而已。 它几乎就像我提到的那个 (Twig) 的竞争对手。 这是所有示例中最先进的一个!
我创建了一个标准库,其中包含您可以想象的所有可能的运算符。 借助助手,每个运算符都是一个小的、一行的添加。 添加了重要的数据类型,例如数组、字符串、数字、布尔值、日期等,以及一些函数,使其更棒。 查看以获取灵感!
总而言之,它为我的模型对象生成项目增添了出色的功能,并且对于服务器端 Swift 开发也非常有用!
我创建了另一个小例子,使用 XML 样式标签(例如粗体、斜体、下划线、彩色等)从简单表达式解析属性化字符串。
仅需几个运算符,此解决方案就可以从基本 API 交付属性化字符串,否则这些 API 将难以管理。
我连接的项目是一个 iOS 应用程序,使用 Spotify HUB 框架,在其中我现在可以使用我的视图模型提供富文本字符串,并从 JSON 字符串结果中解析它们。
颜色解析器也由我之前提到的 BFF(Backend For Frontend,不是 👭)项目使用。 它可以从多种不同样式的字符串中解析 Swift Color 对象,例如 #ffddee
、red
或 rgba(1,0.5,0.4,1)
。 我也在存储库中包含了这个基本示例。
非常欢迎任何人为 Eval 做出贡献! 甚至可以是通过 提出问题 或以 pull request 的形式直接添加到文档或代码中。 两者对我来说都同样有价值! 很高兴帮助任何人!
如果您需要帮助或想报告错误 - 请提交 issue。 确保提供尽可能多的信息; 示例代码也使我更容易帮助您。 查看 贡献指南 以获取更多信息。
我收集了一些用例,以及对于任何有动力将这个项目推向更令人印象深刻的状态的初学者任务的绝佳机会!
请查看 https://tevelee.github.io/Eval 以获取更详细的文档页面!
我是 Laszlo Teveli,软件工程师,iOS 布道者。 在我的空闲时间,我喜欢从事我的业余项目并将它们开源 😉
随时通过 tevelee [at] gmail [dot] com
或 Twitter 上的 @tevelee
与我联系。
Eval 在 Apache 2.0 许可规则下可用。 有关更多信息,请参阅 LICENSE 文件。