├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── Benchmarks
├── BenchmarkUtils.swift
├── Benchmarks.swift
└── PerformanceTests.swift
├── CHANGELOG.md
├── Examples
├── Benchmark
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── ViewController.swift
├── Calculator
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── ViewController.swift
├── Colors
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── ColorLabel.swift
│ ├── Info.plist
│ ├── UIColor+Expression.swift
│ └── ViewController.swift
├── Layout
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── View+Layout.swift
│ └── ViewController.swift
└── REPL
│ └── main.swift
├── Expression.podspec.json
├── Expression.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcshareddata
│ ├── xcbaselines
│ ├── 01DD38BB1ED630CB00F17F46.xcbaseline
│ │ ├── A544E583-C966-4A17-B7DE-008EFD1B4FC2.plist
│ │ └── Info.plist
│ └── 01DD38D51ED63B1E00F17F46.xcbaseline
│ │ ├── ECC5D8A7-1C11-4DDD-9972-84D3DA20302D.plist
│ │ └── Info.plist
│ └── xcschemes
│ ├── Benchmark.xcscheme
│ ├── Calculator.xcscheme
│ ├── Colors.xcscheme
│ ├── DebugPerformance.xcscheme
│ ├── Expression (Mac).xcscheme
│ ├── Expression (iOS).xcscheme
│ ├── Layout.xcscheme
│ └── ReleasePerformance.xcscheme
├── LICENSE.md
├── Package.swift
├── README.md
├── Sources
├── AnyExpression.swift
├── Expression.h
├── Expression.swift
└── Info.plist
└── Tests
├── AnyExpressionTests.swift
├── ExpressionTests.swift
├── Info.plist
└── MetadataTests.swift
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | pull_request:
6 | jobs:
7 | macos:
8 | runs-on: macos-latest
9 | strategy:
10 | matrix:
11 | xcode: ["15.3", "14.3.1"]
12 | steps:
13 | - uses: maxim-lobanov/setup-xcode@v1
14 | with:
15 | xcode-version: ${{ matrix.xcode }}
16 | - name: Checkout
17 | uses: actions/checkout@v3
18 | - name: Build and Test
19 | run:
20 | xcodebuild -scheme "Expression (Mac)" -sdk macosx clean build test
21 | - name: Codecov
22 | uses: codecov/codecov-action@v2
23 | with:
24 | # the token is optional for a public repo, but including it anyway
25 | token: f835f552-0734-4266-b5c6-0c184c368bd9
26 | env_vars: MD_APPLE_SDK_ROOT,RUNNER_OS,RUNNER_ARCH
27 | linux:
28 | runs-on: ubuntu-latest
29 | strategy:
30 | fail-fast: false
31 | matrix:
32 | swiftver:
33 | - swift:5.2
34 | - swift:5.9
35 | swiftos:
36 | - focal
37 | container:
38 | image: ${{ format('{0}-{1}', matrix.swiftver, matrix.swiftos) }}
39 | options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --security-opt apparmor=unconfined
40 | steps:
41 | - name: Checkout
42 | uses: actions/checkout@v3
43 | - name: Build and Test
44 | run: swift test --enable-test-discovery
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | html/
7 | *.pbxuser
8 | !default.pbxuser
9 | *.mode1v3
10 | !default.mode1v3
11 | *.mode2v3
12 | !default.mode2v3
13 | *.perspectivev3
14 | !default.perspectivev3
15 | xcuserdata
16 | *.xccheckout
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 | *.dot
22 | /.build
23 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --hexgrouping ignore
2 | --decimalgrouping ignore
3 | --enable isEmpty
4 | --disable blankLineAfterImports
5 |
6 | --exclude Tests/XCTestManifests.swift
--------------------------------------------------------------------------------
/Benchmarks/BenchmarkUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BenchmarkUtils.swift
3 | // Expression
4 | //
5 | // Created by Nick Lockwood on 13/02/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Expression
10 | import Foundation
11 |
12 | #if os(iOS) || os(macOS)
13 | import JavaScriptCore
14 | #endif
15 |
16 | let symbols: [Expression.Symbol: Expression.SymbolEvaluator] = [
17 | .variable("a"): { _ in 5 },
18 | .variable("b"): { _ in 6 },
19 | .variable("c"): { _ in 7 },
20 | .variable("hello"): { _ in -5 },
21 | .variable("world"): { _ in -3 },
22 | .function("foo", arity: 0): { _ in .pi },
23 | .function("foo", arity: 2): { $0[0] - $0[1] },
24 | .function("bar", arity: 1): { $0[0] - 2 },
25 | ]
26 |
27 | let anySymbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = {
28 | var anySymbols = [AnyExpression.Symbol: AnyExpression.SymbolEvaluator]()
29 | for (symbol, fn) in symbols {
30 | anySymbols[symbol] = { args in
31 | try fn(args.map {
32 | guard let arg = $0 as? Double else {
33 | throw AnyExpression.Error.message("Type mismatch")
34 | }
35 | return arg
36 | })
37 | }
38 | }
39 | return anySymbols
40 | }()
41 |
42 | let shortExpressions = [
43 | "5",
44 | "a",
45 | "foo()",
46 | "hello",
47 | "67",
48 | "3.5",
49 | "pi",
50 | ]
51 |
52 | let mediumExpressions = [
53 | "5 + 7",
54 | "a + b",
55 | "foo(5, 6)",
56 | "hello + world",
57 | "67 * 2",
58 | "3.5 / 6",
59 | "pi + 15",
60 | ]
61 |
62 | let longExpressions = [
63 | "5 + min(a, b * 10)",
64 | "max(a + b, b + c)",
65 | "foo(5, 6 + bar(6))",
66 | "hello + world",
67 | "(67 * 2) + (68 * 3)",
68 | "3.5 / 6 + 1234 * 54",
69 | "pi * -56.4 + (5 + 4)",
70 | ]
71 |
72 | let reallyLongExpression: String = {
73 | var parts = [String]()
74 | for i in 0 ..< 100 {
75 | parts.append("\(i)")
76 | }
77 | return "foo(" + parts.joined(separator: "+") + " + bar(5), a) + b"
78 | }()
79 |
80 | let booleanExpressions = [
81 | "true && false",
82 | "a == b",
83 | "foo(5, 6) != foo(5, 6)",
84 | "a ? hello : world",
85 | "false || true",
86 | "pi > 3",
87 | ]
88 |
89 | // MARK: Expression support
90 |
91 | func buildExpressions(_ expressions: [String]) -> [Expression] {
92 | return expressions.map {
93 | let parsedExpression = Expression.parse($0, usingCache: false)
94 | return Expression(parsedExpression, options: .pureSymbols, symbols: symbols)
95 | }
96 | }
97 |
98 | func evaluateExpressions(_ expressions: [Expression]) -> Double? {
99 | var result: Double?
100 | for _ in 0 ..< evalRepetitions {
101 | for expression in expressions {
102 | result = try! expression.evaluate()
103 | }
104 | }
105 | return result
106 | }
107 |
108 | func evaluateExpressions(_ expressions: [String]) -> Double? {
109 | var result: Double?
110 | for _ in 0 ..< parseRepetitions {
111 | for exp in expressions {
112 | let parsedExpression = Expression.parse(exp, usingCache: false)
113 | let expression = Expression(parsedExpression, options: .pureSymbols, symbols: symbols)
114 | result = try! expression.evaluate()
115 | }
116 | }
117 | return result
118 | }
119 |
120 | func buildAnyExpressions(_ expressions: [String]) -> [AnyExpression] {
121 | return expressions.map {
122 | let parsedExpression = Expression.parse($0, usingCache: false)
123 | return AnyExpression(parsedExpression, options: .pureSymbols, symbols: anySymbols)
124 | }
125 | }
126 |
127 | func evaluateAnyExpressions(_ expressions: [AnyExpression]) -> Double? {
128 | var result: Any?
129 | for _ in 0 ..< evalRepetitions {
130 | for expression in expressions {
131 | result = try! expression.evaluate()
132 | }
133 | }
134 | return (result as? NSNumber).map(Double.init(truncating:))
135 | }
136 |
137 | func evaluateAnyExpressions(_ expressions: [String]) -> Double? {
138 | var result: Any?
139 | for _ in 0 ..< parseRepetitions {
140 | for exp in expressions {
141 | let parsedExpression = Expression.parse(exp, usingCache: false)
142 | let expression = AnyExpression(parsedExpression, options: .noOptimize, symbols: anySymbols)
143 | result = try! expression.evaluate()
144 | }
145 | }
146 | return (result as? NSNumber).map(Double.init(truncating:))
147 | }
148 |
149 | // MARK: NSExpression support
150 |
151 | let shortNSExpressions: [String] = shortExpressions.map {
152 | switch $0 {
153 | case "foo()":
154 | return "FUNCTION(0, 'foo')"
155 | default:
156 | return $0
157 | }
158 | }
159 |
160 | let mediumNSExpressions: [String] = mediumExpressions.map {
161 | switch $0 {
162 | case "foo(5, 6)":
163 | return "FUNCTION(5, 'foo:', 6)"
164 | default:
165 | return $0
166 | }
167 | }
168 |
169 | let longNSExpressions: [String] = longExpressions.map {
170 | switch $0 {
171 | case "5 + min(a, b * 10)":
172 | return "5 + FUNCTION(a, 'min:', b * 10)"
173 | case "max(a + b, b + c)":
174 | return "FUNCTION(a + b, 'max:', b + c)"
175 | case "foo(5, 6 + bar(6))":
176 | return "FUNCTION(5, 'foo:', 6 + FUNCTION(6, 'bar'))"
177 | default:
178 | return $0
179 | }
180 | }
181 |
182 | let nsSymbols = [
183 | "pi": Double.pi,
184 | "a": 5,
185 | "b": 6,
186 | "c": 7,
187 | "hello": -5,
188 | "world": -3,
189 | ]
190 |
191 | extension NSNumber {
192 | @objc func foo() -> NSNumber {
193 | return Double.pi as NSNumber
194 | }
195 |
196 | @objc func foo(_ other: NSNumber) -> NSNumber {
197 | return (Double(truncating: self) + Double(truncating: other)) as NSNumber
198 | }
199 |
200 | @objc func bar() -> NSNumber {
201 | return (Double(truncating: self) + 2) as NSNumber
202 | }
203 |
204 | @objc func min(_ other: NSNumber) -> NSNumber {
205 | return Swift.min(Double(truncating: self), Double(truncating: other)) as NSNumber
206 | }
207 |
208 | @objc func max(_ other: NSNumber) -> NSNumber {
209 | return Swift.max(Double(truncating: self), Double(truncating: other)) as NSNumber
210 | }
211 | }
212 |
213 | func buildNSExpressions(_ expressions: [String]) -> [NSExpression] {
214 | return expressions.map { NSExpression(format: $0) }
215 | }
216 |
217 | func evaluateNSExpressions(_ expressions: [NSExpression]) -> NSNumber? {
218 | var result: NSNumber?
219 | for _ in 0 ..< evalRepetitions {
220 | for expression in expressions {
221 | result = expression.expressionValue(with: nsSymbols, context: nil) as? NSNumber
222 | }
223 | }
224 | return result
225 | }
226 |
227 | func evaluateNSExpressions(_ expressions: [String]) -> NSNumber? {
228 | var result: NSNumber?
229 | for _ in 0 ..< parseRepetitions {
230 | for exp in expressions {
231 | let expression = NSExpression(format: exp)
232 | result = expression.expressionValue(with: nsSymbols, context: nil) as? NSNumber
233 | }
234 | }
235 | return result
236 | }
237 |
238 | #if os(iOS) || os(macOS)
239 |
240 | // MARK: JS symbols
241 |
242 | private let foo: @convention(block) (Double, Double) -> Double = { a, b in
243 | if a.isNaN {
244 | return Double.pi
245 | }
246 | return a + b
247 | }
248 |
249 | private let bar: @convention(block) (Double) -> Double = {
250 | $0 - 2
251 | }
252 |
253 | let jsSymbols: [String: Any] = [
254 | "pi": Double.pi,
255 | "a": 5,
256 | "b": 6,
257 | "c": 7,
258 | "hello": -5,
259 | "world": -3,
260 | "foo": foo,
261 | "bar": bar,
262 | ]
263 |
264 | func makeJSContext(symbols: [String: Any]) -> JSContext {
265 | let context = JSContext()!
266 | for (key, value) in symbols {
267 | context.globalObject.setValue(value, forProperty: key)
268 | }
269 | return context
270 | }
271 |
272 | func buildJSExpressions(_ expressions: [String]) -> [() -> JSValue] {
273 | return expressions.map { exp -> (() -> JSValue) in
274 | let context = makeJSContext(symbols: jsSymbols)
275 | // Note: it may seem unfair to be evaluating the script inside the block
276 | // however, I tried wrapping the script as a function and storing the
277 | // evaluated result in a JSValue to be executed in the block, and that
278 | // was at least an order of magnitude slower than this approac
279 | return { context.evaluateScript(exp) }
280 | }
281 | }
282 |
283 | func evaluateJSExpressions(_ expressions: [() -> JSValue]) -> JSValue? {
284 | var result: JSValue?
285 | for _ in 0 ..< evalRepetitions {
286 | for expression in expressions {
287 | result = expression()
288 | }
289 | }
290 | return result
291 | }
292 |
293 | func evaluateJSExpressions(_ expressions: [String]) -> JSValue? {
294 | var result: JSValue?
295 | for _ in 0 ..< parseRepetitions {
296 | let context = makeJSContext(symbols: jsSymbols)
297 | for exp in expressions {
298 | result = context.evaluateScript(exp)
299 | }
300 | }
301 | return result
302 | }
303 |
304 | #endif
305 |
--------------------------------------------------------------------------------
/Benchmarks/Benchmarks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Benchmarks.swift
3 | // ExpressionTests
4 | //
5 | // Created by Nick Lockwood on 13/02/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Expression
10 | import XCTest
11 |
12 | #if os(iOS) || os(macOS)
13 | import JavaScriptCore
14 | #endif
15 |
16 | let parseRepetitions = 50
17 | let evalRepetitions = 500
18 |
19 | class Benchmarks: XCTestCase {
20 | // MARK: End-to-end
21 |
22 | func testEndToEndShortExpressions() {
23 | var result: Double?
24 | measure {
25 | result = evaluateExpressions(shortExpressions)
26 | }
27 | XCTAssertEqual(result, Double.pi)
28 | }
29 |
30 | func testEndToEndShortAnyExpressions() {
31 | var result: Double?
32 | measure {
33 | result = evaluateAnyExpressions(shortExpressions)
34 | }
35 | XCTAssertEqual(result, Double.pi)
36 | }
37 |
38 | func testEndToEndShortNSExpressions() {
39 | var result: NSNumber?
40 | measure {
41 | result = evaluateNSExpressions(shortNSExpressions)
42 | }
43 | XCTAssertEqual(result, Double.pi as NSNumber)
44 | }
45 |
46 | #if os(iOS) || os(macOS)
47 | func testEndToEndShortJSExpressions() {
48 | var result: JSValue?
49 | measure {
50 | result = evaluateJSExpressions(shortExpressions)
51 | }
52 | XCTAssertEqual(result?.toNumber(), Double.pi as NSNumber)
53 | }
54 | #endif
55 |
56 | func testEndToEndMediumExpressions() {
57 | var result: Double?
58 | measure {
59 | result = evaluateExpressions(mediumExpressions)
60 | }
61 | XCTAssertEqual(result, Double.pi + 15)
62 | }
63 |
64 | func testEndToEndMediumAnyExpressions() {
65 | var result: Double?
66 | measure {
67 | result = evaluateAnyExpressions(mediumExpressions)
68 | }
69 | XCTAssertEqual(result, Double.pi + 15)
70 | }
71 |
72 | func testEndToEndMediumNSExpressions() {
73 | var result: NSNumber?
74 | measure {
75 | result = evaluateNSExpressions(mediumNSExpressions)
76 | }
77 | XCTAssertEqual(result, (Double.pi + 15) as NSNumber)
78 | }
79 |
80 | #if os(iOS) || os(macOS)
81 | func testEndToEndMediumJSExpressions() {
82 | var result: JSValue?
83 | measure {
84 | result = evaluateJSExpressions(mediumExpressions)
85 | }
86 | XCTAssertEqual(result?.toNumber(), (Double.pi + 15) as NSNumber)
87 | }
88 | #endif
89 |
90 | func testEndToEndLongExpressions() {
91 | var result: Double?
92 | measure {
93 | result = evaluateExpressions(longExpressions)
94 | }
95 | XCTAssertEqual(result, Double.pi * -56.4 + 9)
96 | }
97 |
98 | func testEndToEndLongAnyExpressions() {
99 | var result: Double?
100 | measure {
101 | result = evaluateAnyExpressions(longExpressions)
102 | }
103 | XCTAssertEqual(result, Double.pi * -56.4 + 9)
104 | }
105 |
106 | func testEndToEndLongNSExpressions() {
107 | var result: NSNumber?
108 | measure {
109 | result = evaluateNSExpressions(longNSExpressions)
110 | }
111 | XCTAssertEqual(result, (Double.pi * -56.4 + 9) as NSNumber)
112 | }
113 |
114 | #if os(iOS) || os(macOS)
115 | func testEndToEndLongJSExpressions() {
116 | var result: JSValue?
117 | measure {
118 | result = evaluateJSExpressions(longExpressions)
119 | }
120 | XCTAssertEqual(result?.toNumber(), (Double.pi * -56.4 + 9) as NSNumber)
121 | }
122 | #endif
123 |
124 | // MARK: Evaluation
125 |
126 | func testEvaluateShortExpressions() {
127 | let expressions = buildExpressions(shortExpressions)
128 | var result: Double?
129 | measure {
130 | result = evaluateExpressions(expressions)
131 | }
132 | XCTAssertEqual(result, Double.pi)
133 | }
134 |
135 | func testEvaluateShortAnyExpressions() {
136 | let expressions = buildAnyExpressions(shortExpressions)
137 | var result: Double?
138 | measure {
139 | result = evaluateAnyExpressions(expressions)
140 | }
141 | XCTAssertEqual(result, Double.pi)
142 | }
143 |
144 | func testEvaluateShortNSExpressions() {
145 | let expressions = buildNSExpressions(shortNSExpressions)
146 | var result: NSNumber?
147 | measure {
148 | result = evaluateNSExpressions(expressions)
149 | }
150 | XCTAssertEqual(result, Double.pi as NSNumber)
151 | }
152 |
153 | #if os(iOS) || os(macOS)
154 | func testEvaluateShortJSExpressions() {
155 | let expressions = buildJSExpressions(shortExpressions)
156 | var result: JSValue?
157 | measure {
158 | result = evaluateJSExpressions(expressions)
159 | }
160 | XCTAssertEqual(result?.toNumber(), Double.pi as NSNumber)
161 | }
162 | #endif
163 |
164 | func testEvaluateMediumExpressions() {
165 | let expressions = buildExpressions(mediumExpressions)
166 | var result: Double?
167 | measure {
168 | result = evaluateExpressions(expressions)
169 | }
170 | XCTAssertEqual(result, Double.pi + 15)
171 | }
172 |
173 | func testEvaluateMediumAnyExpressions() {
174 | let expressions = buildAnyExpressions(mediumExpressions)
175 | var result: Double?
176 | measure {
177 | result = evaluateAnyExpressions(expressions)
178 | }
179 | XCTAssertEqual(result, Double.pi + 15)
180 | }
181 |
182 | func testEvaluateMediumNSExpressions() {
183 | let expressions = buildNSExpressions(mediumNSExpressions)
184 | var result: NSNumber?
185 | measure {
186 | result = evaluateNSExpressions(expressions)
187 | }
188 | XCTAssertEqual(result, (Double.pi + 15) as NSNumber)
189 | }
190 |
191 | #if os(iOS) || os(macOS)
192 | func testEvaluateMediumJSExpressions() {
193 | let expressions = buildJSExpressions(mediumExpressions)
194 | var result: JSValue?
195 | measure {
196 | result = evaluateJSExpressions(expressions)
197 | }
198 | XCTAssertEqual(result?.toNumber(), (Double.pi + 15) as NSNumber)
199 | }
200 | #endif
201 |
202 | func testEvaluateLongExpressions() {
203 | let expressions = buildExpressions(longExpressions)
204 | var result: Double?
205 | measure {
206 | result = evaluateExpressions(expressions)
207 | }
208 | XCTAssertEqual(result, Double.pi * -56.4 + 9)
209 | }
210 |
211 | func testEvaluateLongAnyExpressions() {
212 | let expressions = buildAnyExpressions(longExpressions)
213 | var result: Double?
214 | measure {
215 | result = evaluateAnyExpressions(expressions)
216 | }
217 | XCTAssertEqual(result, Double.pi * -56.4 + 9)
218 | }
219 |
220 | func testEvaluateLongNSExpressions() {
221 | let expressions = buildNSExpressions(longNSExpressions)
222 | var result: NSNumber?
223 | measure {
224 | result = evaluateNSExpressions(expressions)
225 | }
226 | XCTAssertEqual(result, (Double.pi * -56.4 + 9) as NSNumber)
227 | }
228 |
229 | #if os(iOS) || os(macOS)
230 | func testEvaluateLongJSExpressions() {
231 | let expressions = buildJSExpressions(longExpressions)
232 | var result: JSValue?
233 | measure {
234 | result = evaluateJSExpressions(expressions)
235 | }
236 | XCTAssertEqual(result?.toNumber(), (Double.pi * -56.4 + 9) as NSNumber)
237 | }
238 | #endif
239 | }
240 |
--------------------------------------------------------------------------------
/Benchmarks/PerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PerformanceTests.swift
3 | // ExpressionTests
4 | //
5 | // Created by Nick Lockwood on 24/05/2017.
6 | // Copyright © 2017 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Expression
33 | import XCTest
34 |
35 | class PerformanceTests: XCTestCase {
36 | private let parseRepetitions = 500
37 | private let evalRepetitions = 5000
38 |
39 | // MARK: parsing
40 |
41 | func testParsingShortExpressions() {
42 | measure { parseExpressions(shortExpressions) }
43 | }
44 |
45 | func testParsingMediumExpressions() {
46 | measure { parseExpressions(mediumExpressions) }
47 | }
48 |
49 | func testParsingLongExpressions() {
50 | measure { parseExpressions(longExpressions) }
51 | }
52 |
53 | func testParsingReallyLongExpressions() {
54 | measure { parseExpressions([reallyLongExpression]) }
55 | }
56 |
57 | func testParsingBooleanExpressions() {
58 | measure { parseExpressions(booleanExpressions) }
59 | }
60 |
61 | // MARK: optimizing
62 |
63 | func testOptimizingShortExpressions() {
64 | let expressions = shortExpressions.map { Expression.parse($0, usingCache: false) }
65 | measure { optimizeExpressions(expressions) }
66 | }
67 |
68 | func testOptimizingShortExpressionsWithNewInitializer() {
69 | let expressions = shortExpressions.map { Expression.parse($0, usingCache: false) }
70 | measure { optimizeExpressionsWithNewInitializer(expressions) }
71 | }
72 |
73 | func testOptimizingShortAnyExpressions() {
74 | let expressions = shortExpressions.map { Expression.parse($0, usingCache: false) }
75 | measure { optimizeAnyExpressions(expressions) }
76 | }
77 |
78 | func testOptimizingShortAnyExpressionsWithNewInitializer() {
79 | let expressions = shortExpressions.map { Expression.parse($0, usingCache: false) }
80 | measure { optimizeAnyExpressionsWithNewInitializer(expressions) }
81 | }
82 |
83 | func testOptimizingMediumExpressions() {
84 | let expressions = mediumExpressions.map { Expression.parse($0, usingCache: false) }
85 | measure { optimizeExpressions(expressions) }
86 | }
87 |
88 | func testOptimizingMediumExpressionsWithNewInitializer() {
89 | let expressions = mediumExpressions.map { Expression.parse($0, usingCache: false) }
90 | measure { optimizeExpressionsWithNewInitializer(expressions) }
91 | }
92 |
93 | func testOptimizingMediumAnyExpressions() {
94 | let expressions = mediumExpressions.map { Expression.parse($0, usingCache: false) }
95 | measure { optimizeAnyExpressions(expressions) }
96 | }
97 |
98 | func testOptimizingMediumAnyExpressionsWithNewInitializer() {
99 | let expressions = mediumExpressions.map { Expression.parse($0, usingCache: false) }
100 | measure { optimizeExpressionsWithNewInitializer(expressions) }
101 | }
102 |
103 | func testOptimizingLongExpressions() {
104 | let expressions = longExpressions.map { Expression.parse($0, usingCache: false) }
105 | measure { optimizeExpressions(expressions) }
106 | }
107 |
108 | func testOptimizingLongExpressionsWithNewInitializer() {
109 | let expressions = longExpressions.map { Expression.parse($0, usingCache: false) }
110 | measure { optimizeExpressionsWithNewInitializer(expressions) }
111 | }
112 |
113 | func testOptimizingLongAnyExpressions() {
114 | let expressions = longExpressions.map { Expression.parse($0, usingCache: false) }
115 | measure { optimizeAnyExpressions(expressions) }
116 | }
117 |
118 | func testOptimizingLongAnyExpressionsWithNewInitializer() {
119 | let expressions = longExpressions.map { Expression.parse($0, usingCache: false) }
120 | measure { optimizeAnyExpressionsWithNewInitializer(expressions) }
121 | }
122 |
123 | func testOptimizingReallyLongExpression() {
124 | let exp = Expression.parse(reallyLongExpression, usingCache: false)
125 | measure { optimizeExpressions([exp]) }
126 | }
127 |
128 | func testOptimizinReallyLongExpressionWithNewInitializer() {
129 | let exp = Expression.parse(reallyLongExpression, usingCache: false)
130 | measure { optimizeExpressionsWithNewInitializer([exp]) }
131 | }
132 |
133 | func testOptimizingReallyLongAnyExpression() {
134 | let exp = Expression.parse(reallyLongExpression, usingCache: false)
135 | measure { optimizeAnyExpressions([exp]) }
136 | }
137 |
138 | func testOptimizingReallyLongAnyExpressionWithNewInitializer() {
139 | let exp = Expression.parse(reallyLongExpression, usingCache: false)
140 | measure { optimizeAnyExpressionsWithNewInitializer([exp]) }
141 | }
142 |
143 | func testOptimizingBooleanExpressions() {
144 | let expressions = booleanExpressions.map { Expression.parse($0, usingCache: false) }
145 | measure { optimizeExpressions(expressions) }
146 | }
147 |
148 | func testOptimizingBooleanExpressionsWithNewInitializer() {
149 | let expressions = booleanExpressions.map { Expression.parse($0, usingCache: false) }
150 | measure { optimizeExpressionsWithNewInitializer(expressions) }
151 | }
152 |
153 | func testOptimizingBooleanAnyExpressions() {
154 | let expressions = booleanExpressions.map { Expression.parse($0, usingCache: false) }
155 | measure { optimizeAnyExpressions(expressions) }
156 | }
157 |
158 | func testOptimizingBooleanAnyExpressionsWithNewInitializer() {
159 | let expressions = booleanExpressions.map { Expression.parse($0, usingCache: false) }
160 | measure { optimizeAnyExpressionsWithNewInitializer(expressions) }
161 | }
162 |
163 | // MARK: evaluating
164 |
165 | func testEvaluatingShortExpressions() {
166 | let expressions = shortExpressions.map { Expression($0, options: .pureSymbols, symbols: symbols) }
167 | measure { evaluateExpressions(expressions) }
168 | }
169 |
170 | func testEvaluatingShortAnyExpressions() {
171 | let expressions = shortExpressions.map { AnyExpression($0, options: .pureSymbols, symbols: anySymbols) }
172 | measure { evaluateExpressions(expressions) }
173 | }
174 |
175 | func testEvaluatingMediumExpressions() {
176 | let expressions = mediumExpressions.map { Expression($0, options: .pureSymbols, symbols: symbols) }
177 | measure { evaluateExpressions(expressions) }
178 | }
179 |
180 | func testEvaluatingMediumAnyExpressions() {
181 | let expressions = mediumExpressions.map { AnyExpression($0, options: .pureSymbols, symbols: anySymbols) }
182 | measure { evaluateExpressions(expressions) }
183 | }
184 |
185 | func testEvaluatingLongExpressions() {
186 | let expressions = mediumExpressions.map { Expression($0, options: .pureSymbols, symbols: symbols) }
187 | measure { evaluateExpressions(expressions) }
188 | }
189 |
190 | func testEvaluatingLongAnyExpressions() {
191 | let expressions = mediumExpressions.map { AnyExpression($0, options: .pureSymbols, symbols: anySymbols) }
192 | measure { evaluateExpressions(expressions) }
193 | }
194 |
195 | func testEvaluatingReallyLongExpression() {
196 | let exp = Expression(reallyLongExpression, options: .pureSymbols, symbols: symbols)
197 | measure { evaluateExpressions([exp]) }
198 | }
199 |
200 | func testEvaluatingReallyLongAnyExpression() {
201 | let exp = AnyExpression(reallyLongExpression, options: .pureSymbols, symbols: anySymbols)
202 | measure { evaluateExpressions([exp]) }
203 | }
204 |
205 | func testEvaluatingBooleanExpressions() {
206 | let expressions = booleanExpressions.map { Expression($0, options: [.boolSymbols, .pureSymbols], symbols: symbols) }
207 | measure { evaluateExpressions(expressions) }
208 | }
209 |
210 | func testEvaluatingBooleanAnyExpressions() {
211 | let expressions = booleanExpressions.map { AnyExpression($0, options: [.boolSymbols, .pureSymbols], symbols: anySymbols) }
212 | measure { evaluateExpressions(expressions) }
213 | }
214 |
215 | // MARK: == performance
216 |
217 | func testEquateDoubles() {
218 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
219 | .variable("a"): { _ in 5 },
220 | .variable("b"): { _ in 6 },
221 | ]
222 | let equalExpression = AnyExpression("a == a", symbols: symbols)
223 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
224 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
225 | XCTAssertTrue(try equalExpression.evaluate())
226 | XCTAssertFalse(try unequalExpression.evaluate())
227 | }
228 |
229 | func testEquateBools() {
230 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
231 | .variable("a"): { _ in true },
232 | .variable("b"): { _ in false },
233 | ]
234 | let equalExpression = AnyExpression("a == a", symbols: symbols)
235 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
236 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
237 | XCTAssertTrue(try equalExpression.evaluate())
238 | XCTAssertFalse(try unequalExpression.evaluate())
239 | }
240 |
241 | func testEquateStrings() {
242 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
243 | .variable("a"): { _ in "a" },
244 | .variable("b"): { _ in "b" },
245 | ]
246 | let equalExpression = AnyExpression("a == a", symbols: symbols)
247 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
248 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
249 | XCTAssertTrue(try equalExpression.evaluate())
250 | XCTAssertFalse(try unequalExpression.evaluate())
251 | }
252 |
253 | func testEquateNSObjects() {
254 | let objectA = NSObject()
255 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
256 | .variable("a"): { _ in objectA },
257 | .variable("b"): { _ in NSObject() },
258 | ]
259 | let equalExpression = AnyExpression("a == a", symbols: symbols)
260 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
261 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
262 | XCTAssertTrue(try equalExpression.evaluate())
263 | XCTAssertFalse(try unequalExpression.evaluate())
264 | }
265 |
266 | func testEquateArrays() {
267 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
268 | .variable("a"): { _ in ["hello"] },
269 | .variable("b"): { _ in ["goodbye"] },
270 | ]
271 | let equalExpression = AnyExpression("a == a", symbols: symbols)
272 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
273 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
274 | XCTAssertTrue(try equalExpression.evaluate())
275 | XCTAssertFalse(try unequalExpression.evaluate())
276 | }
277 |
278 | func testEquateHashables() {
279 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
280 | .variable("a"): { _ in HashableStruct(foo: 5) },
281 | .variable("b"): { _ in HashableStruct(foo: 6) },
282 | ]
283 | let equalExpression = AnyExpression("a == a", symbols: symbols)
284 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
285 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
286 | XCTAssertTrue(try equalExpression.evaluate())
287 | XCTAssertFalse(try unequalExpression.evaluate())
288 | }
289 |
290 | func testCompareAgainstNil() {
291 | let symbols: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
292 | .variable("a"): { _ in NSNull() },
293 | .variable("b"): { _ in 5 },
294 | ]
295 | let equalExpression = AnyExpression("a == a", symbols: symbols)
296 | let unequalExpression = AnyExpression("a == b", symbols: symbols)
297 | measure { evaluateExpressions([equalExpression, unequalExpression]) }
298 | XCTAssertTrue(try equalExpression.evaluate())
299 | XCTAssertFalse(try unequalExpression.evaluate())
300 | }
301 |
302 | // MARK: Utility functions
303 |
304 | private func parseExpressions(_ expressions: [String]) {
305 | for _ in 0 ..< parseRepetitions {
306 | for exp in expressions {
307 | _ = Expression.parse(exp, usingCache: false)
308 | }
309 | }
310 | }
311 |
312 | private func optimizeExpressions(_ expressions: [ParsedExpression]) {
313 | for _ in 0 ..< parseRepetitions {
314 | for exp in expressions {
315 | _ = Expression(exp, options: [.pureSymbols, .boolSymbols], symbols: symbols)
316 | }
317 | }
318 | }
319 |
320 | private func optimizeExpressionsWithNewInitializer(_ expressions: [ParsedExpression]) {
321 | for _ in 0 ..< parseRepetitions {
322 | for exp in expressions {
323 | _ = Expression(exp, pureSymbols: { symbols[$0] })
324 | }
325 | }
326 | }
327 |
328 | private func optimizeAnyExpressions(_ expressions: [ParsedExpression]) {
329 | for _ in 0 ..< parseRepetitions {
330 | for exp in expressions {
331 | _ = AnyExpression(exp, options: [.pureSymbols, .boolSymbols], symbols: anySymbols)
332 | }
333 | }
334 | }
335 |
336 | private func optimizeAnyExpressionsWithNewInitializer(_ expressions: [ParsedExpression]) {
337 | for _ in 0 ..< parseRepetitions {
338 | for exp in expressions {
339 | _ = AnyExpression(exp, pureSymbols: { anySymbols[$0] })
340 | }
341 | }
342 | }
343 |
344 | private func evaluateExpressions(_ expressions: [Expression]) {
345 | for _ in 0 ..< evalRepetitions {
346 | for exp in expressions {
347 | _ = try! exp.evaluate()
348 | }
349 | }
350 | }
351 |
352 | private func evaluateExpressions(_ expressions: [AnyExpression]) {
353 | for _ in 0 ..< evalRepetitions {
354 | for exp in expressions {
355 | _ = try! exp.evaluate() as Any
356 | }
357 | }
358 | }
359 |
360 | private struct HashableStruct: Hashable {
361 | let foo: Int
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [0.13.9](https://github.com/nicklockwood/Expression/releases/tag/0.13.9) (2024-07-14)
4 |
5 | - Added `NumericExpression` typealias for Xcode 16
6 |
7 | ## [0.13.8](https://github.com/nicklockwood/Expression/releases/tag/0.13.8) (2024-01-04)
8 |
9 | - Fixed precedence for (^) exponent operator
10 |
11 | ## [0.13.7](https://github.com/nicklockwood/Expression/releases/tag/0.13.7) (2023-02-21)
12 |
13 | - Fixed String.Index crash on macOS 13 / Swift 5.7
14 | - Added log() function to standard library
15 |
16 | ## [0.13.6](https://github.com/nicklockwood/Expression/releases/tag/0.13.6) (2022-03-26)
17 |
18 | - Fixed crash when running with Swift 5.6 / Xcode 13.3
19 | - Fixed warnings and build failures in Xcode 13.3
20 |
21 | ## [0.13.5](https://github.com/nicklockwood/Expression/releases/tag/0.13.5) (2021-10-16)
22 |
23 | - Fixed build failure in benchmark example on Xcode 13
24 |
25 | ## [0.13.4](https://github.com/nicklockwood/Expression/releases/tag/0.13.4) (2021-07-29)
26 |
27 | - Fixed precedence bug where postfix operator was incorrectly applied to entire preceding expression
28 |
29 | ## [0.13.3](https://github.com/nicklockwood/Expression/releases/tag/0.13.3) (2021-07-24)
30 |
31 | - Fixed parsing error with string literals that begin with a double backslash
32 |
33 | ## [0.13.2](https://github.com/nicklockwood/Expression/releases/tag/0.13.2) (2019-06-30)
34 |
35 | - AnyExpression now supports concurrent evaluation on Linux
36 | - Fixed NSString bug on Linux
37 |
38 | ## [0.13.1](https://github.com/nicklockwood/Expression/releases/tag/0.13.1) (2019-06-05)
39 |
40 | - Fixed error in Swift 5.1 beta
41 |
42 | ## [0.13.0](https://github.com/nicklockwood/Expression/releases/tag/0.13.0) (2019-05-10)
43 |
44 | - Fixed Xcode 10.2 warnings
45 | - Expression now requires a minimum Swift version of 4.2
46 |
47 | ## [0.12.12](https://github.com/nicklockwood/Expression/releases/tag/0.12.12) (2019-03-26)
48 |
49 | - Expression now builds correctly on Linux, including the test suite
50 | - Fixed bug where whitespace around operators could affect the precedence
51 | - Fixed a bug where numeric values could incorrectly be printed as a boolean
52 |
53 | ## [0.12.11](https://github.com/nicklockwood/Expression/releases/tag/0.12.11) (2018-06-15)
54 |
55 | - Fixed all warnings in Xcode 10 beta
56 | - Expression now requires Xcode 9.3 or higher
57 |
58 | ## [0.12.10](https://github.com/nicklockwood/Expression/releases/tag/0.12.10) (2018-06-05)
59 |
60 | - Fixed compilation errors in Xcode 10 beta (but not warnings, yet)
61 | - Fixed slow-compiling unit tests
62 |
63 | ## [0.12.9](https://github.com/nicklockwood/Expression/releases/tag/0.12.9) (2018-03-07)
64 |
65 | - AnyExpression now works on Linux, with a couple of caveats (see README for details)
66 | - Fixed Swift Package Manager integration
67 |
68 | ## [0.12.8](https://github.com/nicklockwood/Expression/releases/tag/0.12.8) (2018-02-26)
69 |
70 | - Pure function symbols now take precedence over impure SymbolEvaluator symbols of the same name
71 |
72 | ## [0.12.7](https://github.com/nicklockwood/Expression/releases/tag/0.12.7) (2018-02-26)
73 |
74 | - Anonymous function syntax now works with all symbol types
75 | - AnyExpression `evaluate()` now supports CGFloat values
76 | - AnyExpression now correctly handles `NSArray` and `NSDictionary` values
77 | - Improved AnyExpression stringifying of array, dictionary and partial range values
78 | - Improved error messages for invalid range and type mismatch
79 | - Added AnyExpression REPL example project
80 |
81 | ## [0.12.6](https://github.com/nicklockwood/Expression/releases/tag/0.12.6) (2018-02-22)
82 |
83 | - AnyExpression now supports calling functions stored in a constant or variable
84 | - AnyExpression now supports calling anonymous functions returned by a sub-expression
85 | - Fixed some bugs with array constants incorrectly shadowing symbols
86 |
87 | ## [0.12.5](https://github.com/nicklockwood/Expression/releases/tag/0.12.5) (2018-02-13)
88 |
89 | - Added Benchmark app for comparing Expression performance against NSExpression and JavaScriptCore
90 | - Added support for partial ranges (e.g. `array[startIndex...]`, `string[...upperBound]`, etc)
91 | - Fixed crash when accessing string with an out-of-bounds range
92 |
93 | ## [0.12.4](https://github.com/nicklockwood/Expression/releases/tag/0.12.4) (2018-02-12)
94 |
95 | - Array subscripting operator can now be used with array literals and the result of expressions
96 | - Added support for subscripting strings using either `String.Index` or `Int` character offsets
97 | - AnyExpression now supports range literals using the `...` and `..<` operators
98 | - You can now create substrings or sub-arrays using subscript syntax with range values
99 | - Added automatic casting between `String` and `Substring` within expressions
100 | - AnyExpression's + operator can now concatenate arrays as well as strings
101 |
102 | ## [0.12.3](https://github.com/nicklockwood/Expression/releases/tag/0.12.3) (2018-02-06)
103 |
104 | - AnyExpression now supports array literals like `[1,2,3]` and `["hello", "world"]`
105 | - AnyExpression can now automatically cast between numeric arrays of different types
106 | - Improved messaging for function arity errors and some types of syntax error
107 | - Fixed Swift 3.2 compatibility issues
108 |
109 | ## [0.12.2](https://github.com/nicklockwood/Expression/releases/tag/0.12.2) (2018-02-05)
110 |
111 | - AnyExpression now supports subscripting of `ArraySlice` and `Dictionary` values
112 | - AnyExpression's `evaluate()` type casting now supports almost all numeric types
113 | - Improved AnyExpression error messaging, especially array subscripting errors
114 |
115 | ## [0.12.1](https://github.com/nicklockwood/Expression/releases/tag/0.12.1) (2018-01-31)
116 |
117 | - Reduced initialization time for `Expression` and `AnyExpression` instances
118 |
119 | ## [0.12.0](https://github.com/nicklockwood/Expression/releases/tag/0.12.0) (2018-01-25)
120 |
121 | - An `AnyExpression` instance can now be evaluated concurrently on multiple threads (`Expression` instances were already thread-safe)
122 | - Using the AnyExpression == operator with unsupported types now throws an error instead of returning false
123 | - Significantly reduced initialization time for Expression and AnyExpression instances
124 | - The `boolSymbols` and `noOptimize` options now work correctly for AnyExpression
125 | - Removed all deprecated APIs and deprecation warnings
126 |
127 | ## [0.11.4](https://github.com/nicklockwood/Expression/releases/tag/0.11.4) (2018-01-24)
128 |
129 | - Improved AnyExpression == operator implementation, now supports equating tuples and dictionaries
130 | - Array symbols implemented using `symbols` dictionary are no longer inlined by optimizer, in accordance with documentation
131 | - Deprecated the `Evaluator` function and provided alternative initializers for `Expression` and `AnyExpression`
132 | - Removed deferred optimization. Expressions using a custom `Evaluator` function may now run slower as a result
133 |
134 | ## [0.11.3](https://github.com/nicklockwood/Expression/releases/tag/0.11.3) (2018-01-22)
135 |
136 | - Added new initializers for Expression and AnyExpression to simplify and improve performance when using advanced features
137 | - Attempting to index an array with a non-numeric type in AnyExpression now produces a more meaningful error message
138 | - Fixed optimization bug when using the built-in `pi` constant
139 |
140 | ## [0.11.2](https://github.com/nicklockwood/Expression/releases/tag/0.11.2) (2018-01-18)
141 |
142 | - Significantly improved AnyExpression evaluation performance
143 | - The `pureSymbols` option is now taken into account when optimizing custom AnyExpression symbols
144 | - Added `noDeferredOptimize` option to disable additional optimization of expressions during first evaluation
145 | - Updated performance tests to include tests for boolean expressions and AnyExpression
146 |
147 | ## [0.11.1](https://github.com/nicklockwood/Expression/releases/tag/0.11.1) (2018-01-17)
148 |
149 | - Fixed optimization bug where custom symbols could unexpectedly produce NaN output in AnyExpression
150 | - The `pureSymbols` option now has no effect for AnyExpression (regular Expression is unaffected)
151 |
152 | ## [0.11.0](https://github.com/nicklockwood/Expression/releases/tag/0.11.0) (2018-01-16)
153 |
154 | - Added `AnyExpression` extension for dealing with arbitrary data types
155 | - Renamed `Symbol.Evaluator` to `SymbolEvaluator` (the old name is now deprecated)
156 | - Improved error messages for missing function arguments
157 |
158 | ## [0.10.0](https://github.com/nicklockwood/Expression/releases/tag/0.10.0) (2017-12-28)
159 |
160 | - Added support for variadic functions. This may cause minor breaking changes to custom Evaluator functions
161 | - The built-in `min()` and `max()` functions now both support more than two arguments (using the new variadics support)
162 |
163 | ## [0.9.3](https://github.com/nicklockwood/Expression/releases/tag/0.9.3) (2017-12-18)
164 |
165 | - Hyphens are now only permitted at the start of an operator, which solves an ambiguity with unary minus
166 | - Dots are now only permitted at the start of an operator, which solves an ambiguity with float literals
167 |
168 | ## [0.9.2](https://github.com/nicklockwood/Expression/releases/tag/0.9.2) (2017-12-15)
169 |
170 | - A dot followed by a digit is now treated as a floating point literal instead of an identifier
171 | - Parens are no longer stripped around function arguments containing a comma operator (tuples)
172 | - Fixed edge case when printing description for operators containing special characters
173 | - Refactored parser implementation to removed unreachable code and improve test coverage
174 | - Improved error message when trying to pass multiple arguments to an array subscript
175 |
176 | ## [0.9.1](https://github.com/nicklockwood/Expression/releases/tag/0.9.1) (2017-12-04)
177 |
178 | - Expression description now correctly escapes unprintable characters in quoted symbols
179 | - Expression description no longer adds unnecessary parens around sub-expressions
180 | - More helpful error messages are now generated for various syntax mistakes
181 | - Improved test coverage and fixed many other minor bugs
182 |
183 | ## [0.9.0](https://github.com/nicklockwood/Expression/releases/tag/0.9.0) (2017-12-01)
184 |
185 | - Switched to a more conventional MIT license
186 | - Added support for array symbols, so expressions like `foo[5]` and `bar[x + 1]` are now possible
187 | - Enabled trailing apostrophe in symbol names, so you can use symbols like `x'`
188 | - Added `isValidIdentifier()` and `isValidOperator()` methods for validating symbol names
189 | - Fixed warnings in Xcode 9.1 and dropped support for Swift 3.1
190 | - Improved cache performance
191 |
192 | ## [0.8.5](https://github.com/nicklockwood/Expression/releases/tag/0.8.5) (2017-09-04)
193 |
194 | - Improved expression parsing performance in Swift 3.2 and 4.0
195 | - Fixed some bugs in parsing of identifiers containing dots
196 |
197 | ## [0.8.4](https://github.com/nicklockwood/Expression/releases/tag/0.8.4) (2017-08-22)
198 |
199 | - Fixed spurious parsing errors when expressions have leading whitespace
200 | - The `parse(_: String.UnicodeScalarView)` method now accepts an optional list of terminating delimiters
201 |
202 | ## [0.8.3](https://github.com/nicklockwood/Expression/releases/tag/0.8.3) (2017-08-16)
203 |
204 | - Fixed crash when parsing a malformed expression that contains just a single operator
205 | - Internal `mathSymbols` and `boolSymbols` dictionaries are now public, so you can filter them from symbols array
206 |
207 | ## [0.8.2](https://github.com/nicklockwood/Expression/releases/tag/0.8.2) (2017-08-08)
208 |
209 | - Xcode 9b5 compatibility fixes
210 |
211 | ## [0.8.1](https://github.com/nicklockwood/Expression/releases/tag/0.8.1) (2017-07-25)
212 |
213 | - Now marks the correct token as unexpected when attempting to chain function calls (e.g. `foo(5)(6)`)
214 | - Now produces a clearer error for empty expressions
215 |
216 | ## [0.8.0](https://github.com/nicklockwood/Expression/releases/tag/0.8.0) (2017-07-07)
217 |
218 | - Added `parse(_: String.UnicodeScalarView)` method for parsing expressions embedded in an interpolated string
219 | - Improved parsing of expressions containing ambiguous whitespace around operators
220 | - Fixed some more bugs in the expression description logic
221 | - Removed the deprecated `noCache` option
222 |
223 | ## [0.7.1](https://github.com/nicklockwood/Expression/releases/tag/0.7.1) (2017-07-05)
224 |
225 | - Made `clearCache()` method public (was previously left internal by accident)
226 | - Added additional hard-coded precedence for common operator types and names
227 | - Now supports right-associativity for assignment and comparison operators
228 | - Improved description logic, now correctly handles nested prefix/postfix operators
229 | - Added support for infix alphanumeric operators, in addition to postfix
230 | - Fixed bug when parsing a binary `?:` operator
231 | - Swift 4 compatibility fixes
232 |
233 | ## [0.7.0](https://github.com/nicklockwood/Expression/releases/tag/0.7.0) (2017-06-03)
234 |
235 | - Significantly improved evaluation performance of by storing functions inline inside the parsed expression
236 | - Expressions can now contain quoted string literals, which are treated as identifiers (variable names)
237 | - Added `pureSymbols` optimization option, allowing custom functions and operators to be inlined where possible
238 | - Added deferred optimization, allowing functions that use a custom evaluator take advantage of optimization
239 | - Added `parse(_, usingCache:)` method for fine-grained control of caching and pre-parsing
240 | - The `clearCache()` method now optionally accepts a specific expression to be cleared
241 | - Deprecated the `noCache` option. Use the new `parse(_, usingCache:)` method instead
242 | - Added optimization guide to the README file
243 |
244 | ## [0.6.1](https://github.com/nicklockwood/Expression/releases/tag/0.6.1) (2017-05-28)
245 |
246 | - Fixed bug where optimizer stopped as soon as it encountered a custom symbol in the expression
247 |
248 | ## [0.6.0](https://github.com/nicklockwood/Expression/releases/tag/0.6.0) (2017-05-27)
249 |
250 | - BREAKING CHANGE: `constant` symbols have now been renamed to `variable` to more accurately reflect their behavior
251 | - Minor breaking change: `description` now returns the optimized description
252 | - Added built-in symbol library for boolean operations
253 | - Added thread-safe in-memory caching of previously parsed expressions
254 | - Improved optimizer - now pre-evaluates subexpressions with constant arguments
255 | - Added configuration options for enabling/disabling optimizations and boolean arguments
256 | - Added modulo `%` operator to the standard math symbol library
257 | - Added support for hexadecimal literals
258 |
259 | ## [0.5.0](https://github.com/nicklockwood/Expression/releases/tag/0.5.0) (2017-04-12)
260 |
261 | - Added support for multi-character operators, and precedence rules for most standard operators
262 | - Added special-case support for implementing a ternary `?:` operator with 3 arguments
263 | - Static constants are now replaced by their literal values at initialization time, reducing lookup overhead
264 | - Constant expressions are now computed once at initialization time and then cached
265 | - Numeric literals are now stored as Doubles instead of Strings, avoiding conversion overhead
266 | - Fixed bug where printing an expression omitted the parens around sub-expressions
267 | - Fixed crash when parsing a trailing postfix operator preceded by a space
268 | - Fixed bug in Colors example when running on 32-bit
269 |
270 | ## [0.4.0](https://github.com/nicklockwood/Expression/releases/tag/0.4.0) (2017-03-27)
271 |
272 | - You can now get all symbols used by an expression via the `symbols` property
273 | - Fixed crash with postfix operators followed by comma or closing paren
274 |
275 | ## [0.3](https://github.com/nicklockwood/Expression/releases/tag/0.3) (2017-01-04)
276 |
277 | - Fixed crash when processing malformed expression
278 | - Added support for Swift Package Manager and Linux
279 | - Updated for latest Xcode version
280 |
281 | ## [0.2](https://github.com/nicklockwood/Expression/releases/tag/0.2) (2016-10-15)
282 |
283 | - `Expression.init` no longer throws. The expression will still be compiled on init, but errors won't be thrown until first evaluation
284 | - Added optional `constants` and `symbols` arguments to `Expression.init` for simpler setup of custom functions and operators
285 | - Removed the `constants` param from the `evaluate()` function - this can now be provided in `Expression.init`
286 | - Added automatic error reporting for custom functions called with the wrong arity
287 | - Improved evaluation performance for built-in symbols
288 |
289 | ## [0.1](https://github.com/nicklockwood/Expression/releases/tag/0.1) (2016-10-01)
290 |
291 | - First release
292 |
--------------------------------------------------------------------------------
/Examples/Benchmark/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Benchmark
4 | //
5 | // Created by Nick Lockwood on 13/02/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/Benchmark/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Examples/Benchmark/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Examples/Benchmark/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Examples/Benchmark/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/Benchmark/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Benchmark
4 | //
5 | // Created by Nick Lockwood on 13/02/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Expression
10 | import JavaScriptCore
11 | import UIKit
12 |
13 | let parseRepetitions = 50
14 | let evalRepetitions = 50
15 |
16 | private func time(_ block: () -> Void) -> Double {
17 | let start = CFAbsoluteTimeGetCurrent()
18 | block()
19 | return CFAbsoluteTimeGetCurrent() - start
20 | }
21 |
22 | private func time(_ setup: () -> Any, _ block: (Any) -> Void) -> Double {
23 | let value = setup()
24 | let start = CFAbsoluteTimeGetCurrent()
25 | block(value)
26 | return CFAbsoluteTimeGetCurrent() - start
27 | }
28 |
29 | let formatter: NumberFormatter = {
30 | let formatter = NumberFormatter()
31 | formatter.groupingSeparator = ","
32 | formatter.numberStyle = .decimal
33 | return formatter
34 | }()
35 |
36 | class ViewController: UITableViewController {
37 | var results: [(String, [[(String, Double)]])] = []
38 |
39 | @objc func update() {
40 | results = [
41 | ("End-to-end (x\(parseRepetitions))", [
42 | [
43 | ("Short Expressions", time {
44 | _ = evaluateExpressions(shortExpressions)
45 | }),
46 | ("Short AnyExpressions", time {
47 | _ = evaluateAnyExpressions(shortExpressions)
48 | }),
49 | ("Short NSExpressions", time {
50 | _ = evaluateNSExpressions(shortNSExpressions)
51 | }),
52 | ("Short JS Expressions", time {
53 | _ = evaluateJSExpressions(shortExpressions)
54 | }),
55 | ],
56 | [
57 | ("Medium Expressions", time {
58 | _ = evaluateExpressions(mediumExpressions)
59 | }),
60 | ("Medium AnyExpressions", time {
61 | _ = evaluateAnyExpressions(mediumExpressions)
62 | }),
63 | ("Medium NSExpressions", time {
64 | _ = evaluateNSExpressions(mediumNSExpressions)
65 | }),
66 | ("Medium JS Expressions", time {
67 | _ = evaluateJSExpressions(mediumExpressions)
68 | }),
69 | ],
70 | [
71 | ("Long Expressions", time {
72 | _ = evaluateExpressions(longExpressions)
73 | }),
74 | ("Long AnyExpressions", time {
75 | _ = evaluateAnyExpressions(longExpressions)
76 | }),
77 | ("Long NSExpressions", time {
78 | _ = evaluateNSExpressions(longNSExpressions)
79 | }),
80 | ("Long JS Expressions", time {
81 | _ = evaluateJSExpressions(longExpressions)
82 | }),
83 | ],
84 | ]),
85 | ("Setup (x\(parseRepetitions))", [
86 | [
87 | ("Short Expressions", time {
88 | _ = buildExpressions(shortExpressions)
89 | }),
90 | ("Short AnyExpressions", time {
91 | _ = buildAnyExpressions(shortExpressions)
92 | }),
93 | ("Short NSExpressions", time {
94 | _ = buildNSExpressions(shortNSExpressions)
95 | }),
96 | ("Short JS Expressions", time {
97 | _ = buildJSExpressions(shortExpressions)
98 | }),
99 | ],
100 | [
101 | ("Medium Expressions", time {
102 | _ = buildExpressions(mediumExpressions)
103 | }),
104 | ("Medium AnyExpressions", time {
105 | _ = buildAnyExpressions(mediumExpressions)
106 | }),
107 | ("Medium NSExpressions", time {
108 | _ = buildNSExpressions(mediumNSExpressions)
109 | }),
110 | ("Medium JS Expressions", time {
111 | _ = buildJSExpressions(mediumExpressions)
112 | }),
113 | ],
114 | [
115 | ("Long Expressions", time {
116 | _ = buildExpressions(longExpressions)
117 | }),
118 | ("Long AnyExpressions", time {
119 | _ = buildAnyExpressions(longExpressions)
120 | }),
121 | ("Long NSExpressions", time {
122 | _ = buildNSExpressions(longNSExpressions)
123 | }),
124 | ("Long JS Expressions", time {
125 | _ = buildJSExpressions(longExpressions)
126 | }),
127 | ],
128 | ]),
129 | ("Evaluation (x\(evalRepetitions))", [
130 | [
131 | ("Short Expressions", time(
132 | { buildExpressions(shortExpressions) },
133 | { _ = evaluateExpressions($0 as! [Expression]) }
134 | )),
135 | ("Short AnyExpressions", time(
136 | { buildAnyExpressions(shortExpressions) },
137 | { _ = evaluateAnyExpressions($0 as! [AnyExpression]) }
138 | )),
139 | ("Short NSExpressions", time(
140 | { buildNSExpressions(shortNSExpressions) },
141 | { _ = evaluateNSExpressions($0 as! [NSExpression]) }
142 | )),
143 | ("Short JS Expressions", time(
144 | { buildJSExpressions(shortExpressions) },
145 | { _ = evaluateJSExpressions($0 as! [() -> JSValue]) }
146 | )),
147 | ],
148 | [
149 | ("Medium Expressions", time(
150 | { buildExpressions(mediumExpressions) },
151 | { _ = evaluateExpressions($0 as! [Expression]) }
152 | )),
153 | ("Medium AnyExpressions", time(
154 | { buildAnyExpressions(mediumExpressions) },
155 | { _ = evaluateAnyExpressions($0 as! [AnyExpression]) }
156 | )),
157 | ("Medium NSExpressions", time(
158 | { buildNSExpressions(mediumNSExpressions) },
159 | { _ = evaluateNSExpressions($0 as! [NSExpression]) }
160 | )),
161 | ("Medium JS Expressions", time(
162 | { buildJSExpressions(mediumExpressions) },
163 | { _ = evaluateJSExpressions($0 as! [() -> JSValue]) }
164 | )),
165 | ],
166 | [
167 | ("Long Expressions", time(
168 | { buildExpressions(longExpressions) },
169 | { _ = evaluateExpressions($0 as! [Expression]) }
170 | )),
171 | ("Long AnyExpressions", time(
172 | { buildAnyExpressions(longExpressions) },
173 | { _ = evaluateAnyExpressions($0 as! [AnyExpression]) }
174 | )),
175 | ("Long NSExpressions", time(
176 | { buildNSExpressions(longNSExpressions) },
177 | { _ = evaluateNSExpressions($0 as! [NSExpression]) }
178 | )),
179 | ("Long JS Expressions", time(
180 | { buildJSExpressions(longExpressions) },
181 | { _ = evaluateJSExpressions($0 as! [() -> JSValue]) }
182 | )),
183 | ],
184 | ]),
185 | ]
186 | tableView.reloadData()
187 | refreshControl?.endRefreshing()
188 | }
189 |
190 | override func viewDidLoad() {
191 | super.viewDidLoad()
192 | refreshControl = UIRefreshControl()
193 | refreshControl?.addTarget(self, action: #selector(update), for: .valueChanged)
194 | refreshControl?.beginRefreshing()
195 | update()
196 | }
197 |
198 | override func numberOfSections(in _: UITableView) -> Int {
199 | return results.count
200 | }
201 |
202 | override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
203 | return results[section].1.flatMap { $0 }.count
204 | }
205 |
206 | override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
207 | return results[section].0
208 | }
209 |
210 | override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
211 | let subsections = results[indexPath.section].1
212 | let row = subsections.flatMap { $0 }[indexPath.row]
213 | let cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
214 |
215 | let subsection = subsections[Int(indexPath.row / subsections[0].count)]
216 | let useMS = !subsection.contains(where: { $0.1 < 0.001 })
217 |
218 | cell.textLabel?.text = row.0
219 | cell.detailTextLabel?.text = useMS ?
220 | "\(formatter.string(from: Int(row.1 * 1000) as NSNumber)!)ms" :
221 | "\(formatter.string(from: Int(row.1 * 1000000) as NSNumber)!)µs"
222 |
223 | cell.detailTextLabel?.textColor = {
224 | if !subsection.contains(where: { $0.1 > row.1 }) {
225 | return .red // worst
226 | } else if !subsection.contains(where: { $0.1 < row.1 }) {
227 | return UIColor(red: 0, green: 0.75, blue: 0, alpha: 1) // best
228 | } else {
229 | return .gray
230 | }
231 | }()
232 | return cell
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/Examples/Calculator/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Calculator
4 | //
5 | // Created by Nick Lockwood on 17/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import UIKit
33 |
34 | @UIApplicationMain
35 | class AppDelegate: UIResponder, UIApplicationDelegate {
36 | var window: UIWindow?
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/Calculator/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Examples/Calculator/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Calculator/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Examples/Calculator/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/Calculator/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Calculator
4 | //
5 | // Created by Nick Lockwood on 17/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Expression
33 | import UIKit
34 |
35 | class ViewController: UIViewController, UITextFieldDelegate {
36 | @IBOutlet private var inputField: UITextField!
37 | @IBOutlet private var outputView: UITextView!
38 |
39 | private var output = NSMutableAttributedString()
40 |
41 | private func addOutput(_ string: String, color: UIColor) {
42 | let text = NSAttributedString(string: string + "\n\n", attributes: [
43 | NSAttributedStringKey.foregroundColor: color,
44 | NSAttributedStringKey.font: outputView.font!,
45 | ])
46 |
47 | output.replaceCharacters(in: NSMakeRange(0, 0), with: text)
48 | outputView.attributedText = output
49 | }
50 |
51 | override func viewDidLoad() {
52 | super.viewDidLoad()
53 | output.append(outputView.attributedText)
54 | }
55 |
56 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
57 | if let text = textField.text, text != "" {
58 | do {
59 | let result = try Expression(text).evaluate()
60 | addOutput(String(format: "= %g", result), color: .black)
61 | } catch {
62 | addOutput("\(error)", color: .red)
63 | }
64 | }
65 | return false
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Examples/Colors/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Colors
4 | //
5 | // Created by Nick Lockwood on 30/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 | }
15 |
--------------------------------------------------------------------------------
/Examples/Colors/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Examples/Colors/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Colors/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
32 |
37 |
44 |
51 |
58 |
65 |
72 |
79 |
86 |
93 |
100 |
107 |
114 |
121 |
128 |
135 |
142 |
149 |
156 |
163 |
170 |
177 |
184 |
191 |
198 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/Examples/Colors/ColorLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorLabel.swift
3 | // Colors
4 | //
5 | // Created by Nick Lockwood on 30/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ColorLabel: UILabel {
12 | private func updateColor() {
13 | do {
14 | backgroundColor = .clear
15 | textColor = try UIColor(expression: text ?? "")
16 | } catch {
17 | backgroundColor = .red
18 | textColor = .black
19 | }
20 | }
21 |
22 | override var text: String? {
23 | get { return super.text }
24 | set {
25 | super.text = newValue
26 | updateColor()
27 | }
28 | }
29 |
30 | required init?(coder aDecoder: NSCoder) {
31 | super.init(coder: aDecoder)
32 | updateColor()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/Colors/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeRight
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Examples/Colors/UIColor+Expression.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Expression.swift
3 | // Colors
4 | //
5 | // Created by Nick Lockwood on 30/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Expression
10 | import UIKit
11 |
12 | private let colors: [String: UIColor] = [
13 | "red": .red,
14 | "green": .green,
15 | "blue": .blue,
16 | "yellow": .yellow,
17 | "purple": .purple,
18 | "cyan": .cyan,
19 | "pink": UIColor(rgba: 0xFF7F7FFF),
20 | "orange": .orange,
21 | "gray": .gray,
22 | "black": .black,
23 | "white": .white,
24 | ]
25 |
26 | private let functions: [AnyExpression.Symbol: AnyExpression.SymbolEvaluator] = [
27 | .function("rgb", arity: 3): { args in
28 | guard let r = args[0] as? Double,
29 | let g = args[1] as? Double,
30 | let b = args[2] as? Double
31 | else {
32 | throw AnyExpression.Error.message("Type mismatch")
33 | }
34 | return UIColor(
35 | red: CGFloat(r / 255),
36 | green: CGFloat(g / 255),
37 | blue: CGFloat(b / 255),
38 | alpha: 1
39 | )
40 | },
41 | .function("rgba", arity: 4): { args in
42 | guard let r = args[0] as? Double,
43 | let g = args[1] as? Double,
44 | let b = args[2] as? Double,
45 | let a = args[3] as? Double
46 | else {
47 | throw Expression.Error.message("Type mismatch")
48 | }
49 | return UIColor(
50 | red: CGFloat(r / 255),
51 | green: CGFloat(g / 255),
52 | blue: CGFloat(b / 255),
53 | alpha: CGFloat(a)
54 | )
55 | },
56 | ]
57 |
58 | public extension UIColor {
59 | convenience init(rgba: UInt32) {
60 | let red = CGFloat((rgba & 0xFF000000) >> 24) / 255
61 | let green = CGFloat((rgba & 0x00FF0000) >> 16) / 255
62 | let blue = CGFloat((rgba & 0x0000FF00) >> 8) / 255
63 | let alpha = CGFloat((rgba & 0x000000FF) >> 0) / 255
64 | self.init(red: red, green: green, blue: blue, alpha: alpha)
65 | }
66 |
67 | convenience init(expression: String) throws {
68 | let parsedExpression = Expression.parse(expression)
69 | var constants = [String: Any]()
70 |
71 | for symbol in parsedExpression.symbols {
72 | if case let .variable(name) = symbol {
73 | if name.hasPrefix("#") {
74 | var string = String(name.dropFirst())
75 | switch string.count {
76 | case 3:
77 | string += "f"
78 | fallthrough
79 | case 4:
80 | let chars = Array(string)
81 | let red = chars[0]
82 | let green = chars[1]
83 | let blue = chars[2]
84 | let alpha = chars[3]
85 | string = "\(red)\(red)\(green)\(green)\(blue)\(blue)\(alpha)\(alpha)"
86 | case 6:
87 | string += "ff"
88 | case 8:
89 | break
90 | default:
91 | // unsupported format
92 | continue
93 | }
94 | guard let rgba = Double("0x" + string).flatMap({ UInt32(exactly: $0) }) else {
95 | throw Expression.Error.message("Unsupported color format")
96 | }
97 | constants[name] = UIColor(rgba: rgba)
98 | } else if let color = colors[name.lowercased()] {
99 | constants[name] = color
100 | }
101 | }
102 | }
103 | let expression = AnyExpression(
104 | expression,
105 | constants: constants,
106 | symbols: functions
107 | )
108 | let color: UIColor = try expression.evaluate()
109 | self.init(cgColor: color.cgColor)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Examples/Colors/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Colors
4 | //
5 | // Created by Nick Lockwood on 30/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | // Do any additional setup after loading the view, typically from a nib.
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/Layout/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Layout
4 | //
5 | // Created by Nick Lockwood on 21/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import UIKit
33 |
34 | @UIApplicationMain
35 | class AppDelegate: UIResponder, UIApplicationDelegate {
36 | var window: UIWindow?
37 | }
38 |
--------------------------------------------------------------------------------
/Examples/Layout/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Examples/Layout/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/Layout/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/Examples/Layout/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/Layout/View+Layout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Layout.swift
3 | // Layout
4 | //
5 | // Created by Nick Lockwood on 21/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import Expression
33 | import UIKit
34 |
35 | private class LayoutData: NSObject {
36 | private weak var view: UIView!
37 | private var inProgress = Set()
38 |
39 | func computedValue(forKey key: String) throws -> Double {
40 | if inProgress.contains(key) {
41 | throw Expression.Error.message("Circular reference: \(key) depends on itself")
42 | }
43 | defer { inProgress.remove(key) }
44 | inProgress.insert(key)
45 |
46 | if let expression = props[key] {
47 | return try expression.evaluate()
48 | }
49 | switch key {
50 | case "right":
51 | return try computedValue(forKey: "left") + computedValue(forKey: "width")
52 | case "bottom":
53 | return try computedValue(forKey: "top") + computedValue(forKey: "height")
54 | default:
55 | throw Expression.Error.undefinedSymbol(.variable(key))
56 | }
57 | }
58 |
59 | private func common(_ symbol: Expression.Symbol) -> Expression.SymbolEvaluator? {
60 | switch symbol {
61 | case .variable("auto"):
62 | return { _ in throw Expression.Error.message("`auto` can only be used for width or height") }
63 | case let .variable(name):
64 | let parts = name.components(separatedBy: ".")
65 | if parts.count == 2 {
66 | return { [unowned self] _ in
67 | if let sublayout = self.view.window?.subview(forKey: parts[0])?.layout {
68 | return try sublayout.computedValue(forKey: parts[1])
69 | }
70 | throw Expression.Error.message("No view found for key `\(parts[0])`")
71 | }
72 | }
73 | return { [unowned self] _ in
74 | try self.computedValue(forKey: parts[0])
75 | }
76 | default:
77 | return nil
78 | }
79 | }
80 |
81 | var key: String?
82 | var left: String? {
83 | didSet {
84 | props["left"] = Expression(
85 | Expression.parse(left ?? "0"),
86 | impureSymbols: { symbol in
87 | switch symbol {
88 | case .postfix("%"):
89 | return { [unowned self] args in
90 | self.view.superview.map { Double($0.frame.width) / 100 * args[0] } ?? 0
91 | }
92 | default:
93 | return self.common(symbol)
94 | }
95 | }
96 | )
97 | }
98 | }
99 |
100 | var top: String? {
101 | didSet {
102 | props["top"] = Expression(
103 | Expression.parse(top ?? "0"),
104 | impureSymbols: { symbol in
105 | switch symbol {
106 | case .postfix("%"):
107 | return { [unowned self] args in
108 | self.view.superview.map { Double($0.frame.height) / 100 * args[0] } ?? 0
109 | }
110 | default:
111 | return self.common(symbol)
112 | }
113 | }
114 | )
115 | }
116 | }
117 |
118 | var width: String? {
119 | didSet {
120 | props["width"] = Expression(
121 | Expression.parse(width ?? "100%"),
122 | impureSymbols: { symbol in
123 | switch symbol {
124 | case .postfix("%"):
125 | return { [unowned self] args in
126 | self.view.superview.map { Double($0.frame.width) / 100 * args[0] } ?? 0
127 | }
128 | case .variable("auto"):
129 | return { [unowned self] _ in
130 | self.view.superview.map { superview in
131 | Double(self.view.systemLayoutSizeFitting(superview.frame.size).width)
132 | } ?? 0
133 | }
134 | default:
135 | return self.common(symbol)
136 | }
137 | }
138 | )
139 | }
140 | }
141 |
142 | var height: String? {
143 | didSet {
144 | props["height"] = Expression(
145 | Expression.parse(height ?? "100%"),
146 | impureSymbols: { symbol in
147 | switch symbol {
148 | case .postfix("%"):
149 | return { [unowned self] args in
150 | self.view.superview.map { Double($0.frame.height) / 100 * args[0] } ?? 0
151 | }
152 | case .variable("auto"):
153 | return { [unowned self] _ in
154 | try self.view.superview.map { superview in
155 | var size = superview.frame.size
156 | size.width = try CGFloat(self.computedValue(forKey: "width"))
157 | return Double(self.view.systemLayoutSizeFitting(size).height)
158 | } ?? 0
159 | }
160 | default:
161 | return self.common(symbol)
162 | }
163 | }
164 | )
165 | }
166 | }
167 |
168 | private var props: [String: Expression] = [:]
169 |
170 | init(_ view: UIView) {
171 | self.view = view
172 | left = nil
173 | top = nil
174 | width = nil
175 | height = nil
176 | }
177 | }
178 |
179 | @IBDesignable
180 | public extension UIView {
181 | fileprivate var layout: LayoutData? {
182 | return layout(create: false)
183 | }
184 |
185 | private func layout(create: Bool) -> LayoutData! {
186 | let layout = layer.value(forKey: "layout") as? LayoutData
187 | if layout == nil, create {
188 | let layout = LayoutData(self)
189 | layer.setValue(layout, forKey: "layout")
190 | return layout
191 | }
192 | return layout
193 | }
194 |
195 | @IBInspectable var key: String? {
196 | get { return layout?.key }
197 | set { layout(create: true).key = newValue }
198 | }
199 |
200 | @IBInspectable var left: String? {
201 | get { return layout?.left }
202 | set { layout(create: true).left = newValue }
203 | }
204 |
205 | @IBInspectable var top: String? {
206 | get { return layout?.top }
207 | set { layout(create: true).top = newValue }
208 | }
209 |
210 | @IBInspectable var width: String? {
211 | get { return layout?.width }
212 | set { layout(create: true).width = newValue }
213 | }
214 |
215 | @IBInspectable var height: String? {
216 | get { return layout?.height }
217 | set { layout(create: true).height = newValue }
218 | }
219 |
220 | fileprivate func subview(forKey key: String) -> UIView? {
221 | if self.key == key {
222 | return self
223 | }
224 | for view in subviews {
225 | if let match = view.subview(forKey: key) {
226 | return match
227 | }
228 | }
229 | return nil
230 | }
231 |
232 | func updateLayout() throws {
233 | guard let layout = layout(create: true) else {
234 | return
235 | }
236 | frame = try CGRect(x: layout.computedValue(forKey: "left"),
237 | y: layout.computedValue(forKey: "top"),
238 | width: layout.computedValue(forKey: "width"),
239 | height: layout.computedValue(forKey: "height"))
240 |
241 | for view in subviews {
242 | try view.updateLayout()
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/Examples/Layout/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Layout
4 | //
5 | // Created by Nick Lockwood on 21/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | import UIKit
33 |
34 | class ViewController: UIViewController, UITextFieldDelegate {
35 | @IBOutlet private var leftField: UITextField!
36 | @IBOutlet private var topField: UITextField!
37 | @IBOutlet private var widthField: UITextField!
38 | @IBOutlet private var heightField: UITextField!
39 | @IBOutlet private var errorLabel: UILabel!
40 | @IBOutlet private var layoutView: UIView!
41 |
42 | var selectedView: UIView? {
43 | didSet {
44 | oldValue?.layer.borderWidth = 0
45 | selectedView?.layer.borderWidth = 2
46 | selectedView?.layer.borderColor = UIColor.black.cgColor
47 | leftField.isEnabled = true
48 | leftField.text = selectedView?.left
49 | topField.isEnabled = true
50 | topField.text = selectedView?.top
51 | widthField.isEnabled = true
52 | widthField.text = selectedView?.width
53 | heightField.isEnabled = true
54 | heightField.text = selectedView?.height
55 | }
56 | }
57 |
58 | @IBAction func didTap(sender: UITapGestureRecognizer) {
59 | let point = sender.location(in: layoutView)
60 | if let view = layoutView.hitTest(point, with: nil), view != layoutView {
61 | selectedView = view
62 | }
63 | }
64 |
65 | override func viewDidLayoutSubviews() {
66 | super.viewDidLayoutSubviews()
67 | updateLayout()
68 | }
69 |
70 | func updateLayout() {
71 | do {
72 | for view in layoutView.subviews {
73 | try view.updateLayout()
74 | }
75 | errorLabel.text = nil
76 | } catch {
77 | errorLabel.text = "\(error)"
78 | }
79 | }
80 |
81 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
82 | textField.resignFirstResponder()
83 | return true
84 | }
85 |
86 | func textFieldDidEndEditing(_: UITextField) {
87 | selectedView?.left = leftField.text
88 | selectedView?.top = topField.text
89 | selectedView?.width = widthField.text
90 | selectedView?.height = heightField.text
91 | updateLayout()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Examples/REPL/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // REPL
4 | //
5 | // Created by Nick Lockwood on 23/02/2018.
6 | // Copyright © 2018 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // Prevent control characters confusing expression
12 | private let start = UnicodeScalar(63232)!
13 | private let end = UnicodeScalar(63235)!
14 | private let cursorCharacters = CharacterSet(charactersIn: start ... end)
15 |
16 | // Previously defined variables
17 | private var variables = [String: Any]()
18 |
19 | func evaluate(_ parsed: ParsedExpression) throws -> Any {
20 | let expression = AnyExpression(parsed, constants: variables)
21 | return try expression.evaluate()
22 | }
23 |
24 | while true {
25 | print("> ", terminator: "")
26 | guard var input = readLine() else { break }
27 | input = String(input.unicodeScalars.filter { !cursorCharacters.contains($0) })
28 | do {
29 | var parsed = Expression.parse(input)
30 | if parsed.symbols.contains(where: { $0 == .infix("=") || $0 == .prefix("=") }) {
31 | let range = input.range(of: " = ") ?? input.range(of: "= ") ?? input.range(of: "=")!
32 | parsed = Expression.parse(String(input[range.upperBound...]))
33 | let identifier = input[.. Any in
42 | try expression.evaluate()
43 | }
44 | default:
45 | print("error: Invalid variable name '\(identifier)'")
46 | }
47 | } else {
48 | print("error: Invalid left side for = expression: '\(identifier)'")
49 | }
50 | } else {
51 | try print(AnyExpression.stringify(evaluate(parsed)))
52 | }
53 | } catch {
54 | print("error: \(error)")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Expression.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Expression",
3 | "version": "0.13.9",
4 | "license": {
5 | "type": "MIT",
6 | "file": "LICENSE.md"
7 | },
8 | "summary": "Mac and iOS library for evaluating numeric expressions at runtime.",
9 | "homepage": "https://github.com/nicklockwood/Expression",
10 | "authors": "Nick Lockwood",
11 | "source": {
12 | "git": "https://github.com/nicklockwood/Expression.git",
13 | "tag": "0.13.9"
14 | },
15 | "source_files": "Sources",
16 | "requires_arc": true,
17 | "platforms": {
18 | "ios": "11.0",
19 | "osx": "10.13",
20 | "tvos": "11.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcbaselines/01DD38BB1ED630CB00F17F46.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | A544E583-C966-4A17-B7DE-008EFD1B4FC2
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2500
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro11,5
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone10,5
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcbaselines/01DD38D51ED63B1E00F17F46.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | ECC5D8A7-1C11-4DDD-9972-84D3DA20302D
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2500
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro11,5
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone10,5
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Benchmark.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Calculator.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Colors.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/DebugPerformance.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Expression (Mac).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Expression (iOS).xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/Layout.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Expression.xcodeproj/xcshareddata/xcschemes/ReleasePerformance.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nick Lockwood
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.2
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Expression",
6 | products: [
7 | .library(name: "Expression", targets: ["Expression"]),
8 | ],
9 | targets: [
10 | .target(name: "Expression", path: "Sources"),
11 | .testTarget(name: "ExpressionTests", dependencies: ["Expression"], path: "Tests"),
12 | ]
13 | )
14 |
--------------------------------------------------------------------------------
/Sources/Expression.h:
--------------------------------------------------------------------------------
1 | //
2 | // Expression.h
3 | // Expression
4 | //
5 | // Created by Nick Lockwood on 15/09/2016.
6 | // Copyright © 2016 Nick Lockwood. All rights reserved.
7 | //
8 | // Distributed under the permissive MIT license
9 | // Get the latest version from here:
10 | //
11 | // https://github.com/nicklockwood/Expression
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 | //
31 |
32 | #import
33 |
34 | //! Project version number for Expression.
35 | FOUNDATION_EXPORT double ExpressionVersionNumber;
36 |
37 | //! Project version string for Expression.
38 | FOUNDATION_EXPORT const unsigned char ExpressionVersionString[];
39 |
40 | // In this header, you should import all the public headers of your framework using statements like #import
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2016 Nick Lockwood. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleVersion
18 | 1
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Tests/MetadataTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetadataTests.swift
3 | // EuclidTests
4 | //
5 | // Created by Nick Lockwood on 04/07/2021.
6 | // Copyright © 2019 Nick Lockwood. All rights reserved.
7 | //
8 |
9 | @testable import Expression
10 | import XCTest
11 |
12 | private let projectDirectory = URL(fileURLWithPath: #file)
13 | .deletingLastPathComponent().deletingLastPathComponent()
14 |
15 | private let changelogURL = projectDirectory
16 | .appendingPathComponent("CHANGELOG.md")
17 |
18 | private let podspecURL = projectDirectory
19 | .appendingPathComponent("Expression.podspec.json")
20 |
21 | private let projectURL = projectDirectory
22 | .appendingPathComponent("Expression.xcodeproj")
23 | .appendingPathComponent("project.pbxproj")
24 |
25 | private let expressionVersion: String = {
26 | let string = try! String(contentsOf: projectURL)
27 | let start = string.range(of: "MARKETING_VERSION = ")!.upperBound
28 | let end = string.range(of: ";", range: start ..< string.endIndex)!.lowerBound
29 | return String(string[start ..< end])
30 | }()
31 |
32 | class MetadataTests: XCTestCase {
33 | // MARK: releases
34 |
35 | func testLatestVersionInChangelog() {
36 | let changelog = try! String(contentsOf: changelogURL, encoding: .utf8)
37 | XCTAssertTrue(changelog.contains("[\(expressionVersion)]"), "CHANGELOG.md does not mention latest release")
38 | XCTAssertTrue(
39 | changelog.contains("(https://github.com/nicklockwood/Expression/releases/tag/\(expressionVersion))"),
40 | "CHANGELOG.md does not include correct link for latest release"
41 | )
42 | }
43 |
44 | func testLatestVersionInPodspec() {
45 | let podspec = try! String(contentsOf: podspecURL, encoding: .utf8)
46 | XCTAssertTrue(
47 | podspec.contains("\"version\": \"\(expressionVersion)\""),
48 | "Podspec version does not match latest release"
49 | )
50 | XCTAssertTrue(
51 | podspec.contains("\"tag\": \"\(expressionVersion)\""),
52 | "Podspec tag does not match latest release"
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------