Expression
A cross-platform Swift library for evaluating mathematical expressions at runtime
Install / Use
/learn @nicklockwood/ExpressionREADME
Introduction
What?
Expression is a Swift framework for evaluating expressions at runtime on Apple and Linux platforms
The Expression library is split into two parts:
-
The
Expressionclass, which is similar to Foundation's built-inNSExpressionclass, but with better support for custom operators, a more Swift-friendly API, and superior performance. -
AnyExpression, an extension of Expression that handles arbitrary types and provides additional built-in support for common types such asString,Dictionary,ArrayandOptional.
Why?
There are many situations where it is useful to be able to evaluate a simple expression at runtime. Some are demonstrated in the example apps included with the library:
- A scientific calculator
- A CSS-style color string parser
- A basic layout engine, similar to AutoLayout
But there are other possible applications, e.g.
- A spreadsheet app
- Configuration (e.g. using expressions in a config file to avoid data duplication)
- The basis for simple scripting language
(If you find any other use cases, let me know and I'll add them)
Normally these kind of calculations would involve embedding a heavyweight interpreted language such as JavaScript or Lua into your app. Expression avoids that overhead, and is also more secure as it reduces the risk of arbitrary code injection or crashes due to infinite loops, buffer overflows, etc.
Expression is fast, lightweight, well-tested, and written entirely in Swift. It is substantially faster than using JavaScriptCore for evaluating simple expressions (see the Benchmark app for a scientific comparison.
How?
Expression works by parsing an expression string into a tree of symbols, which can then be evaluated at runtime. Each symbol maps to a Swift closure (function) which is executed during evaluation. There are built-in functions representing common math operations, or you can provide your own custom ones.
Although the Expression class only works with Double values, AnyExpression uses a technique called NaN boxing to reference arbitrary data via the unused bit patterns in the IEEE floating point specification.
Usage
Installation
The Expression class is encapsulated in a single file, and everything public is prefixed or name-spaced, so you can simply drag the Expression.swift file into your project to use it. If you wish to use the AnyExpression extension then include the AnyExpression.swift file as well.
If you prefer, there's a framework that you can import which includes both the Expression and AnyExpression classes. You can install this manually by drag and drop, or automatically using CocoaPods, Carthage, or Swift Package Manager.
To install Expression using CocoaPods, add the following to your Podfile:
pod 'Expression', '~> 0.13'
To install using Carthage, add this to your Cartfile:
github "nicklockwood/Expression" ~> 0.13
To install using Swift Package Manager, add this to the dependencies: section in your Package.swift file:
.package(url: "https://github.com/nicklockwood/Expression.git", .upToNextMinor(from: "0.13.0")),
Integration
To start using Expression, import the Expression module at the top of your file:
import Expression
Note: In iOS 18 / macOS 15 Apple added a Foundation.Expression class that clashes with the Expression defined in the Expression library if you are importing Foundation in your file. To work around this, use the NumericExpression alias instead. Or if you prefer, you can override Apple's Expression with NumericExpression locally in your project by writing:
typealias Expression = NumericExpression
You create an Expression instance by passing a string containing your expression, and (optionally) any or all of the following:
- A set of configuration options - used to enabled or disable certain features
- A dictionary of named constants - this is the simplest way to specify predefined constants
- A dictionary of named array constants - this is the simplest way to specify predefined arrays of related values
- A dictionary of symbols and
SymbolEvaluatorfunctions - this allows you to provide custom variables, functions or operators
You can then calculate the result by calling the evaluate() method.
Note: The evaluate() function for a given Expression or AnyExpression instance is thread-safe, meaning that you can call it concurrently from multiple threads.
By default, Expression already implements most standard math functions and operators, so you only need to provide a custom symbol dictionary if your app needs to support additional functions or variables. You can mix and match implementations, so if you have some custom constants or arrays and some custom functions or operators, you can provide separate constants and symbols dictionaries.
Here are some examples:
// 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)
}()
Note that the evaluate() function may throw an error. An error will be thrown automatically during evaluation if the expression was malformed, or if it references an unknown symbol. Your custom symbol implementations may also throw application-specific errors, as in the colors example above.
For a simple, hard-coded expression like the first example there is no possibility of an error being thrown, but if you accept user-entered expressions, you must always ensure that you catch and handle errors. The error messages produced by Expression are detailed and human-readable (but not localized).
do {
let result = try expression.evaluate()
print("Result: \(result)")
} catch {
print("Error: \(error)")
}
When using the constants, arrays and symbols dictionaries, error message generation is handled automatically by the Expression library. If you need to support dynamic symbol decoding (as in the hex color example earlier), you can use the init(impureSymbols:pureSymbols) initializer, which is a little bit more complex.
The init(impureSymbols:pureSymbols) initializer accepts a pair of lookup functions that take a Symbol and return a SymbolEvaluator function. This interface is very powerful because it allows you to dynamically resolve symbols (such as the hex color constants in the colors example) without needing to create a dictionary of all possible values in advance.
For each symbol, your lookup functions can return either a SymbolEvaluator function, or nil. If you do not recognize a symbol, you should return nil so that it can be handled by the default evaluator. If neither lo
Related Skills
node-connect
338.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.4kCommit, push, and open a PR
