Expression 是一个 Swift 框架,用于在 Apple 和 Linux 平台上运行时评估表达式。
Expression 库分为两个部分:
Expression
类,它类似于 Foundation 内置的 NSExpression
类,但对自定义运算符有更好的支持,更友好的 Swift API,以及更卓越的性能。
AnyExpression
,是 Expression 的扩展,处理任意类型,并为常见的类型(如 String
、Dictionary
、Array
和 Optional
)提供额外的内置支持。
在许多情况下,能够运行时评估一个简单的表达式非常有用。一些示例在库包含的示例应用程序中进行了演示:
但还有其他可能的应用,例如:
(如果您发现任何其他用例,请告诉我,我会添加它们)
通常,这类计算会涉及将 JavaScript 或 Lua 等重量级解释型语言嵌入到您的应用程序中。Expression 避免了这种开销,并且更安全,因为它降低了由于任意代码注入或无限循环、缓冲区溢出等导致的崩溃风险。
Expression 快速、轻量、经过充分测试,并且完全用 Swift 编写。对于评估简单表达式而言,它比使用 JavaScriptCore 快得多(有关科学比较,请参阅 基准测试 应用程序)。
Expression 的工作原理是将表达式字符串解析为符号树,然后可以在运行时对其进行评估。每个符号都映射到一个 Swift 闭包(函数),该闭包在评估期间执行。有表示常见数学运算的内置函数,或者您可以提供自己的自定义函数。
虽然 Expression
类仅适用于 Double
值,但 AnyExpression 使用了一种称为 NaN boxing 的技术,通过 IEEE 浮点规范中未使用的位模式来引用任意数据。
Expression
类封装在单个文件中,并且所有公共内容都带有前缀或命名空间,因此您只需将 Expression.swift
文件拖到您的项目中即可使用它。如果您希望使用 AnyExpression 扩展,那么也请包含 AnyExpression.swift
文件。
如果您愿意,可以使用一个框架导入,该框架同时包含 Expression
和 AnyExpression
类。您可以手动拖放安装,也可以使用 CocoaPods、Carthage 或 Swift Package Manager 自动安装。
要使用 CocoaPods 安装 Expression,请将以下内容添加到您的 Podfile 中:
pod 'Expression', '~> 0.13'
要使用 Carthage 安装,请将以下内容添加到您的 Cartfile 中:
github "nicklockwood/Expression" ~> 0.13
要使用 Swift Package Manager 安装,请将以下内容添加到您的 dependencies:
部分的 Package.swift 文件中:
.package(url: "https://github.com/nicklockwood/Expression.git", .upToNextMinor(from: "0.13.0")),
要开始使用 Expression,请在您的文件顶部导入 Expression 模块:
import Expression
注意: 在 iOS 18 / macOS 15 中,Apple 添加了一个 Foundation.Expression
类,如果您在文件中导入 Foundation,它会与 Expression 库中定义的 Expression
冲突。为了解决这个问题,请改用 NumericExpression
别名。或者,如果您愿意,您可以通过在项目中本地写入以下内容来使用 NumericExpression
本地覆盖 Apple 的 Expression
:
typealias Expression = NumericExpression
您可以通过传递包含表达式的字符串以及(可选)以下任何或全部内容来创建 Expression
实例:
SymbolEvaluator
函数的字典 - 这允许您提供自定义变量、函数或运算符然后,您可以通过调用 evaluate()
方法来计算结果。
注意: 给定 Expression
或 AnyExpression
实例的 evaluate()
函数是线程安全的,这意味着您可以从多个线程并发调用它。
默认情况下,Expression 已经实现了大多数标准数学函数和运算符,因此如果您的应用程序需要支持其他函数或变量,您只需要提供自定义符号字典。您可以混合和匹配实现,因此如果您有一些自定义常量或数组以及一些自定义函数或运算符,您可以提供单独的常量和符号字典。
以下是一些示例:
// Basic usage:
// Only using built-in math functions
let expression = Expression("5 + 6")
let result = try expression.evaluate() // 11
// Intermediate usage:
// Custom constants, variables and and functions
var bar = 7 // variable
let expression = Expression("foo + bar + baz(5) + rnd()", constants: [
"foo": 5,
], symbols: [
.variable("bar"): { _ in bar },
.function("baz", arity: 1): { args in args[0] + 1 },
.function("rnd", arity: 0): { _ in arc4random() },
])
let result = try expression.evaluate()
// Advanced usage:
// Using the alternative constructor to dynamically hex color literals
let hexColor = "#FF0000FF" // rrggbbaa
let expression = Expression(hexColor, pureSymbols: { symbol in
if case .variable(let name) = symbol, name.hasPrefix("#") { {
let hex = String(name.characters.dropFirst())
guard let value = Double("0x" + hex) else {
return { _ in throw Expression.Error.message("Malformed color constant #\(hex)") }
}
return { _ in value }
}
return nil // pass to default evaluator
})
let color: UIColor = {
let rgba = UInt32(try expression.evaluate())
let red = CGFloat((rgba & 0xFF000000) >> 24) / 255
let green = CGFloat((rgba & 0x00FF0000) >> 16) / 255
let blue = CGFloat((rgba & 0x0000FF00) >> 8) / 255
let alpha = CGFloat((rgba & 0x000000FF) >> 0) / 255
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}()
请注意,evaluate()
函数可能会抛出错误。如果表达式格式错误,或者它引用了未知符号,则在评估期间会自动抛出错误。您的自定义符号实现也可能会抛出特定于应用程序的错误,如上面的颜色示例所示。
对于像第一个示例这样简单的硬编码表达式,不可能抛出错误,但是如果您接受用户输入的表达式,则必须始终确保捕获并处理错误。Expression 生成的错误消息是详细且易于人类阅读的(但未本地化)。
do {
let result = try expression.evaluate()
print("Result: \(result)")
} catch {
print("Error: \(error)")
}
当使用 constants
、arrays
和 symbols
字典时,错误消息生成由 Expression 库自动处理。如果您需要支持动态符号解码(如之前的十六进制颜色示例中),您可以使用 init(impureSymbols:pureSymbols)
初始化器,它稍微复杂一些。
init(impureSymbols:pureSymbols)
初始化器接受一对查找函数,这些函数接受一个 Symbol
并返回一个 SymbolEvaluator
函数。此接口非常强大,因为它允许您动态解析符号(例如颜色示例中的十六进制颜色常量),而无需预先创建所有可能值的字典。
对于每个符号,您的查找函数可以返回 SymbolEvaluator
函数或 nil。如果您不识别某个符号,则应返回 nil,以便可以由默认评估器处理。如果两个查找函数都不匹配该符号,并且它不是标准数学或布尔函数之一,则 evaluate()
将抛出错误。
在某些情况下,您可能识别出某个符号,但确定它是错误的,这是一个提供比 Expression 默认生成的错误消息更具体的错误消息的机会。以下示例匹配了一个 arity 为 1 的函数 bar
(意味着它接受一个参数)。这将仅匹配对接受单个参数的 bar
的调用,并将忽略对零个或多个参数的调用。
switch symbol {
case .function("bar", arity: 1):
return { args in args[0] + 1 }
default:
return nil // pass to default evaluator
}
由于 bar
是一个自定义函数,我们知道它应该只接受一个参数,因此如果使用错误数量的参数调用它,抛出错误比返回 nil 来指示该函数不存在更有帮助。这看起来像这样:
switch symbol {
case .function("bar", let arity):
guard arity == 1 else {
return { _ in throw Expression.Error.message("function bar expects 1 argument") }
}
return { arg in args[0] + 1 }
default:
return nil // pass to default evaluator
}
注意: 较新版本的 Expression 无论如何都可以正确报告像这样的微不足道的 arity 错误,所以这是一个稍微人为的例子,但这种方法可能对其他类型的错误有用,例如当参数超出范围或类型错误时。
表达式由数字字面量和符号混合而成,符号是 Expression.Symbol
枚举类型的实例。内置的数学和布尔库定义了许多标准符号,但您可以自由定义自己的符号。
Expression.Symbol
枚举支持以下符号类型:
.variable(String)
这是一个字母数字标识符,表示表达式中的常量或变量。标识符可以是字母和数字的任意序列,以字母、下划线 (_)、美元符号 ($)、at 符号 (@) 或井号/磅符号 (#) 开头。
与 Swift 类似,Expression 允许在标识符中使用 Unicode 字符,例如表情符号和科学符号。与 Swift 不同,Expression 的标识符也可能包含句点 (.) 作为分隔符,这对于命名空间很有用(如布局示例应用程序中所示)。
解析器还接受带引号的字符串作为标识符。可以使用单引号、双引号或反引号。由于 Expression
仅处理数值,因此将这些字符串标识符映射到数字取决于您的应用程序(如果您使用 AnyExpression,则会自动处理)。
与常规标识符不同,带引号的标识符可以包含任何 Unicode 字符,包括空格。换行符、引号和其他特殊字符可以使用反斜杠 () 转义。转义序列会为您解码,但外引号会被保留,因此您可以区分字符串和其他标识符。
最后,允许不带引号的标识符以单引号 (') 结尾,因为这是数学中用于指示修改值的常用表示法。标识符中任何其他位置的引号都将被视为名称的结尾。
要验证给定的字符串是否可以安全地用作标识符,您可以使用 Expression.isValidIdentifier()
方法。
.infix(String)
.prefix(String)
.postfix(String)
这些符号表示运算符。运算符可以是一个或多个字符长,并且可以包含几乎任何不与有效标识符名称冲突的符号,但有一些注意事项:
逗号 (,) 本身是一个有效的运算符,但不能构成更长字符序列的一部分
括号字符 [
、(
、{
及其对应字符是保留的,不能用作运算符
运算符可以以一个或多个点 (.) 或连字符 (-) 开头,但点或连字符不能出现在任何其他字符之后。以下是被允许的:
...
, ..<
, .
, -
, --
, -=
, -->
但以下是不允许的:
+.
, >.<
, *-
, -+-
, <--
, .-
, -.
要验证给定的字符序列是否可以安全地用作运算符,您可以使用 Expression.isValidOperator()
方法。
您可以使用后/前缀变体重载现有的中缀运算符,反之亦然。消除歧义取决于运算符周围的空格(这与 Swift 使用的方法相同)。
任何有效的标识符也可以用作中缀运算符,方法是将其放在两个操作数之间;或者用作后缀运算符,方法是将其放在操作数之后。例如,在处理距离逻辑时,您可以将 m
和 cm
定义为后缀运算符,或者使用 and
作为布尔运算符 &&
的更易读的替代方案。
运算符优先级遵循标准的 BODMAS 顺序,乘法/除法优先于加法/减法。前缀运算符优先于后缀运算符,后缀运算符优先于中缀运算符。目前无法为自定义运算符指定优先级 - 它们都具有与加法/减法相同的优先级。
支持标准布尔运算符,并遵循正常的优先级规则,但需要注意的是,不支持短路(右侧参数可能不会被评估,具体取决于左侧)。解析器还将识别三元运算符 ?:
,将 a ? b : c
视为具有三个参数的单个中缀运算符。
.function(String, arity: Arity)
函数符号使用名称和 Arity
定义,Arity
是它期望的参数数量。Arity
类型是一个枚举,可以设置为 exactly(Int)
或 atLeast(Int)
用于可变参数函数。给定的函数名称可以使用不同的 arity 重载多次。
注意: Arity
符合 ExpressibleByIntegerLiteral
,因此对于固定 arity 函数,您可以直接写 .function("foo", arity: 2)
而不是 .function("foo", arity: .exactly(2))
通过使用函数名称,后跟括号中以逗号分隔的参数序列来调用函数。如果参数计数与任何指定的 arity 变体不匹配,则会抛出 arityError
。
由于函数符号必须具有名称,因此无法直接在表达式中使用匿名函数(例如,存储在变量中或由另一个函数返回的函数)。
但是,如果实现函数调用运算符 .infix("()")
,则对此有语法支持,该运算符接受一个或多个参数,第一个参数被视为要调用的函数。这在 Expression
(其中值都是数值)中的用途有限,但 AnyExpression 使用此方法来提供对[匿名函数(#anonymous-functions)的完全支持。
.array(String)
数组符号表示可以通过索引访问的值序列。在表达式中,通过使用数组名称后跟方括号中的索引参数来引用数组符号。
将数组与 Expression
一起使用的最简单方法是通过 arrays
初始化器参数传入常量数组值。对于可变数组,您可以通过 symbols
参数返回 .array()
符号实现。
Expression 还支持 Swift 样式的数组字面量语法,如 [1, 2, 3]
和任意表达式的下标,如 (a + b)[c]
。数组字面量映射到数组字面量构造函数符号 .function("[]", arity: .any)
,下标映射到数组下标运算符 .infix("[]")
。
由于 Expression
无法处理非数值类型,因此数组字面量构造函数和数组下标运算符在 Expression
中都没有默认实现,但是这两种都在 AnyExpression 的标准符号库中实现。
默认情况下,Expression 缓存已解析的表达式。表达式缓存的大小没有限制。在大多数应用程序中,这不太可能成为问题 - 表达式很小,即使您能想象到的最复杂的表达式也可能远小于 1KB,因此需要很多表达式才会导致内存压力 - 但是,如果出于某种原因您确实需要回收缓存表达式使用的内存,您可以通过调用 flushCache()
方法来完成。
Expression.flushCache())
flushCache()
方法接受一个可选的字符串参数,因此您也可以从缓存中删除特定的表达式,而无需清除其他表达式。
Expression.flushCache(for: "foo + bar"))
如果您希望对缓存进行更精细的控制,您可以预先解析表达式而不进行缓存,然后从预解析的表达式创建 Expression 实例,如下所示:
let expressionString = "foo + bar"
let parsedExpression = Expression.parse(expressionString, usingCache: false)
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])
通过在上面的代码中将 usingCache
参数设置为 false
,我们避免将表达式添加到全局缓存。您也可以自由地通过存储解析后的表达式并重复使用它来实现自己的缓存,这在某些情况下可能比内置缓存更有效(例如,通过避免线程管理,如果您的代码是单线程的)。
Expression.parse()
方法的第二个变体接受 String.UnicodeScalarView.SubSequence
和可选的终止分隔符字符串列表。这可以用于匹配嵌入在较长字符串中的表达式,并将字符序列的 startIndex
留在正确的位置,以便在到达分隔符后继续解析。
let expressionString = "lorem ipsum {foo + bar} dolor sit"
var characters = String.UnicodeScalarView.SubSequence(expression.unicodeScalars)
while characters.popFirst() != "{" {} // Read up to start of expression
let parsedExpression = Expression.parse(&characters, upTo: "}")
let expression = Expression(parsedExpression, constants: ["foo": 4, "bar": 5])
默认情况下,表达式在可能的情况下会进行优化,以使评估更有效率。常见的优化包括用字面量值替换常量,以及当所有参数都是常量时,用结果替换纯函数或运算符。
优化器会减少评估时间,但会增加初始化时间,对于只评估一两次的表达式,这种权衡可能不值得,在这种情况下,您可以使用 options
参数禁用优化。
let expression = Expression("foo + bar", options: .noOptimize, ...)
另一方面,如果您的表达式要被评估数百或数千次,您将需要充分利用优化器来提高应用程序的性能。为了确保您充分利用 Expression 的优化器,请遵循以下准则:
始终通过 constants
或 arrays
参数传递常量值,而不是作为 symbols
字典中的变量。常量值可以内联,而变量每次评估函数时都必须重新计算,以防它们发生更改。
如果您的自定义函数和运算符都是纯函数 - 即它们没有副作用,并且对于给定的参数值集始终返回相同的输出 - 那么您应该为您的表达式设置 pureSymbols
选项。此选项告诉优化器,如果 symbols
字典中的所有函数或运算符的所有参数都是常量,则可以安全地内联它们。请注意,pureSymbols
选项不影响变量或数组符号,它们永远不会内联。
如果您的表达式可能包含常量值,但并非所有可能的值都可以预先计算 - 例如,十六进制颜色示例中的编码值,或必须在深层对象图中查找的任意键路径 - 您可以使用 init(pureSymbols:)
初始化器来解码或查找仅所需的特定值。
默认情况下,Expression 支持许多基本的数学函数、运算符和常量,这些函数、运算符和常量通常很有用,并且独立于任何特定的应用程序。
如果您使用自定义符号字典,您可以覆盖任何默认符号,或使用不同数量的参数(arity)重载默认函数。标准库中您未显式覆盖的任何符号仍然可用。
要显式禁用标准库中的单个符号,您可以覆盖它们并抛出异常:
let expression = Expression("pow(2,3)", symbols: [
.function("pow", arity: 2): { _ in throw Expression.Error.undefinedSymbol(.function("pow", arity: 2)) }
])
try expression.evaluate() // this will throw an error because pow() has been undefined
如果您使用 init(impureSymbols:pureSymbols:)
初始化器,您可以通过为无法识别的符号返回 nil
来回退到标准库函数和运算符。如果您不想在表达式中提供对标准库函数的访问权限,请为无法识别的符号抛出错误,而不是返回 nil
。
let expression = Expression("3 + 4", pureSymbols: { symbol in
switch symbol {
case .function("foo", arity: 1):
return { args in args[0] + 1 }
default:
return { _ in throw Expression.Error.undefinedSymbol(symbol) }
}
})
try expression.evaluate() // this will throw an error because no standard library operators are supported, including +
以下是当前支持的数学符号:
常量
pi
中缀运算符
+ - / * %
前缀运算符
-
函数
// Unary functions
sqrt(x)
floor(x)
ceil(x)
round(x)
cos(x)
acos(x)
sin(x)
asin(x)
tan(x)
atan(x)
abs(x)
log(x)
// Binary functions
pow(x,y)
atan2(x,y)
mod(x,y)
// Variadic functions
max(x,y,[...])
min(x,y,[...])
除了数学之外,Expression 还支持布尔逻辑,遵循 C 约定,即零为假,任何非零值都为真。默认情况下未启用标准布尔符号,但您可以使用 .boolSymbols
选项启用它们:
let expression = Expression("foo ? bar : baz", options: .boolSymbols, ...)
与数学符号一样,可以使用 symbols
初始化器参数为给定的表达式单独覆盖或禁用所有标准布尔运算符。
以下是当前支持的布尔符号:
常量
true
false
中缀运算符
==
!=
>
>=
<
<=
&&
||
前缀运算符
!
三元运算符
?:
AnyExpression
的使用方式几乎与 Expression
类完全相同,但以下例外情况除外:
AnyExpression
的 SymbolEvaluator
函数接受和返回 Any
而不是 Double
AnyExpression
时,默认情况下启用布尔符号和运算符AnyExpression
构造函数没有单独的 arrays
参数。如果您希望传递数组或字典常量,您可以像任何其他值类型一样将其添加到 constants
字典中AnyExpression
支持 [匿名函数[(#anonymous-functions),可以是 Expression.SymbolEvaluator
或 AnyExpression.SymbolEvaluator
类型的任何值Expression.SymbolEvaluator
或 AnyExpression.SymbolEvaluator
函数传递到 AnyExpression
中,这些函数的行为将与普通函数符号完全相同您可以按如下方式创建和评估 AnyExpression
实例:
let expression = AnyExpression("'hello' + 'world'")
let result: String = try expression.evaluate() // 'helloworld'
请注意字符串字面量使用单引号 (')。AnyExpression
支持单引号或双引号用于字符串字面量。两者之间没有区别,只是单引号不需要在 Swift 字符串字面量中转义。
由于 AnyExpression
的 evaluate()
方法具有泛型返回类型,因此您需要告诉它期望的类型。在上面的示例中,我们通过为 result
变量指定显式类型来做到这一点,但您也可以通过使用 as
运算符(不带 ! 或 ?)来做到这一点:
let result = try expression.evaluate() as String
evaluate
函数在类型方面具有一定的内置宽松性,因此,例如,如果表达式返回布尔值,但您指定 Double
作为期望的类型,则类型将自动转换,但如果它返回字符串,而您要求 Bool
,则会抛出类型不匹配错误。
当前支持的自动转换是:
除了添加对字符串字面量的支持之外,AnyExpression
还使用一些额外的符号扩展了 Expression
的标准库,用于处理 Optionals 和空值:
nil
- 空字面量??
- 空值合并运算符Optional 解包是自动的,因此目前不需要后缀 ?
或 !
运算符。nil
(又名 Optional.none
)和 NSNull
都以相同的方式处理,以避免在使用 JSON 或 Objective-C API 数据时混淆。
像 ==
和 !=
这样的比较运算符也被扩展为可用于任何 Hashable
类型,并且 +
可以用于字符串连接,如上面的示例所示。
对于 array
符号,AnyExpression
可以使用任何 Hashable
类型作为索引。这意味着 AnyExpression
可以处理 Dictionary
值以及 Array
和 ArraySlice
。
如上所述,AnyExpression
支持使用带引号的字符串字面量,用单引号 (') 或双引号 (") 分隔。字符串内的特殊字符可以使用反斜杠 () 转义。
AnyExpression
支持在方括号中定义的数组字面量,例如 [1, 2, 3]
或 ['foo', 'bar', 'baz']
。数组字面量可以包含值类型和/或子表达式的混合。
您还可以使用 ..<
和 ...
语法创建范围字面量。支持闭合范围、半开范围和部分范围。范围适用于 Int
或 String.Index
值,并且可以与下标语法结合使用,用于切片数组和字符串。
除了普通的命名函数符号之外,AnyExpression
还支持调用匿名函数,匿名函数是 Expression.SymbolEvaluator
或 AnyExpression.SymbolEvaluator
类型的值,可以存储在常量中或从子表达式返回。
您可以通过使用常量值而不是 .function()
符号将匿名函数传递到 AnyExpression
中,但请注意,这种方法不允许您选择按 arity 重载具有相同名称的函数。
与函数符号不同,匿名函数不支持重载,但您可以使用函数体内的 switch 语句来实现不同的行为,具体取决于参数的数量。如果传递了不支持的参数数量,您还应该抛出 arityMismatch
错误,因为这无法自动检测到,例如:
let bar = { (args: [Any] throws -> Any in
switch args.count {
case 1:
// behavior 1
case 2:
// behavior 2
default:
throw Expression.Error.arityMismatch(.function("bar", arity: 2))
}
}
// static function foo returns anonymous function bar, which is called in the expression
let expression = AnyExpression("foo()(2)", symbols: [
.function("foo"): { _ in bar }
])
注意: 匿名函数被假定为不纯函数,因此它们永远不符合内联条件,无论您是否使用 pureSymbols
选项。
AnyExpression 在 Linux 上工作,但有以下注意事项:
AnyExpression
不支持 NSString
桥接。如果您想将 AnyExpression
与 NSString
值一起使用,则必须在评估前后手动将它们转换为 String
。基准测试应用程序分别使用 Expression
、AnyExpression
、NSExpression
和 JavaScriptCore 的 JSContext
运行一组测试表达式。然后,它会记录解析和评估表达式所需的时间,并在表格中显示结果。
时间以微秒 (µs) 或毫秒为单位显示。每个类别中最快的结果以绿色显示,最慢的结果以红色显示。
为了获得准确的结果,基准测试应用程序应在真实设备上的发布模式下运行。您可以下拉表格以刷新测试结果。测试在主线程上运行,因此如果显示在刷新时短暂锁定,请不要感到惊讶。
在我自己的测试中,Expression 始终是最快的实现,而 JavaScriptCore 始终是最慢的实现,无论是对于初始设置还是对于上下文初始化后的评估。
关于这个例子没什么好说的。这是一个计算器。您可以在其中键入数学表达式,它将评估它们并生成结果(或者如果键入的内容无效,则会生成错误)。
颜色示例演示了如何使用 AnyExpression
创建(大部分)符合 CSS 标准的颜色解析器。它接受包含命名颜色、十六进制颜色或 rgb()
函数调用的字符串,并返回 UIColor 对象。
有趣的地方来了:布局示例演示了一个粗糙但可用的布局系统,该系统支持视图坐标的任意表达式。
它在概念上类似于 AutoLayout,但有一些重要的区别:
示例视图的默认布局值已在 Storyboard 中设置,但您可以通过点击视图并键入新值在应用程序中实时编辑它们。
以下是一些需要注意的事项:
top
、left
、width
和 height
表达式,用于定义其在屏幕上的坐标。key
(类似于标签,但基于字符串),可用于从另一个视图引用其属性。width
和 height
属性可以使用 auto
变量,该变量对于普通视图没有用处,但可以与文本标签一起使用,以根据文本量计算给定宽度的最佳高度。width
或 height
属性。min()
和 max()
之类的函数来确保相对值不会高于或低于固定阈值。这只是一个玩具示例,但如果您喜欢这个概念,请查看 Github 上的 Layout 框架,它将这个想法提升到了一个新的水平。
Expression REPL(读取-评估-打印循环)是一个用于评估表达式的 Mac 命令行工具。与计算器示例不同,REPL 基于 AnyExpression
,因此它允许使用任何可以表示为 Expression 语法中的字面量的类型 - 而不仅仅是数字。
您在 REPL 中键入的每一行都将独立评估。要在表达式之间共享值,您可以使用标识符名称后跟 =
然后是表达式来定义变量,例如:
foo = (5 + 6) + 7
然后,命名变量(在本例中为“foo”)可在后续表达式中使用。
Expression 框架主要是 Nick Lockwood 的工作成果。
(贡献者完整列表)