jaeggerr/evaluator
Evaluator is a Swift library that allows parsing and evaluating expressions provided as strings. It was initially inspired by [Expression](https://github.com/nicklockwood/Expression), a library that was incredibly useful but lacked support for short-circuit operations, which was
Overview
Evaluator is a Swift library that allows parsing and evaluating expressions provided as strings. It was initially inspired by Expression, a library that was incredibly useful but lacked support for short-circuit operations, which was a critical requirement for my use case. Additionally, I wanted more robust type handling instead of relying heavily on Any.
Evaluator is cross-platform and has no dependencies, making it lightweight and easy to integrate into any Swift project.
Features
- Supports mathematical operations (
+,-,*,/,%) - Supports logical operations (
&&,||,!) - Supports comparison operations (
==,!=,<,<=,>,>=) - Supports bitwise operations (
&,|) - Supports custom variables prefixed with
#or$, which accept_and. - Supports custom functions with dynamic arguments
- Supports short-circuit evaluation for logical operations
- Supports arrays within variables
- Provides strong type conversion using custom Swift protocols
- Works with custom structs and classes via protocol conformance
Installation
Evaluator is available through Swift Package Manager (SPM). To install it, add the following to your Package.swift:
.package(url: "https://github.com/jaeggerr/evaluator.git", from: "1.1.2")Or, if using Xcode, go to File > Swift Packages > Add Package Dependency and enter the repository URL.
Usage
Basic Expressions
Here are some examples of expressions that Evaluator can handle:
try ExpressionEvaluator.evaluate(expression: "5 + 3 * 2") // Returns 11
try ExpressionEvaluator.evaluate(expression: "(10 - 4) / 2") // Returns 3.0
try ExpressionEvaluator.evaluate(expression: "true && false") // Returns false
try ExpressionEvaluator.evaluate(expression: "5 > 3 && 10 < 20") // Returns trueVariables
Variables must be prefixed with # or $. They support underscores (_) and dots (.) to allow structured names.
let result = try ExpressionEvaluator.evaluate(expression: "$my_var + 10", variables: { name in
switch name {
case "$my_var": return 5
default: throw ExpressionError.variableNotFound(name)
}
})
// result == 15Using Dotted Notation
let result = try ExpressionEvaluator.evaluate(expression: "#user.score * 2", variables: { name in
switch name {
case "#user.score": return 10
default: throw ExpressionError.variableNotFound(name)
}
})
// result == 20Arrays
Evaluator supports arrays in variables, allowing indexed access.
let result = try ExpressionEvaluator.evaluate(expression: "#values[2] + 1", variables: { name in
switch name {
case "#values": return [10, 20, 30, 40]
default: throw ExpressionError.variableNotFound(name)
}
})
// result == 31Supported Functions
Evaluator provides a set of built-in mathematical functions, which can be used in expressions:
| Function | Description | Example | |-----------|------------|---------| | sqrt(x) | Square root | sqrt(4) = 2.0 | | floor(x) | Rounds down to the nearest integer | floor(3.7) = 3.0 | | ceil(x) | Rounds up to the nearest integer | ceil(3.2) = 4.0 | | round(x) | Rounds to the nearest integer | round(3.5) = 4.0 | | cos(x) | Cosine (radians) | cos(0) = 1.0 | | acos(x) | Arc cosine | acos(1) = 0.0 | | sin(x) | Sine (radians) | sin(0) = 0.0 | | asin(x) | Arc sine | asin(0) = 0.0 | | tan(x) | Tangent (radians) | tan(0) = 0.0 | | atan(x) | Arc tangent | atan(1) = π/4 | | log(x) | Natural logarithm | log(e) = 1.0 | | abs(x) | Absolute value | abs(-5) = 5.0 | | pow(x, y) | Power function | pow(2, 3) = 8.0 | | atan2(y, x) | Two-argument arc tangent | atan2(1, 1) = π/4 | | max(a, b, ...) | Maximum value | max(3, 5, 2) = 5.0 | | min(a, b, ...) | Minimum value | min(3, 5, 2) = 2.0 | | exists(name) | Returns true if the variable is defined | exists("foo") = true |
Overriding Built-in Functions
You can override any built-in function by defining a custom function with the same name. Custom functions take precedence over built-in functions.
let customFunctions: ExpressionEvaluator.FunctionResolver = { name, args in
switch name {
case "sqrt":
return "Overridden sqrt function!"
default:
throw ExpressionError.functionNotFound(name)
}
}
let result = try ExpressionEvaluator.evaluate(expression: "sqrt(4)", functions: customFunctions)
print(result) // "Overridden sqrt function!"Custom Functions
Evaluator allows defining custom functions that can be called within an expression.
let result = try ExpressionEvaluator.evaluate(expression: "sum(1, 2, 3)", functions: { name, args in
switch name {
case "sum": return args.reduce(0, { ($0 as! Int) + ($1 as! Int) })
default: throw ExpressionError.functionNotFound(name)
}
})
// result == 6Type Conversion
Evaluator supports custom types via protocol conformance. If an unknown type is encountered, it tries to convert it into a suitable type.
Custom Struct Example
If you have a custom struct IntWrapper, you can make it compatible by conforming to EvaluatorIntConvertible:
struct IntWrapper: EvaluatorIntConvertible {
let value: Int
func convertToInt() throws -> Int {
return value
}
}Then, use it in an expression:
let result = try ExpressionEvaluator.evaluate(expression: "$wrapped + 2", variables: { name in
switch name {
case "$wrapped": return IntWrapper(value: 8)
default: throw ExpressionError.variableNotFound(name)
}
})
// result == 10Generic Return Type Handling
The return type of evaluate is determined by the generic parameter T.
let intResult: Int = try ExpressionEvaluator.evaluate(expression: "5 + 2") // Returns Int
let doubleResult: Double = try ExpressionEvaluator.evaluate(expression: "5 / 2") // Returns Double
let boolResult: Bool = try ExpressionEvaluator.evaluate(expression: "5 > 2") // Returns BoolSupported return types:
Int,Int8,Int16,Int32,Int64UInt8,UInt16,UInt32,UInt64Double,FloatBoolString
Custom Comparators
Evaluator allows defining custom comparison behavior via the ComparatorResolver. This is useful for handling user-defined types.
Example: Comparing Custom Structs
struct CustomObject {
let score: Int
}
let comparator: ExpressionEvaluator.ComparatorResolver = { lhs, rhs, op in
guard let lhsObj = lhs as? CustomObject, let rhsObj = rhs as? CustomObject else {
throw ExpressionError.typeMismatch("Cannot compare these types")
}
return op.compare(lhs: lhsObj.score, rhs: rhsObj.score)
}Then, pass it into evaluate:
let result = try ExpressionEvaluator.evaluate(expression: "$obj1 > $obj2", variables: { name in
switch name {
case "$obj1": return CustomObject(score: 50)
case "$obj2": return CustomObject(score: 30)
default: throw ExpressionError.variableNotFound(name)
}
}, comparator: comparator)
// result == trueFunction Argument Helper
A helper struct ArgumentsHelper has been added to simplify function argument parsing. This helps enforce correct argument types and arities when defining custom functions.
Usage
let functions: ExpressionEvaluator.FunctionResolver = {
switch $0 {
case "multiply":
let argsHelper = ArgumentsHelper($1)
let a: Double = try argsHelper.get(0)
let b: Double = try argsHelper.get(1)
return a * b
default: throw ExpressionError.functionNotFound($0)
}
}
let result: Double = try ExpressionEvaluator.evaluate(expression: "multiply(3, 4)", functions: functions)
print(result) // Output: 12.0Features of ArgumentsHelper
- Ensures correct number of arguments with
ensureArity() - Retrieves arguments safely with type checks
- Supports
Double,Int,String,Bool, and generic types
License
This library is licensed under a standard open-source license that allows modifications and closed-source usage but does not permit reselling the library.
Contributions
Contributions and suggestions are welcome on a case-by-case basis.
Unit Tests
Unit tests are included to ensure correctness and reliability.
Package Metadata
Repository: jaeggerr/evaluator
Default branch: main
README: README.md