├── MathParser
├── .gitignore
├── Tests
│ ├── LinuxMain.swift
│ └── MathParserTests
│ │ ├── XCTestManifests.swift
│ │ ├── TestHelpers.swift
│ │ ├── RewriterTests.swift
│ │ ├── GroupingTests.swift
│ │ └── ExpressionTests.swift
└── Sources
│ └── MathParser
│ ├── Either.swift
│ ├── String.swift
│ ├── TokenExtractor.swift
│ ├── GroupedToken.swift
│ ├── RawToken.swift
│ ├── Function.swift
│ ├── Configuration.swift
│ ├── PeekingIterator.swift
│ ├── Double.swift
│ ├── OperatorTokenSet.swift
│ ├── DynamicResolution.swift
│ ├── FractionNumberExtractor.swift
│ ├── ResolvedToken.swift
│ ├── IdentifierExtractor.swift
│ ├── TokenCharacterBuffer.swift
│ ├── ExponentExtractor.swift
│ ├── SubstitutionExtensions.swift
│ ├── Operator.swift
│ ├── HexNumberExtractor.swift
│ ├── OctalNumberExtractor.swift
│ ├── Deprecations.swift
│ ├── OperatorExtractor.swift
│ ├── DecimalNumberExtractor.swift
│ ├── ExpressionRewriter.swift
│ ├── LocalizedNumberExtractor.swift
│ ├── Character.swift
│ ├── VariableExtractor.swift
│ ├── FunctionSet.swift
│ ├── Tokenizer.swift
│ ├── QuotedVariableExtractor.swift
│ ├── RewriteRule+Defaults.swift
│ ├── RewriteRule.swift
│ ├── Evaluator.swift
│ ├── MathParserErrors.swift
│ ├── Expression+Matching.swift
│ ├── Expression.swift
│ ├── TokenGrouper.swift
│ ├── Operator+Defaults.swift
│ ├── OperatorSet.swift
│ └── Expressionizer.swift
├── README.markdown
├── .gitignore
├── Demo.xcodeproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Demo
├── Demo.entitlements
├── AppDelegate.swift
├── AnalyzerViewController.swift
├── Info.plist
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── RawTokenAnalyzerViewController.swift
├── ResolvedTokenAnalyzerViewController.swift
├── AnalyzerFlowViewController.swift
├── AnalyzerFlowViewController.xib
├── ExpressionAnalyzerViewController.swift
├── GroupedTokenAnalyzerViewController.swift
├── VariableAnalyzerViewController.swift
├── DemoViewController.xib
├── DemoViewController.swift
├── RawTokenAnalyzerViewController.xib
├── ResolvedTokenAnalyzerViewController.xib
├── ExpressionAnalyzerViewController.xib
├── GroupedTokenAnalyzerViewController.xib
└── VariableAnalyzerViewController.xib
├── DDMathParser.podspec
├── LICENSE
├── Package.swift
└── Package@swift-5.1.swift
/MathParser/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | ## Documentation
2 |
3 | Please see [the DDMathParser wiki](https://github.com/davedelong/DDMathParser/wiki) for up-to-date documentation.
4 |
--------------------------------------------------------------------------------
/MathParser/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import MathParserTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += MathParserTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | **/*.pbxuser
3 | **/*.perspective*
4 | **/*.mode*
5 | .DS_Store
6 |
7 | **/xcuserdata
8 | **/**/xcuserdata
9 | .build
10 | .swiftpm
11 |
12 | # Project generated by SPM
13 | MathParser.xcodeproj/
14 |
--------------------------------------------------------------------------------
/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MathParser/Tests/MathParserTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(MathParserTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Either.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Either.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal enum Either {
12 | case value(T)
13 | case error(E)
14 | }
15 |
--------------------------------------------------------------------------------
/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demo/Demo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/24/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | public func evaluate(using evaluator: Evaluator = .default, _ substitutions: Substitutions = [:]) throws -> Double {
13 | return try evaluator.evaluate(Expression(string: self), substitutions: substitutions)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/TokenExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal protocol TokenExtractor {
12 |
13 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool
14 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/GroupedToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupedToken.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/13/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct GroupedToken {
12 | public enum Kind {
13 | case number(Double)
14 | case variable(String)
15 | case `operator`(Operator)
16 | case function(String, Array)
17 | case group(Array)
18 | }
19 |
20 | public let kind: Kind
21 | public let range: Range
22 | }
23 |
--------------------------------------------------------------------------------
/Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/20/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | var demoWindow: NSWindowController?
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 |
19 | let window = NSWindow(contentViewController: DemoViewController())
20 | window.title = "Demo"
21 | demoWindow = NSWindowController(window: window)
22 | demoWindow?.showWindow(self)
23 | }
24 |
25 | func applicationWillTerminate(_ aNotification: Notification) {
26 | // Insert code here to tear down your application
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/RawToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RawToken.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/12/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public class RawToken {
12 | public let string: String
13 | public let range: Range
14 |
15 | public init(string: String, range: Range) {
16 | self.string = string
17 | self.range = range
18 | }
19 | }
20 |
21 | public class HexNumberToken: RawToken { }
22 | public class OctalNumberToken: RawToken { }
23 | public class DecimalNumberToken: RawToken { }
24 | public class FractionNumberToken: RawToken { }
25 | public class LocalizedNumberToken: RawToken { }
26 | public class ExponentToken: RawToken { }
27 | public class VariableToken: RawToken { }
28 | public class OperatorToken: RawToken { }
29 | public class IdentifierToken: RawToken { }
30 |
--------------------------------------------------------------------------------
/DDMathParser.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'DDMathParser'
3 | s.version = '2.0.0'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'String → Number'
6 | s.description = <<-DESC
7 | An extensible and flexible library to parse a string
8 | as a mathematical expression and evaluate it.
9 | DESC
10 | s.homepage = 'https://github.com/davedelong/DDMathParser'
11 | s.social_media_url = 'https://twitter.com/davedelong'
12 | s.authors = { 'Dave DeLong' => 'me@davedelong.com' }
13 | s.source = { :git => 'https://github.com/davedelong/DDMathParser.git', :tag => s.version }
14 |
15 | s.ios.deployment_target = '8.0'
16 | s.osx.deployment_target = '10.9'
17 | s.watchos.deployment_target = '2.0'
18 |
19 | s.source_files = 'MathParser/Sources/MathParser/*.{h,m,swift}'
20 |
21 | s.requires_arc = true
22 | s.module_name = 'MathParser'
23 | end
24 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Function.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Function.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 9/17/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct EvaluationState {
12 | public let expressionRange: Range
13 | public let arguments: Array
14 | public let substitutions: Substitutions
15 | public let evaluator: Evaluator
16 | }
17 |
18 | public typealias FunctionEvaluator = (EvaluationState) throws -> Double
19 |
20 | public struct Function {
21 |
22 | public let names: Set
23 | public let evaluator: FunctionEvaluator
24 |
25 | public init(name: String, evaluator: @escaping FunctionEvaluator) {
26 | self.names = [name]
27 | self.evaluator = evaluator
28 | }
29 |
30 | public init(names: Set, evaluator: @escaping FunctionEvaluator) {
31 | self.names = names
32 | self.evaluator = evaluator
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Demo/AnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnalyzerViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | protocol AnalyzerDelegate: class {
12 |
13 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsHighlightedRanges ranges: Array>)
14 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsErrorPresented error: MathParserError?)
15 | }
16 |
17 | class AnalyzerViewController: NSViewController {
18 |
19 | weak var analyzerDelegate: AnalyzerDelegate?
20 |
21 | init() {
22 | let nibName = NSNib.Name("\(type(of: self))")
23 | let bundle = Bundle(for: type(of: self))
24 | super.init(nibName: nibName, bundle: bundle)
25 | }
26 |
27 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
28 |
29 | func analyzeString(_ string: String) {
30 | fatalError("Subclasses must override \(#function)")
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Configuration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configuration.swift
3 | // MathParser
4 | //
5 | // Created by Dave DeLong on 4/18/19.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct Configuration {
11 |
12 | public static let `default` = Configuration()
13 |
14 | public var operatorSet: OperatorSet
15 | public var locale: Locale?
16 |
17 | public var allowZeroLengthVariables: Bool
18 | public var unescapesQuotedVariables: Bool
19 |
20 | public var allowArgumentlessFunctions: Bool
21 | public var allowImplicitMultiplication: Bool
22 | public var useHighPrecedenceImplicitMultiplication: Bool
23 |
24 | public init() {
25 | operatorSet = OperatorSet.default
26 | locale = nil
27 |
28 | allowZeroLengthVariables = false
29 | unescapesQuotedVariables = true
30 |
31 | allowArgumentlessFunctions = true
32 | allowImplicitMultiplication = true
33 | useHighPrecedenceImplicitMultiplication = true
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSMainNibFile
26 | MainMenu
27 | NSPrincipalClass
28 | NSApplication
29 |
30 |
31 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/PeekingIterator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PeekingIterator.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/14/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | protocol PeekingIteratorType: IteratorProtocol {
12 | func peek() -> Element?
13 | }
14 |
15 | internal final class PeekingIterator: PeekingIteratorType {
16 | typealias Element = G.Element
17 |
18 | private var generator: G
19 | private var peekBuffer = Array()
20 |
21 | init(generator: G) {
22 | self.generator = generator
23 | }
24 |
25 | func next() -> Element? {
26 | if let n = peekBuffer.first {
27 | peekBuffer.removeFirst()
28 | return n
29 | }
30 |
31 | return generator.next()
32 | }
33 |
34 | func peek() -> Element? {
35 | if let p = peekBuffer.first { return p }
36 |
37 | if let p = generator.next() {
38 | peekBuffer.append(p)
39 | return p
40 | }
41 |
42 | return nil
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2015 Dave DeLong
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "MathParser",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "MathParser",
12 | targets: ["MathParser"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "MathParser",
23 | dependencies: [],
24 | path: "MathParser/Sources/MathParser"),
25 | .testTarget(
26 | name: "MathParserTests",
27 | dependencies: ["MathParser"],
28 | path: "MathParser/Tests/MathParserTests"),
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/Package@swift-5.1.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "MathParser",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "MathParser",
12 | targets: ["MathParser"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "MathParser",
23 | dependencies: [],
24 | path: "MathParser/Sources/MathParser"),
25 | .testTarget(
26 | name: "MathParserTests",
27 | dependencies: ["MathParser"],
28 | path: "MathParser/Tests/MathParserTests"),
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Double.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 9/5/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal extension Int {
12 |
13 | static let largestSupportedIntegerFactorial: Int = {
14 | var n = Int.max
15 | var i = 2
16 | while i < n {
17 | n /= i
18 | i += 1
19 | }
20 | return i - 1
21 | }()
22 |
23 | }
24 |
25 | internal extension Double {
26 |
27 | func factorial() -> Double {
28 | if Darwin.floor(self) == self && self > 1 {
29 | // it's a factorial of an integer
30 |
31 | if self <= Double(Int.largestSupportedIntegerFactorial) {
32 | // it's a factorial of an integer representable as an Int
33 | let arg1Int = Int(self)
34 | return Double((1...arg1Int).reduce(1, *))
35 | } else if self < Double(Int.max) {
36 | // it's a factorial of an integer NOT representable as an Int
37 | var result = 1.0
38 | for i in 2 ... Int(self) {
39 | result *= Double(i)
40 | }
41 | return result
42 | }
43 | }
44 | return tgamma(self+1)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/OperatorTokenSet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OperatorTokenSet.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct OperatorTokenSet {
12 | private let characters: Set
13 | private let tokens: Set
14 |
15 | init(tokens: Set) {
16 | var characters = Set()
17 | var normalizedTokens = Set()
18 |
19 | for token in tokens {
20 | let lower = token.lowercased()
21 |
22 | normalizedTokens.insert(token)
23 | normalizedTokens.insert(lower)
24 |
25 | characters.formUnion(token)
26 | characters.formUnion(lower)
27 | }
28 |
29 | self.characters = characters
30 | self.tokens = normalizedTokens
31 | }
32 |
33 | func isOperatorCharacter(_ c: Character) -> Bool {
34 | if c.isAlphabetic { return false }
35 | return characters.contains(c)
36 | }
37 |
38 | func isOperatorToken(_ s: String) -> Bool {
39 | return tokens.contains(s)
40 | }
41 |
42 | func hasOperatorWithPrefix(_ s: String) -> Bool {
43 | // TODO: make this more efficient
44 | let matching = tokens.filter { $0.hasPrefix(s) }
45 | return !matching.isEmpty
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/DynamicResolution.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicResolution.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/24/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol FunctionOverrider {
12 | func overrideFunction(_ function: String, state: EvaluationState) throws -> Double?
13 | }
14 |
15 | public protocol FunctionResolver {
16 | func resolveFunction(_ function: String, state: EvaluationState) throws -> Double?
17 | }
18 |
19 | public protocol VariableResolver {
20 | func resolveVariable(_ variable: String) -> Double?
21 | }
22 |
23 | public protocol Substitution {
24 | func substitutionValue(using evaluator: Evaluator, substitutions: Substitutions) throws -> Double
25 | func substitutionValue(using evaluator: Evaluator) throws -> Double
26 |
27 | func simplified(using evaluator: Evaluator, substitutions: Substitutions) -> Substitution
28 | func simplified(using evaluator: Evaluator) -> Substitution
29 | }
30 |
31 | public extension Substitution {
32 | func substitutionValue(using evaluator: Evaluator) throws -> Double {
33 | return try substitutionValue(using: evaluator, substitutions: [:])
34 | }
35 |
36 | func simplified(using evaluator: Evaluator) -> Substitution {
37 | return simplified(using: evaluator, substitutions: [:])
38 | }
39 | }
40 |
41 | public typealias Substitutions = Dictionary
42 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/FractionNumberExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FractionNumberExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/30/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct FractionNumberExtractor: TokenExtractor {
12 |
13 | internal static let fractions: Dictionary = [
14 | "½": 0.5,
15 | "⅓": 0.3333333,
16 | "⅔": 0.6666666,
17 | "¼": 0.25,
18 | "¾": 0.75,
19 | "⅕": 0.2,
20 | "⅖": 0.4,
21 | "⅗": 0.6,
22 | "⅘": 0.8,
23 | "⅙": 0.1666666,
24 | "⅚": 0.8333333,
25 | "⅛": 0.125,
26 | "⅜": 0.375,
27 | "⅝": 0.625,
28 | "⅞": 0.875
29 | ]
30 |
31 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
32 | guard let peek = buffer.peekNext() else { return false }
33 | guard let _ = FractionNumberExtractor.fractions[peek] else { return false }
34 | return true
35 | }
36 |
37 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
38 | let start = buffer.currentIndex
39 |
40 | // consume the character
41 | buffer.consume()
42 |
43 | let range: Range = start ..< buffer.currentIndex
44 | let raw = buffer[range]
45 | return .value(FractionNumberToken(string: raw, range: range))
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/ResolvedToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResolvedToken.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/8/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ResolvedToken {
12 | public enum Kind {
13 | case number(Double)
14 | case variable(String)
15 | case identifier(String)
16 | case `operator`(Operator)
17 | }
18 |
19 | public let kind: Kind
20 | public let string: String
21 | public let range: Range
22 | }
23 |
24 | extension ResolvedToken.Kind {
25 |
26 | public var number: Double? {
27 | guard case .number(let o) = self else { return nil }
28 | return o
29 | }
30 |
31 | public var variable: String? {
32 | guard case .variable(let v) = self else { return nil }
33 | return v
34 | }
35 |
36 | public var identifier: String? {
37 | guard case .identifier(let i) = self else { return nil }
38 | return i
39 | }
40 |
41 | public var resolvedOperator: Operator? {
42 | guard case .operator(let o) = self else { return nil }
43 | return o
44 | }
45 |
46 | public var builtInOperator: BuiltInOperator? {
47 | return resolvedOperator?.builtInOperator
48 | }
49 |
50 | public var isNumber: Bool { return number != nil }
51 | public var isVariable: Bool { return variable != nil }
52 | public var isIdentifier: Bool { return identifier != nil }
53 | public var isOperator: Bool { return resolvedOperator != nil }
54 | }
55 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/IdentifierExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IdentifierExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct IdentifierExtractor: TokenExtractor {
12 | let operatorTokens: OperatorTokenSet
13 |
14 | init(operatorTokens: OperatorTokenSet) {
15 | self.operatorTokens = operatorTokens
16 | }
17 |
18 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
19 | // An identifier can't start with these, because other things already do
20 | let next = buffer.peekNext()
21 | return next != "$" && next?.isDigit == false && next != "\"" && next != "'"
22 | }
23 |
24 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
25 | let start = buffer.currentIndex
26 |
27 | while let next = buffer.peekNext(), next.isWhitespace == false && operatorTokens.isOperatorCharacter(next) == false {
28 | buffer.consume()
29 | }
30 |
31 | let range: Range = start ..< buffer.currentIndex
32 | let result: Tokenizer.Result
33 |
34 | if buffer.currentIndex - start > 0 {
35 | let raw = buffer[range]
36 | result = .value(IdentifierToken(string: raw, range: range))
37 | } else {
38 | let error = MathParserError(kind: .cannotParseIdentifier, range: range)
39 | result = .error(error)
40 | }
41 |
42 | return result
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/TokenCharacterBuffer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenCharacterBuffer.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal final class TokenCharacterBuffer {
12 | private let characters: Array
13 | private let lowercaseCharacters: Array
14 | private(set) var currentIndex: Int
15 |
16 | init(string: String) {
17 | characters = Array(string)
18 | lowercaseCharacters = Array(string.lowercased())
19 |
20 | currentIndex = 0
21 | }
22 |
23 | var isAtEnd: Bool {
24 | return currentIndex >= characters.count
25 | }
26 |
27 | func resetTo(_ index: Int) {
28 | currentIndex = index
29 | }
30 |
31 | func peekNext(_ delta: Int = 0, lowercase: Bool = false) -> Character? {
32 | guard delta >= 0 else {
33 | fatalError("Cannot peek into the past")
34 | }
35 | let chars = lowercase ? lowercaseCharacters : characters
36 |
37 | let index = currentIndex + delta
38 | guard index < chars.count else { return nil }
39 | return chars[index]
40 | }
41 |
42 | func consume(_ delta: Int = 1) {
43 | guard delta > 0 else {
44 | fatalError("Cannot consume less than one character")
45 | }
46 | currentIndex = currentIndex + delta
47 | }
48 |
49 | subscript (i: Int) -> Character {
50 | return characters[i]
51 | }
52 |
53 | subscript (r: Range) -> String {
54 | return String(characters[r])
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/ExponentExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExponentExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/30/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct ExponentExtractor: TokenExtractor {
12 |
13 | internal static let exponentCharacters: Dictionary = [
14 | "⁰": "0",
15 | "¹": "1",
16 | "²": "2",
17 | "³": "3",
18 | "⁴": "4",
19 | "⁵": "5",
20 | "⁶": "6",
21 | "⁷": "7",
22 | "⁸": "8",
23 | "⁹": "9",
24 | "⁺": "+",
25 | "⁻": "-",
26 | "⁽": "(",
27 | "⁾": ")"
28 | ]
29 |
30 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
31 | guard let peek = buffer.peekNext() else { return false }
32 | guard let _ = ExponentExtractor.exponentCharacters[peek] else { return false }
33 | return true
34 | }
35 |
36 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
37 | let start = buffer.currentIndex
38 |
39 | var exponent = ""
40 | while let peek = buffer.peekNext(), let regular = ExponentExtractor.exponentCharacters[peek] {
41 | buffer.consume()
42 | exponent.append(regular)
43 | }
44 |
45 | let length = buffer.currentIndex - start
46 | let range: Range = start ..< buffer.currentIndex
47 |
48 | if length > 0 {
49 | return .value(ExponentToken(string: exponent, range: range))
50 | } else {
51 | let error = MathParserError(kind: .cannotParseExponent, range: range)
52 | return .error(error)
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/SubstitutionExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubstitutionExtensions.swift
3 | // DDMathParser
4 | //
5 | // Created by Florian Friedrich on 29.04.17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension Double: Substitution {
12 | public func substitutionValue(using evaluator: Evaluator, substitutions: Substitutions) throws -> Double {
13 | return self
14 | }
15 |
16 | public func simplified(using evaluator: Evaluator, substitutions: Substitutions) -> Substitution {
17 | return self
18 | }
19 | }
20 |
21 | extension Int: Substitution {
22 | public func substitutionValue(using evaluator: Evaluator, substitutions: Substitutions) throws -> Double {
23 | return Double(self)
24 | }
25 |
26 | public func simplified(using evaluator: Evaluator, substitutions: Substitutions) -> Substitution {
27 | return Double(self)
28 | }
29 | }
30 |
31 | extension String: Substitution {
32 | public func substitutionValue(using evaluator: Evaluator, substitutions: Substitutions) throws -> Double {
33 | return try evaluate(using: evaluator, substitutions)
34 | }
35 |
36 | public func simplified(using evaluator: Evaluator, substitutions: Substitutions) -> Substitution {
37 | // If it's just a Double as String -> Return it as such
38 | if let double = Double(self) {
39 | return double
40 | }
41 |
42 | // If it can be expressed as exression, return the simplified expression
43 | if let exp = try? Expression(string: self) {
44 | return exp.simplified(using: evaluator, substitutions: substitutions)
45 | }
46 |
47 | // If it's neither, return self. This will likely fail in evaluation later.
48 | return self
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Operator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Operator.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public final class Operator: Equatable {
12 |
13 | public enum Arity {
14 | case unary
15 | case binary
16 |
17 | internal var argumentCount: Int {
18 | switch self {
19 | case .unary: return 1
20 | case .binary: return 2
21 | }
22 | }
23 | }
24 |
25 | public enum Associativity {
26 | case left
27 | case right
28 | }
29 |
30 | public let function: String
31 | public let arity: Arity
32 | public let associativity: Associativity
33 |
34 | public internal(set) var tokens: Set
35 | public internal(set) var precedence: Int?
36 |
37 | public init(function: String, arity: Arity, associativity: Associativity, tokens: Set = []) {
38 | self.function = function
39 | self.arity = arity
40 | self.associativity = associativity
41 | self.tokens = tokens
42 | }
43 | }
44 |
45 | extension Operator: CustomStringConvertible {
46 |
47 | public var description: String {
48 | let tokenInfo = tokens.joined(separator: ", ")
49 | let arityInfo = arity == .unary ? "Unary" : "Binary"
50 | let assocInfo = associativity == .left ? "Left" : "Right"
51 | let precedenceInfo = precedence?.description ?? "UNKNOWN"
52 | return "{[\(tokenInfo)] -> \(function)(), \(arityInfo) \(assocInfo), precedence: \(precedenceInfo)}"
53 | }
54 | }
55 |
56 | public func ==(lhs: Operator, rhs: Operator) -> Bool {
57 | return lhs.arity == rhs.arity && lhs.associativity == rhs.associativity && lhs.function == rhs.function
58 | }
59 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/HexNumberExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HexNumberExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct HexNumberExtractor: TokenExtractor {
12 |
13 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
14 | return buffer.peekNext() == "0" && buffer.peekNext(1, lowercase: true) == "x"
15 | }
16 |
17 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
18 | let start = buffer.currentIndex
19 |
20 | guard buffer.peekNext() == "0" && buffer.peekNext(1, lowercase: true) == "x" else {
21 | let error = MathParserError(kind: .cannotParseHexNumber, range: start ..< start)
22 | return .error(error)
23 | }
24 |
25 |
26 | buffer.consume(2) // 0x
27 |
28 | let indexBeforeHexNumbers = buffer.currentIndex
29 | while buffer.peekNext()?.isHexDigit == true {
30 | buffer.consume()
31 | }
32 |
33 | if buffer.currentIndex == indexBeforeHexNumbers {
34 | // there wasn't anything after 0[xX]
35 | buffer.resetTo(start)
36 | }
37 |
38 | let result: Tokenizer.Result
39 |
40 | if buffer.currentIndex - start > 0 {
41 | let range: Range = indexBeforeHexNumbers ..< buffer.currentIndex
42 | let raw = buffer[range]
43 | result = .value(HexNumberToken(string: raw, range: range))
44 | } else {
45 | let range: Range = start ..< buffer.currentIndex
46 | let error = MathParserError(kind: .cannotParseHexNumber, range: range)
47 | result = .error(error)
48 | }
49 |
50 | return result
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Demo/RawTokenAnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RawTokenAnalysisViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class RawTokenAnalyzerViewController: AnalyzerViewController, NSTableViewDelegate, NSTableViewDataSource {
12 |
13 | @IBOutlet var tokenList: NSTableView?
14 |
15 | var tokens = Array()
16 |
17 | override init() {
18 | super.init()
19 | title = "Raw"
20 | }
21 |
22 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | tokenList?.reloadData()
27 | }
28 |
29 | override func analyzeString(_ string: String) {
30 | let tokenizer = Tokenizer(string: string)
31 | do {
32 | tokens = try tokenizer.tokenize()
33 | } catch let e as MathParserError {
34 | tokens = []
35 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
36 | } catch let other {
37 | fatalError("Unknown error parsing expression: \(other)")
38 | }
39 | tokenList?.reloadData()
40 | }
41 |
42 | func numberOfRows(in tableView: NSTableView) -> Int {
43 | return tokens.count
44 | }
45 |
46 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
47 | let token = tokens[row]
48 | return "\(token) - \"\(token.string)\" - range: \(token.range)"
49 | }
50 |
51 | func tableViewSelectionDidChange(_ notification: Notification) {
52 | let row = tokenList?.selectedRow ?? -1
53 | let ranges = (row >= 0) ? [tokens[row].range] : []
54 |
55 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: ranges)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/OctalNumberExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OctalNumberExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 3/3/16.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct OctalNumberExtractor: TokenExtractor {
12 |
13 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
14 | return buffer.peekNext() == "0" && buffer.peekNext(1, lowercase: true) == "o"
15 | }
16 |
17 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
18 | let start = buffer.currentIndex
19 |
20 | guard buffer.peekNext() == "0" && buffer.peekNext(1, lowercase: true) == "o" else {
21 | let error = MathParserError(kind: .cannotParseHexNumber, range: start ..< start)
22 | return .error(error)
23 | }
24 |
25 |
26 | buffer.consume(2) // 0o
27 |
28 | let indexBeforeOctalNumbers = buffer.currentIndex
29 | while buffer.peekNext()?.isOctalDigit == true {
30 | buffer.consume()
31 | }
32 |
33 | if buffer.currentIndex == indexBeforeOctalNumbers {
34 | // there wasn't anything after 0[oO]
35 | buffer.resetTo(start)
36 | }
37 |
38 | let result: Tokenizer.Result
39 |
40 | if buffer.currentIndex - start > 0 {
41 | let range: Range = indexBeforeOctalNumbers ..< buffer.currentIndex
42 | let raw = buffer[range]
43 | result = .value(OctalNumberToken(string: raw, range: range))
44 | } else {
45 | let range: Range = start ..< buffer.currentIndex
46 | let error = MathParserError(kind: .cannotParseOctalNumber, range: range)
47 | result = .error(error)
48 | }
49 |
50 | return result
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/MathParser/Tests/MathParserTests/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestHelpers.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/12/15.
6 | //
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 | import MathParser
12 |
13 | func XCTAssertNoThrows(_ expression: @autoclosure () throws -> Void, _ message: String = "", file: StaticString = #file, line: UInt = #line) -> Bool {
14 | var ok = false
15 | do {
16 | try expression()
17 | ok = true
18 | } catch let e {
19 | let failMessage = "Unexpected exception: \(e). \(message)"
20 | XCTFail(failMessage, file: file, line: line)
21 | }
22 | return ok
23 | }
24 |
25 | func XCTAssertNoThrows(_ expression: @autoclosure () throws -> T, _ message: String = "", file: StaticString = #file, line: UInt = #line) -> T? {
26 | var t: T? = nil
27 | do {
28 | t = try expression()
29 | } catch let e {
30 | let failMessage = "Unexpected exception: \(e). \(message)"
31 | XCTFail(failMessage, file: file, line: line)
32 | }
33 | return t
34 | }
35 |
36 | func XCTAssertThrows(_ expression: @autoclosure () throws -> T, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
37 | do {
38 | let _ = try expression()
39 | XCTFail("Expected thrown error", file: file, line: line)
40 | } catch _ {
41 | }
42 | }
43 |
44 | func TestString(_ string: String, value: Double, evaluator: Evaluator = Evaluator.default, file: StaticString = #file, line: UInt = #line) {
45 |
46 | guard let e = XCTAssertNoThrows(try Expression(string: string), file: file, line: line) else {
47 | return
48 | }
49 |
50 | guard let d = XCTAssertNoThrows(try evaluator.evaluate(e), file: file, line: line) else {
51 | return
52 | }
53 | XCTAssertEqual(d, value, accuracy: .ulpOfOne, file: file, line: line)
54 | }
55 |
56 | extension MathParser.Configuration {
57 | static let defaultWithEmptyOptions: Configuration = {
58 | var c = Configuration.default
59 | c.useHighPrecedenceImplicitMultiplication = false
60 | c.allowImplicitMultiplication = false
61 | c.allowArgumentlessFunctions = false
62 | return c
63 | }()
64 | }
65 |
--------------------------------------------------------------------------------
/Demo/ResolvedTokenAnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResolvedTokenAnalyzerViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class ResolvedTokenAnalyzerViewController: AnalyzerViewController, NSTableViewDelegate, NSTableViewDataSource {
12 |
13 | @IBOutlet var tokenList: NSTableView?
14 |
15 | var tokens = Array()
16 |
17 | override init() {
18 | super.init()
19 | title = "Resolved"
20 | }
21 |
22 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | tokenList?.reloadData()
27 | }
28 |
29 | override func analyzeString(_ string: String) {
30 | let resolver = TokenResolver(string: string)
31 | do {
32 | tokens = try resolver.resolve()
33 | } catch let e as MathParserError {
34 | tokens = []
35 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
36 | } catch let other {
37 | fatalError("Unknown error parsing expression: \(other)")
38 | }
39 | tokenList?.reloadData()
40 | }
41 |
42 | func numberOfRows(in tableView: NSTableView) -> Int {
43 | return tokens.count
44 | }
45 |
46 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
47 | let token = tokens[row]
48 |
49 | let kind: String
50 | switch token.kind {
51 | case .identifier(_): kind = "Identifier"
52 | case .number(_): kind = "Number"
53 | case .operator(_): kind = "Operator"
54 | case .variable(_): kind = "Variable"
55 | }
56 |
57 | return "\(kind) - \"\(token.string)\" - range: \(token.range)"
58 | }
59 |
60 | func tableViewSelectionDidChange(_ notification: Notification) {
61 | let row = tokenList?.selectedRow ?? -1
62 | let ranges = (row >= 0) ? [tokens[row].range] : []
63 |
64 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: ranges)
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Deprecations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deprecations.swift
3 | // MathParser
4 | //
5 | // Created by Dave DeLong on 4/18/19.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Tokenizer {
11 | @available(*, deprecated, renamed: "init(string:configuration:)")
12 | init(string: String, operatorSet: OperatorSet = OperatorSet.default, locale: Locale? = nil) {
13 | var c = Configuration.default
14 | c.operatorSet = operatorSet
15 | c.locale = locale
16 | self.init(string: string, configuration: c)
17 | }
18 | }
19 |
20 | @available(*, deprecated, message: "Use Configuration instead.")
21 | public struct TokenResolverOptions: OptionSet {
22 | public let rawValue: UInt
23 |
24 | public init(rawValue: UInt) {
25 | self.rawValue = rawValue
26 | }
27 |
28 | public static let none: TokenResolverOptions = []
29 | public static let allowArgumentlessFunctions = TokenResolverOptions(rawValue: 1 << 0)
30 | public static let allowImplicitMultiplication = TokenResolverOptions(rawValue: 1 << 1)
31 | public static let useHighPrecedenceImplicitMultiplication = TokenResolverOptions(rawValue: 1 << 2)
32 |
33 | public static let `default`: TokenResolverOptions = [.allowArgumentlessFunctions, .allowImplicitMultiplication, .useHighPrecedenceImplicitMultiplication]
34 | }
35 |
36 | public extension TokenResolver {
37 | @available(*, deprecated, renamed: "init(tokenizer:)")
38 | init(tokenizer: Tokenizer, options: TokenResolverOptions = TokenResolverOptions.default) {
39 | self.init(tokenizer: tokenizer)
40 | }
41 | }
42 |
43 | public extension Expression {
44 | @available(*, deprecated, renamed: "init(string:configuration:)")
45 | convenience init(string: String, operatorSet: OperatorSet = OperatorSet.default, options: TokenResolverOptions = TokenResolverOptions.default, locale: Locale? = nil) throws {
46 | var c = Configuration()
47 | c.operatorSet = operatorSet
48 | c.allowArgumentlessFunctions = options.contains(.allowArgumentlessFunctions)
49 | c.allowImplicitMultiplication = options.contains(.allowImplicitMultiplication)
50 | c.useHighPrecedenceImplicitMultiplication = options.contains(.useHighPrecedenceImplicitMultiplication)
51 | c.locale = locale
52 | try self.init(string: string, configuration: c)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/OperatorExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OperatorExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct OperatorExtractor: TokenExtractor {
12 | let operatorTokens: OperatorTokenSet
13 |
14 | init(operatorTokens: OperatorTokenSet) {
15 | self.operatorTokens = operatorTokens
16 | }
17 |
18 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
19 | guard let peek = buffer.peekNext() else { return false }
20 | return operatorTokens.hasOperatorWithPrefix(String(peek))
21 | }
22 |
23 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
24 | let start = buffer.currentIndex
25 |
26 | var lastGoodIndex = start
27 | var current = ""
28 |
29 | while let next = buffer.peekNext(lowercase: true) {
30 | current.append(next)
31 | if operatorTokens.hasOperatorWithPrefix(current) {
32 | buffer.consume()
33 |
34 | if operatorTokens.isOperatorToken(current) {
35 | lastGoodIndex = buffer.currentIndex
36 | }
37 | } else {
38 | break
39 | }
40 | }
41 |
42 | buffer.resetTo(lastGoodIndex)
43 |
44 | let range: Range = start ..< buffer.currentIndex
45 | let result: Tokenizer.Result
46 |
47 | if buffer[start].isAlphabetic && buffer.peekNext()?.isAlphabetic == true {
48 | // This operator starts with an alphabetic character and
49 | // the next character after it is also alphabetic, and not whitespace.
50 | // This *probably* isn't an operator, but is instead the beginning
51 | // of an identifier that happens to have the same prefix as an operator token.
52 | buffer.resetTo(start)
53 | }
54 |
55 | if buffer.currentIndex - start > 0 {
56 | let raw = buffer[range]
57 | result = .value(OperatorToken(string: raw, range: range))
58 | } else {
59 | let error = MathParserError(kind: .cannotParseOperator, range: range)
60 | result = .error(error)
61 | }
62 |
63 | return result
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/DecimalNumberExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DecimalNumberExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct DecimalNumberExtractor: TokenExtractor {
12 |
13 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
14 | return buffer.peekNext()?.isDigit == true || buffer.peekNext() == "."
15 | }
16 |
17 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
18 | let start = buffer.currentIndex
19 |
20 | while buffer.peekNext()?.isDigit == true {
21 | buffer.consume()
22 | }
23 |
24 | if buffer.peekNext() == "." {
25 | buffer.consume()
26 |
27 | // consume fractional digits
28 | while buffer.peekNext()?.isDigit == true {
29 | buffer.consume()
30 | }
31 | }
32 |
33 | let indexBeforeE = buffer.currentIndex
34 | if buffer.peekNext() == "e" || buffer.peekNext() == "E" {
35 | buffer.consume()
36 |
37 | // there might be a "-" or "+" character preceding the exponent
38 | if buffer.peekNext() == "-" || buffer.peekNext() == "−" || buffer.peekNext() == "+" {
39 | buffer.consume()
40 | }
41 |
42 | let indexAtExponentDigits = buffer.currentIndex
43 |
44 | while buffer.peekNext()?.isDigit == true {
45 | buffer.consume()
46 | }
47 |
48 | if buffer.currentIndex == indexAtExponentDigits {
49 | // we didn't read anything after the [eE][-+]
50 | // so the entire exponent range is invalid
51 | buffer.resetTo(indexBeforeE)
52 | }
53 | }
54 |
55 | let length = buffer.currentIndex - start
56 | let range: Range = start ..< buffer.currentIndex
57 | let error = MathParserError(kind: .cannotParseNumber, range: range)
58 |
59 | var result = Tokenizer.Result.error(error)
60 | if length > 0 {
61 | if length != 1 || buffer[start] != "." {
62 | let raw = buffer[range]
63 | result = .value(DecimalNumberToken(string: raw, range: range))
64 | }
65 | }
66 | return result
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/ExpressionRewriter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionRewriter.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/25/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ExpressionRewriter {
12 | private let maxIterationCount: UInt
13 | private var rules: Array
14 |
15 | public static let `default` = ExpressionRewriter(rules: RewriteRule.defaultRules)
16 |
17 | public init(rules: Array, maxIterationCount: UInt = 256) {
18 | self.maxIterationCount = maxIterationCount
19 | self.rules = rules
20 | }
21 |
22 | public mutating func addRule(_ rule: RewriteRule) {
23 | rules.append(rule)
24 | }
25 |
26 | public func rewriteExpression(_ expression: Expression, substitutions: Substitutions = [:], evaluator: Evaluator = Evaluator.default) -> Expression {
27 |
28 | var tmp = expression
29 | var iterationCount: UInt = 0
30 |
31 | repeat {
32 | var changed = false
33 |
34 | for rule in rules {
35 | let rewritten = rewrite(tmp, usingRule: rule, substitutions: substitutions, evaluator: evaluator)
36 | if rewritten != tmp {
37 | changed = true
38 | tmp = rewritten
39 | }
40 | }
41 |
42 | if changed == false { break }
43 | iterationCount += 1
44 |
45 | } while iterationCount < maxIterationCount
46 |
47 | if iterationCount >= maxIterationCount {
48 | NSLog("replacement limit reached")
49 | }
50 |
51 | return tmp
52 | }
53 |
54 | private func rewrite(_ expression: Expression, usingRule rule: RewriteRule, substitutions: Substitutions, evaluator: Evaluator) -> Expression {
55 |
56 | let simplified = expression.simplify(substitutions, evaluator: evaluator)
57 |
58 | let rewritten = rule.rewrite(simplified, substitutions: substitutions, evaluator: evaluator)
59 | if rewritten != expression { return rewritten }
60 |
61 | guard case let .function(f, args) = rewritten.kind else { return rewritten }
62 |
63 | let newArgs = args.map { rewrite($0, usingRule: rule, substitutions: substitutions, evaluator: evaluator) }
64 |
65 | // if nothing changed, reture
66 | guard args != newArgs else { return rewritten }
67 |
68 | return Expression(kind: .function(f, newArgs), range: rewritten.range)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/LocalizedNumberExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalizedNumberExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/31/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct LocalizedNumberExtractor: TokenExtractor {
12 |
13 | private let decimalNumberFormatter = NumberFormatter()
14 |
15 | internal init(locale: Locale) {
16 | decimalNumberFormatter.locale = locale
17 | decimalNumberFormatter.numberStyle = .decimal
18 | }
19 |
20 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
21 | return buffer.peekNext() != nil
22 | }
23 |
24 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
25 | let start = buffer.currentIndex
26 | var indexBeforeDecimal: Int?
27 |
28 | var soFar = ""
29 | while let peek = buffer.peekNext(), peek.isWhitespace == false {
30 | let test = soFar + String(peek)
31 |
32 | if indexBeforeDecimal == nil && test.hasSuffix(decimalNumberFormatter.decimalSeparator) {
33 | indexBeforeDecimal = buffer.currentIndex
34 | }
35 |
36 | if canParseString(test) || (start == 0 && isValidPrefix(test)) {
37 | soFar = test
38 | buffer.consume()
39 | } else {
40 | break
41 | }
42 | }
43 |
44 | if let indexBeforeDecimal = indexBeforeDecimal, soFar.hasSuffix(decimalNumberFormatter.decimalSeparator) {
45 | buffer.resetTo(indexBeforeDecimal)
46 | soFar = buffer[start ..< indexBeforeDecimal]
47 | }
48 |
49 | let indexAfterNumber = buffer.currentIndex
50 | let range: Range = start ..< indexAfterNumber
51 |
52 | if range.isEmpty {
53 | let error = MathParserError(kind: .cannotParseNumber, range: range)
54 | return .error(error)
55 | }
56 |
57 | let token = LocalizedNumberToken(string: soFar, range: range)
58 | return .value(token)
59 | }
60 |
61 | /// - Returns: True if the string is a valid prefix of a localized number.
62 | private func isValidPrefix(_ string: String) -> Bool {
63 | return string == decimalNumberFormatter.decimalSeparator
64 | }
65 |
66 | private func canParseString(_ string: String) -> Bool {
67 | guard let _ = decimalNumberFormatter.number(from: string) else { return false }
68 | return true
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Character.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Character.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal extension Character {
12 |
13 | var isDigit: Bool {
14 | switch self {
15 | case "0"..."9": return true
16 | default: return false
17 | }
18 | }
19 |
20 | var isOctalDigit: Bool {
21 | switch self {
22 | case "0"..."7": return true
23 | default: return false
24 | }
25 | }
26 |
27 | var isHexDigit: Bool {
28 | switch self {
29 | case "a"..."f": return true
30 | case "A"..."F": return true
31 | default: return isDigit
32 | }
33 | }
34 |
35 | var isAlphabetic: Bool {
36 | switch self {
37 | case "a"..."z": return true
38 | case "A"..."Z": return true
39 | default: return false
40 | }
41 | }
42 |
43 | var isAlphaNumeric: Bool {
44 | return isAlphabetic || isDigit
45 | }
46 |
47 | var isNewline: Bool {
48 | switch self {
49 | // From CoreFoundation/CFUniChar.c:301
50 | // http://www.opensource.apple.com/source/CF/CF-1151.16/CFUniChar.c
51 | case "\u{000a}"..."\u{000d}": return true
52 | case "\u{0085}": return true
53 | case "\u{2028}": return true
54 | case "\u{2029}": return true
55 | default: return false
56 | }
57 | }
58 |
59 | var isWhitespace: Bool {
60 | switch self {
61 | // From CoreFoundation/CFUniChar.c:297
62 | // http://www.opensource.apple.com/source/CF/CF-1151.16/CFUniChar.c
63 | case "\u{0020}": return true
64 | case "\u{0009}": return true
65 | case "\u{00a0}": return true
66 | case "\u{1680}": return true
67 | case "\u{2000}"..."\u{200b}": return true
68 | case "\u{202f}": return true
69 | case "\u{205f}": return true
70 | case "\u{3000}": return true
71 | default: return false
72 | }
73 | }
74 |
75 | var isWhitespaceOrNewline: Bool {
76 | return isWhitespace || isNewline
77 | }
78 |
79 | var isSuperscript: Bool {
80 | switch self {
81 | case "\u{00B2}": return true
82 | case "\u{00B3}": return true
83 | case "\u{00B9}": return true
84 | case "\u{2070}"..."\u{207F}": return true
85 | default: return false
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/VariableExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VariableExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct VariableExtractor: TokenExtractor {
12 | private let identifierExtractor: IdentifierExtractor
13 |
14 | init(operatorTokens: OperatorTokenSet) {
15 | identifierExtractor = IdentifierExtractor(operatorTokens: operatorTokens)
16 | }
17 |
18 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
19 | return buffer.peekNext() == "$"
20 | }
21 |
22 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
23 | let start = buffer.currentIndex
24 |
25 | buffer.consume() // consume the opening $
26 |
27 | let identifierMatches = identifierExtractor.matchesPreconditions(buffer, configuration: configuration)
28 |
29 | if identifierMatches == false {
30 | if configuration.allowZeroLengthVariables {
31 | let token = VariableToken(string: "", range: start ..< buffer.currentIndex)
32 | return Tokenizer.Result.value(token)
33 | } else {
34 | // the stuff that follow "$" must be a valid identifier
35 | let range: Range = start ..< start
36 | let error = MathParserError(kind: .cannotParseVariable, range: range)
37 | return Tokenizer.Result.error(error)
38 | }
39 | }
40 |
41 | let identifierStart = buffer.currentIndex
42 | let identifierResult = identifierExtractor.extract(buffer, configuration: configuration)
43 |
44 | let result: Tokenizer.Result
45 |
46 | switch identifierResult {
47 | case .error(let e):
48 | if e.kind == .cannotParseIdentifier && configuration.allowZeroLengthVariables {
49 | buffer.resetTo(identifierStart)
50 | let token = VariableToken(string: "", range: start ..< identifierStart)
51 | result = .value(token)
52 | } else {
53 | let range: Range = start ..< e.range.upperBound
54 | let error = MathParserError(kind: .cannotParseVariable, range: range)
55 | result = .error(error)
56 | }
57 | case .value(let t):
58 | let range: Range = start ..< t.range.upperBound
59 | let token = VariableToken(string: t.string, range: range)
60 | result = .value(token)
61 | }
62 |
63 | return result
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/FunctionSet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FunctionSet.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 9/18/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal final class FunctionSet {
12 | private var functionsByName = Dictionary()
13 | private let caseSensitive: Bool
14 |
15 | internal init(caseSensitive: Bool) {
16 | self.caseSensitive = caseSensitive
17 | Function.standardFunctions.forEach {
18 | do {
19 | try registerFunction($0)
20 | } catch _ {
21 | fatalError("Conflicting name/alias in built-in functions")
22 | }
23 | }
24 | }
25 |
26 | internal func normalize(_ name: String) -> String {
27 | return caseSensitive ? name : name.lowercased()
28 | }
29 |
30 | private func registeredFunctionForName(_ name: String) -> FunctionRegistration? {
31 | let casedName = normalize(name)
32 | return functionsByName[casedName]
33 | }
34 |
35 | internal func evaluatorForName(_ name: String) -> FunctionEvaluator? {
36 | return registeredFunctionForName(name)?.function
37 | }
38 |
39 | internal func addAlias(_ alias: String, forFunctionName name: String) throws {
40 | guard registeredFunctionForName(alias) == nil else {
41 | throw FunctionRegistrationError.functionAlreadyExists(alias)
42 | }
43 | guard let registration = registeredFunctionForName(name) else {
44 | throw FunctionRegistrationError.functionDoesNotExist(name)
45 | }
46 |
47 | let casedAlias = normalize(alias)
48 | registration.addAlias(casedAlias)
49 | functionsByName[casedAlias] = registration
50 | }
51 |
52 | internal func registerFunction(_ function: Function) throws {
53 | let registration = FunctionRegistration(function: function, caseSensitive: caseSensitive)
54 |
55 | // we need to make sure that every name is accounted for
56 | for name in registration.names {
57 | guard registeredFunctionForName(name) == nil else {
58 | throw FunctionRegistrationError.functionAlreadyExists(name)
59 | }
60 | }
61 |
62 | registration.names.forEach {
63 | self.functionsByName[$0] = registration
64 | }
65 | }
66 | }
67 |
68 | private final class FunctionRegistration {
69 | var names: Set
70 | let function: FunctionEvaluator
71 |
72 | init(function: Function, caseSensitive: Bool) {
73 | self.function = function.evaluator
74 | self.names = Set(function.names.map { caseSensitive ? $0 : $0.lowercased() })
75 | }
76 |
77 | func addAlias(_ name: String) {
78 | names.insert(name)
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Tokenizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tokenizer.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/6/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Tokenizer {
12 | internal typealias Result = Either
13 |
14 | private let string: String
15 | internal let configuration: Configuration
16 |
17 | private let buffer: TokenCharacterBuffer
18 | private let extractors: Array
19 |
20 | public init(string: String, configuration: Configuration = .default) {
21 | self.string = string
22 | self.configuration = configuration
23 |
24 | buffer = TokenCharacterBuffer(string: string)
25 | let operatorTokens = configuration.operatorSet.operatorTokenSet
26 |
27 | let numberExtractor: TokenExtractor
28 | if let locale = configuration.locale {
29 | numberExtractor = LocalizedNumberExtractor(locale: locale)
30 | } else {
31 | numberExtractor = DecimalNumberExtractor()
32 | }
33 |
34 | extractors = [
35 | HexNumberExtractor(),
36 | OctalNumberExtractor(),
37 | numberExtractor,
38 | FractionNumberExtractor(),
39 | ExponentExtractor(),
40 |
41 | VariableExtractor(operatorTokens: operatorTokens),
42 | QuotedVariableExtractor(),
43 |
44 | OperatorExtractor(operatorTokens: operatorTokens),
45 |
46 | IdentifierExtractor(operatorTokens: operatorTokens)
47 | ]
48 |
49 | }
50 |
51 | public func tokenize() throws -> Array {
52 | var tokens = Array()
53 |
54 | while let next = next() {
55 | switch next {
56 | case .error(let e): throw e
57 | case .value(let t): tokens.append(t)
58 | }
59 | }
60 |
61 | return tokens
62 | }
63 |
64 | private func next() -> Result? {
65 | while buffer.peekNext()?.isWhitespaceOrNewline == true {
66 | buffer.consume()
67 | }
68 |
69 | guard buffer.isAtEnd == false else { return nil }
70 |
71 | let start = buffer.currentIndex
72 | var errors = Array()
73 |
74 | for extractor in extractors {
75 | guard extractor.matchesPreconditions(buffer, configuration: configuration) else { continue }
76 |
77 | buffer.resetTo(start)
78 | let result = extractor.extract(buffer, configuration: configuration)
79 |
80 | switch result {
81 | case .value(_): return result
82 | case .error(_): errors.append(result)
83 | }
84 | }
85 |
86 | return errors.first
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/QuotedVariableExtractor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuotedVariableExtractor.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct QuotedVariableExtractor: TokenExtractor {
12 |
13 | func matchesPreconditions(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Bool {
14 | return buffer.peekNext() == "\"" || buffer.peekNext() == "'"
15 | }
16 |
17 | func extract(_ buffer: TokenCharacterBuffer, configuration: Configuration) -> Tokenizer.Result {
18 | let start = buffer.currentIndex
19 |
20 | // consume the opening quote
21 | let quoteCharacter = buffer.peekNext()
22 | buffer.consume()
23 |
24 | var isEscaped = false
25 | var cleaned = ""
26 | while let next = buffer.peekNext() {
27 |
28 | if isEscaped == false {
29 | if next == "\\" {
30 | isEscaped = true
31 | buffer.consume()
32 | } else if next != quoteCharacter {
33 | cleaned.append(next)
34 | buffer.consume()
35 | } else {
36 | // it's a close quote
37 | break
38 | }
39 | } else {
40 | if configuration.unescapesQuotedVariables == true {
41 | switch next {
42 | case "n": cleaned.append("\n")
43 | case "r": cleaned.append("\r")
44 | case "t": cleaned.append("\t")
45 | default: cleaned.append(next)
46 | }
47 | } else {
48 | cleaned.append("\\")
49 | cleaned.append(next)
50 | }
51 | isEscaped = false
52 | buffer.consume()
53 | }
54 |
55 | }
56 |
57 | let result: Tokenizer.Result
58 |
59 | if buffer.peekNext() != quoteCharacter {
60 | let errorRange: Range = start ..< buffer.currentIndex
61 | let error = MathParserError(kind: .cannotParseQuotedVariable, range: errorRange)
62 | result = .error(error)
63 | } else {
64 | buffer.consume()
65 | let range: Range = start ..< buffer.currentIndex
66 | // check to make sure we don't have an empty string
67 | if cleaned.isEmpty && configuration.allowZeroLengthVariables == false {
68 | let error = MathParserError(kind: .zeroLengthVariable, range: range)
69 | result = .error(error)
70 | } else {
71 | let token = VariableToken(string: cleaned, range: range)
72 | result = .value(token)
73 | }
74 | }
75 |
76 | return result
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/Demo/AnalyzerFlowViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnalyzerFlowViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class AnalyzerFlowViewController: AnalyzerViewController, AnalyzerDelegate, NSTabViewDelegate {
12 |
13 | @IBOutlet var tabView: NSTabView?
14 |
15 | private var analyzers = Array()
16 | private var currentAnalyzedString = ""
17 | private var currentAnalyzer: AnalyzerViewController?
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | // Do view setup here.
22 |
23 | while let item = tabView?.tabViewItems.first {
24 | tabView?.removeTabViewItem(item)
25 | }
26 |
27 | analyzers = [
28 | RawTokenAnalyzerViewController(),
29 | ResolvedTokenAnalyzerViewController(),
30 | GroupedTokenAnalyzerViewController(),
31 | ExpressionAnalyzerViewController(),
32 | VariableAnalyzerViewController()
33 | ]
34 |
35 | for analyzer in analyzers {
36 | analyzer.analyzerDelegate = self
37 | let item = NSTabViewItem(viewController: analyzer)
38 | tabView?.addTabViewItem(item)
39 | }
40 |
41 | tabView?.selectTabViewItem(at: 0)
42 | handleSwitchToAnalyzer(analyzers[0])
43 | }
44 |
45 | override func analyzeString(_ string: String) {
46 | currentAnalyzedString = string
47 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [])
48 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: nil)
49 | currentAnalyzer?.analyzeString(string)
50 | }
51 |
52 | private func handleSwitchToAnalyzer(_ analyzer: AnalyzerViewController) {
53 | currentAnalyzer = analyzer
54 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [])
55 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: nil)
56 | analyzer.analyzeString(currentAnalyzedString)
57 | }
58 |
59 | func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
60 | guard let vc = tabViewItem?.viewController else { return }
61 | guard let analyzer = vc as? AnalyzerViewController else { return }
62 | handleSwitchToAnalyzer(analyzer)
63 | }
64 |
65 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsHighlightedRanges ranges: Array>) {
66 | guard analyzer == currentAnalyzer else { return }
67 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: ranges)
68 | }
69 |
70 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsErrorPresented error: MathParserError?) {
71 | guard analyzer == currentAnalyzer else { return }
72 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: error)
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/RewriteRule+Defaults.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // RewriteRule+Defaults.swift
4 | // DDMathParser
5 | //
6 | // Created by Dave DeLong on 8/25/15.
7 | //
8 | //
9 |
10 | import Foundation
11 |
12 | extension RewriteRule {
13 |
14 | public static let defaultRules: Array = [
15 | try! RewriteRule(predicate: "0 + __exp1", template: "__exp1"),
16 | try! RewriteRule(predicate: "__exp1 + 0", template: "__exp1"),
17 | try! RewriteRule(predicate: "__exp1 + __exp1", template: "2 * __exp1"),
18 | try! RewriteRule(predicate: "__exp1 - __exp1", template: "0"),
19 | try! RewriteRule(predicate: "1 * __exp1", template: "__exp1"),
20 | try! RewriteRule(predicate: "__exp1 * 1", template: "__exp1"),
21 | try! RewriteRule(predicate: "__exp1 / 1", template: "__exp1"),
22 | try! RewriteRule(predicate: "__exp1 * __exp1", template: "__exp1 ** 2"),
23 | try! RewriteRule(predicate: "__num1 * __var1", template: "__var1 * __num1"),
24 | try! RewriteRule(predicate: "0 * __exp1", template: "0"),
25 | try! RewriteRule(predicate: "__exp1 * 0", template: "0"),
26 | try! RewriteRule(predicate: "--__exp1", template: "__exp1"),
27 | try! RewriteRule(predicate: "abs(-__exp1)", template: "abs(__exp1)"),
28 | try! RewriteRule(predicate: "exp(__exp1) * exp(__exp2)", template: "exp(__exp1 + __exp2)"),
29 | try! RewriteRule(predicate: "(__exp1 ** __exp3) * (__exp2 ** __exp3)", template: "(__exp1 * __exp2) ** __exp3"),
30 | try! RewriteRule(predicate: "__exp1 ** 0", template: "1"),
31 | try! RewriteRule(predicate: "__exp1 ** 1", template: "__exp1"),
32 | try! RewriteRule(predicate: "sqrt(__exp1 ** 2)", template: "abs(__exp1)"),
33 | try! RewriteRule(predicate: "dtor(rtod(__exp1))", template: "__exp1"),
34 | try! RewriteRule(predicate: "rtod(dtor(__exp1))", template: "__exp1"),
35 |
36 |
37 | //division
38 | try! RewriteRule(predicate: "__exp1 / __exp1", condition: "__exp1 != 0", template: "1"),
39 | try! RewriteRule(predicate: "(__exp1 * __exp2) / __exp2", condition: "__exp2 != 0", template: "__exp1"),
40 | try! RewriteRule(predicate: "(__exp2 * __exp1) / __exp2", condition: "__exp2 != 0", template: "__exp1"),
41 | try! RewriteRule(predicate: "__exp2 / (__exp2 * __exp1)", condition: "__exp2 != 0", template: "1/__exp1"),
42 | try! RewriteRule(predicate: "__exp2 / (__exp1 * __exp2)", condition: "__exp2 != 0", template: "1/__exp1"),
43 |
44 |
45 | //exponents and roots
46 | try! RewriteRule(predicate: "nthroot(__exp1, 1)", template: "__exp1"),
47 | try! RewriteRule(predicate: "nthroot(pow(__exp1, __exp2), __exp2)", condition: "__exp2 % 2 == 0", template: "abs(__exp1)"),
48 | try! RewriteRule(predicate: "nthroot(pow(__exp1, __exp2), __exp2)", condition: "__exp2 % 2 == 1", template: "__exp1"),
49 | try! RewriteRule(predicate: "abs(__exp1)", condition: "__exp1 >= 0", template: "__exp1")
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/RewriteRule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RewriteRule.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/25/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum RuleTemplate {
12 | public static let anyExpression = "__exp"
13 | public static let anyNumber = "__num"
14 | public static let anyVariable = "__var"
15 | public static let anyFunction = "__func"
16 | }
17 |
18 | public struct RewriteRule {
19 |
20 | public let predicate: Expression
21 | public let condition: Expression?
22 |
23 | public let template: Expression
24 |
25 | public init(predicate: Expression, condition: Expression? = nil, template: Expression) {
26 | self.predicate = predicate
27 | self.condition = condition
28 | self.template = template
29 | }
30 |
31 | public init(predicate: String, condition: String? = nil, template: String) throws {
32 | self.predicate = try Expression(string: predicate)
33 | self.template = try Expression(string: template)
34 |
35 | if let condition = condition {
36 | self.condition = try Expression(string: condition)
37 | } else {
38 | self.condition = nil
39 | }
40 | }
41 |
42 | public func rewrite(_ expression: Expression, substitutions: Substitutions, evaluator: Evaluator) -> Expression {
43 |
44 | guard let replacements = expression.match(for: predicate) else {
45 | // the expression doesn't match the predicate
46 | return expression
47 | }
48 |
49 | if let condition = condition {
50 |
51 | // see if the expression matches the condition
52 | let matchingCondition = applyReplacements(replacements, toExpression: condition)
53 |
54 | // if there's an error evaluating the condition, then we don't match
55 | guard let result = try? evaluator.evaluate(matchingCondition, substitutions: substitutions) else {
56 | return expression
57 | }
58 |
59 | // a "zero" result value is interpreted as "false", which means we don't match
60 | if result == 0 { return expression }
61 | }
62 |
63 | // if we get here, then the expression matches the predicate and either:
64 | // 1. we don't have a condition or
65 | // 2. we have a condition, and the condition is satisfied
66 | return applyReplacements(replacements, toExpression: template)
67 | }
68 |
69 | private func applyReplacements(_ replacements: Dictionary, toExpression expression: Expression) -> Expression {
70 |
71 | switch expression.kind {
72 | case .function(let f, let args):
73 | if let replacement = replacements[f] {
74 | return Expression(kind: replacement.kind, range: replacement.range)
75 | }
76 |
77 | let newArgs = args.map { applyReplacements(replacements, toExpression: $0) }
78 | return Expression(kind: .function(f, newArgs), range: expression.range)
79 |
80 | default:
81 | return Expression(kind: expression.kind, range: expression.range)
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Evaluator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Evaluator.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/20/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum FunctionRegistrationError: Error {
12 | case functionAlreadyExists(String)
13 | case functionDoesNotExist(String)
14 | }
15 |
16 | public struct Evaluator {
17 |
18 | public enum AngleMode {
19 | case radians
20 | case degrees
21 | }
22 |
23 | public static let `default` = Evaluator()
24 | private let functionSet: FunctionSet
25 |
26 | public var angleMeasurementMode = AngleMode.radians
27 | public var functionOverrider: FunctionOverrider?
28 | public var functionResolver: FunctionResolver?
29 | public var variableResolver: VariableResolver?
30 |
31 | public init(caseSensitive: Bool = false) {
32 | functionSet = FunctionSet(caseSensitive: caseSensitive)
33 | }
34 |
35 | public func evaluate(_ expression: Expression, substitutions: Substitutions = [:]) throws -> Double {
36 | switch expression.kind {
37 | case .number(let d):
38 | return d
39 | case .variable(let s):
40 | return try evaluateVariable(s, substitutions: substitutions, range: expression.range)
41 | case .function(let f, let args):
42 | return try evaluateFunction(f, arguments: args, substitutions: substitutions, range: expression.range)
43 | }
44 | }
45 |
46 | public func registerFunction(_ function: Function) throws {
47 | try functionSet.registerFunction(function)
48 | }
49 |
50 | public func registerAlias(_ alias: String, forFunctionName name: String) throws {
51 | try functionSet.addAlias(alias, forFunctionName: name)
52 | }
53 |
54 | private func evaluateVariable(_ name: String, substitutions: Substitutions, range: Range) throws -> Double {
55 | if let value = try substitutions[name]?.substitutionValue(using: self, substitutions: substitutions) {
56 | return value
57 | }
58 |
59 | // substitutions were insufficient
60 | // use the variable resolver
61 |
62 | if let resolved = variableResolver?.resolveVariable(name) {
63 | return resolved
64 | }
65 |
66 | throw MathParserError(kind: .unknownVariable(name), range: range)
67 | }
68 |
69 | private func evaluateFunction(_ name: String, arguments: Array, substitutions: Substitutions, range: Range) throws -> Double {
70 | let state = EvaluationState(expressionRange: range, arguments: arguments, substitutions: substitutions, evaluator: self)
71 |
72 | // check for function overrides
73 | if let value = try functionOverrider?.overrideFunction(name, state: state) {
74 | return value
75 | }
76 |
77 | if let function = functionSet.evaluatorForName(name) {
78 | return try function(state)
79 | }
80 |
81 | // a function with this name does not exist
82 | // use the function resolver
83 | let normalized = functionSet.normalize(name)
84 | if let value = try functionResolver?.resolveFunction(normalized, state: state) {
85 | return value
86 | }
87 |
88 | throw MathParserError(kind: .unknownFunction(name), range: range)
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/Demo/AnalyzerFlowViewController.xib:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Demo/ExpressionAnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionAnalyzerViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class ExpressionAnalyzerViewController: AnalyzerViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
12 |
13 | @IBOutlet var expressionTree: NSOutlineView?
14 |
15 | var parsedExpression: Expression?
16 |
17 | override init() {
18 | super.init()
19 | title = "Expression"
20 | }
21 |
22 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
23 |
24 | override func analyzeString(_ string: String) {
25 | do {
26 | parsedExpression = try Expression(string: string)
27 | } catch let e as MathParserError {
28 | parsedExpression = nil
29 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
30 | } catch let other {
31 | fatalError("Unknown error parsing expression: \(other)")
32 | }
33 | expressionTree?.reloadItem(nil, reloadChildren: true)
34 | expressionTree?.expandItem(nil, expandChildren: true)
35 | }
36 |
37 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
38 | if item == nil {
39 | return parsedExpression == nil ? 0 : 1
40 | }
41 | var maybeExpression = item as? Expression
42 | maybeExpression = maybeExpression ?? parsedExpression
43 | guard let expression = maybeExpression else { return 0 }
44 |
45 | switch expression.kind {
46 | case .function(_, let arguments): return arguments.count
47 | default: return 0
48 | }
49 | }
50 |
51 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
52 | if item == nil, let e = parsedExpression { return e }
53 | guard let expression = item as? Expression else { fatalError("should only have Expressions") }
54 | switch expression.kind {
55 | case .function(_, let arguments): return arguments[index]
56 | default: fatalError("only argument functions have children")
57 | }
58 |
59 | }
60 |
61 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
62 | return self.outlineView(outlineView, numberOfChildrenOfItem: item) > 0
63 | }
64 |
65 | func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
66 | if item == nil { return ""}
67 | guard let expression = item as? Expression else { fatalError("should only have Expressions") }
68 | let info: String
69 | switch expression.kind {
70 | case .number(let d): info = "Number: \(d)"
71 | case .variable(let v): info = "Variable: \(v)"
72 | case .function(let f, let args):
73 | let argInfo = args.count == 1 ? "1 argument" : "\(args.count) arguments"
74 | info = "\(f)(\(argInfo))"
75 | }
76 | return "\(info) - range: \(expression.range)"
77 | }
78 |
79 | func outlineViewSelectionDidChange(_ notification: Notification) {
80 | let row = expressionTree?.selectedRow ?? -1
81 |
82 | if row >= 0 {
83 | guard let item = expressionTree?.item(atRow: row) else { fatalError("missing item at row \(row)") }
84 | guard let expression = item as? Expression else { fatalError("only expressions should be in the tree") }
85 |
86 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [expression.range])
87 | } else {
88 | // unhighlight everything in the textfield
89 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [])
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/MathParser/Tests/MathParserTests/RewriterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RewriterTests.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/27/15.
6 | //
7 | //
8 |
9 | import XCTest
10 | import MathParser
11 |
12 | func TestRewrite(_ original: String, expected: String, substitutions: Substitutions = [:], evaluator: Evaluator = Evaluator.default, rewriter: ExpressionRewriter = ExpressionRewriter.default, file: StaticString = #file, line: UInt = #line) {
13 |
14 | guard let originalE = XCTAssertNoThrows(try Expression(string: original), file: file, line: line) else { return }
15 |
16 | guard let expectedE = XCTAssertNoThrows(try Expression(string: expected), file: file, line: line) else { return }
17 |
18 | let rewritten = rewriter.rewriteExpression(originalE, substitutions: substitutions, evaluator: evaluator)
19 |
20 | XCTAssertEqual(rewritten, expectedE, file: file, line: line)
21 |
22 | // Also test that the convenience functions produces the same result.
23 | let convenienceRewritten = originalE.rewrite(substitutions, rewriter: rewriter, evaluator: evaluator)
24 | XCTAssertEqual(convenienceRewritten, rewritten)
25 | }
26 |
27 | class RewriterTests: XCTestCase {
28 |
29 | func testDefaultRules() {
30 | TestRewrite("0 + $foo", expected: "$foo")
31 | TestRewrite("$foo + 0", expected: "$foo")
32 | TestRewrite("$foo + $foo", expected: "$foo * 2")
33 | TestRewrite("$foo - $foo", expected: "0")
34 | TestRewrite("1 * $foo", expected: "$foo")
35 | TestRewrite("$foo * 1", expected: "$foo")
36 | TestRewrite("$foo * $foo", expected: "$foo ** 2")
37 | TestRewrite("__num1 * __var1", expected: "__var1 * __num1")
38 | TestRewrite("0 * $foo", expected: "0")
39 | TestRewrite("$foo * 0", expected: "0")
40 | TestRewrite("--$foo", expected: "$foo")
41 | TestRewrite("abs(-$foo)", expected: "abs($foo)")
42 | TestRewrite("exp($foo) * exp($bar)", expected: "exp($foo + $bar)")
43 | TestRewrite("($foo ** $baz) * ($bar ** $baz)", expected: "($foo * $bar) ** $baz")
44 | TestRewrite("$foo ** 0", expected: "1")
45 | TestRewrite("$foo ** 1", expected: "$foo")
46 | TestRewrite("sqrt($foo ** 2)", expected: "abs($foo)")
47 | TestRewrite("dtor(rtod($foo))", expected: "$foo")
48 | TestRewrite("rtod(dtor($foo))", expected: "$foo")
49 |
50 |
51 | //division
52 | TestRewrite("$foo / $foo", expected: "1", substitutions: ["foo": 1])
53 | TestRewrite("$foo / $foo", expected: "$foo / $foo")
54 |
55 | TestRewrite("($foo * $bar) / $bar", expected: "$foo", substitutions: ["bar": 1])
56 | TestRewrite("($foo * $bar) / $bar", expected: "($foo * $bar) / $bar")
57 |
58 |
59 | TestRewrite("($bar * $foo) / $bar", expected: "$foo", substitutions: ["bar": 1])
60 | TestRewrite("($bar * $foo) / $bar", expected: "($bar * $foo) / $bar")
61 |
62 | TestRewrite("$bar / ($bar * $foo)", expected: "1/$foo", substitutions: ["bar": 1])
63 | TestRewrite("$bar / ($bar * $foo)", expected: "$bar / ($bar * $foo)")
64 |
65 | TestRewrite("$bar / ($foo * $bar)", expected: "1/$foo", substitutions: ["bar": 1])
66 | TestRewrite("$bar / ($foo * $bar)", expected: "$bar / ($foo * $bar)")
67 |
68 |
69 | //exponents and roots
70 | TestRewrite("nthroot(pow($foo, $bar), $bar)", expected: "abs($foo)", substitutions: ["bar": 2])
71 | TestRewrite("nthroot(pow($foo, $bar), $bar)", expected: "nthroot(pow($foo, $bar), $bar)")
72 |
73 |
74 | TestRewrite("nthroot(pow($foo, $bar), $bar)", expected: "$foo", substitutions: ["bar": 1])
75 | TestRewrite("nthroot(pow($foo, $bar), $bar)", expected: "nthroot(pow($foo, $bar), $bar)")
76 |
77 | TestRewrite("abs($foo)", expected: "1", substitutions: ["foo": 1])
78 | TestRewrite("abs($foo)", expected: "abs($foo)")
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/MathParserErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MathParserErrors.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 5/6/16.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct MathParserError: Error {
12 |
13 | public enum Kind {
14 | // Tokenization Errors
15 | case cannotParseNumber
16 | case cannotParseHexNumber // can also occur during Resolution
17 | case cannotParseOctalNumber // can also occur during Resolution
18 | case cannotParseFractionalNumber
19 | case cannotParseExponent
20 | case cannotParseIdentifier
21 | case cannotParseVariable
22 | case cannotParseQuotedVariable
23 | case cannotParseOperator
24 | case zeroLengthVariable
25 |
26 | // Resolution Errors
27 | case cannotParseLocalizedNumber
28 | case unknownOperator
29 | case ambiguousOperator
30 |
31 | // Grouping Errors
32 | case missingOpenParenthesis
33 | case missingCloseParenthesis
34 | case emptyFunctionArgument
35 | case emptyGroup
36 |
37 | // Expression Errors
38 | case invalidFormat
39 | case missingLeftOperand(Operator)
40 | case missingRightOperand(Operator)
41 |
42 | // Evaluation Errors
43 | case unknownFunction(String)
44 | case unknownVariable(String)
45 | case divideByZero
46 | case invalidArguments
47 | }
48 |
49 | public let kind: Kind
50 |
51 | // the location within the original source string where the error was found
52 | public let range: Range
53 |
54 | public init(kind: Kind, range: Range) {
55 | self.kind = kind
56 | self.range = range
57 | }
58 | }
59 |
60 | extension MathParserError.Kind: Equatable { }
61 |
62 | public func ==(lhs: MathParserError.Kind, rhs: MathParserError.Kind) -> Bool {
63 | switch (lhs, rhs) {
64 | case (.cannotParseNumber, .cannotParseNumber): return true
65 | case (.cannotParseHexNumber, .cannotParseHexNumber): return true
66 | case (.cannotParseOctalNumber, .cannotParseOctalNumber): return true
67 | case (.cannotParseExponent, .cannotParseExponent): return true
68 | case (.cannotParseIdentifier, .cannotParseIdentifier): return true
69 | case (.cannotParseVariable, .cannotParseVariable): return true
70 | case (.cannotParseQuotedVariable, .cannotParseQuotedVariable): return true
71 | case (.cannotParseOperator, .cannotParseOperator): return true
72 | case (.zeroLengthVariable, .zeroLengthVariable): return true
73 |
74 | // Resolution Errors
75 | case (.cannotParseLocalizedNumber, .cannotParseLocalizedNumber): return true
76 | case (.unknownOperator, .unknownOperator): return true
77 | case (.ambiguousOperator, .ambiguousOperator): return true
78 |
79 | // Grouping Errors
80 | case (.missingOpenParenthesis, .missingOpenParenthesis): return true
81 | case (.missingCloseParenthesis, .missingCloseParenthesis): return true
82 | case (.emptyFunctionArgument, .emptyFunctionArgument): return true
83 | case (.emptyGroup, .emptyGroup): return true
84 |
85 | // Expression Errors
86 | case (.invalidFormat, .invalidFormat): return true
87 | case (.missingLeftOperand(let leftOp), .missingLeftOperand(let rightOp)): return leftOp == rightOp
88 | case (.missingRightOperand(let leftOp), .missingRightOperand(let rightOp)): return leftOp == rightOp
89 |
90 | // Evaluation Errors
91 | case (.unknownFunction(let leftString), .unknownFunction(let rightString)): return leftString == rightString
92 | case (.unknownVariable(let leftString), .unknownVariable(let rightString)): return leftString == rightString
93 | case (.divideByZero, .divideByZero): return true
94 | case (.invalidArguments, .invalidArguments): return true
95 |
96 | default: return false
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Expression+Matching.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Expression+Matching.swift
3 | // MathParser
4 | //
5 | // Created by Dave DeLong on 4/22/19.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Expression {
11 |
12 | typealias Matches = Dictionary
13 |
14 | func match(for target: Expression, matchedSoFar: Matches? = nil) -> Matches? {
15 | var replacements = matchedSoFar ?? [:]
16 |
17 | switch target.kind {
18 | // we're looking for a specific number; return the replacements if we match that number
19 | case .number(_): return self == target ? replacements : nil
20 |
21 | // we're looking for a specific variable; return the replacements if we match
22 | case .variable(_): return self == target ? replacements : nil
23 |
24 | // we're looking for something else
25 | case .function(let f, let args):
26 |
27 | // we're looking for anything
28 | if f.hasPrefix(RuleTemplate.anyExpression) {
29 | // is this a matcher ("__exp42") we've seen before?
30 | // if it is, only return replacements if it's the same expression
31 | // as what has already been matched
32 | if let seenBefore = replacements[f] {
33 | return seenBefore == self ? replacements : nil
34 | }
35 |
36 | // otherwise remember this one and return the new replacements
37 | replacements[f] = self
38 | return replacements
39 | }
40 |
41 | // we're looking for any number
42 | if f.hasPrefix(RuleTemplate.anyNumber) && kind.isNumber {
43 | if let seenBefore = replacements[f] {
44 | return seenBefore == self ? replacements : nil
45 | }
46 | replacements[f] = self
47 | return replacements
48 | }
49 |
50 | // we're looking for any variable
51 | if f.hasPrefix(RuleTemplate.anyVariable) && kind.isVariable {
52 | if let seenBefore = replacements[f] {
53 | return seenBefore == self ? replacements : nil
54 | }
55 | replacements[f] = self
56 | return replacements
57 | }
58 |
59 | // we're looking for any function
60 | if f.hasPrefix(RuleTemplate.anyFunction) && kind.isFunction {
61 | if let seenBefore = replacements[f] {
62 | return seenBefore == self ? replacements : nil
63 | }
64 | replacements[f] = self
65 | return replacements
66 | }
67 |
68 | // if we make it this far, we're looking for a specific function
69 | // make sure the expression we're matching against is also a function
70 | guard case let .function(expressionF, expressionArgs) = kind else { return nil }
71 | // make sure the functions have the same name
72 | guard expressionF == f else { return nil }
73 | // make sure the functions have the same number of arguments
74 | guard expressionArgs.count == args.count else { return nil }
75 |
76 | // make sure each argument matches
77 | for (expressionArg, targetArg) in zip(expressionArgs, args) {
78 | // if this argument doesn't match, return nil
79 | guard let argReplacements = expressionArg.match(for: targetArg, matchedSoFar: replacements) else { return nil }
80 | replacements = argReplacements
81 | }
82 |
83 | return replacements
84 | }
85 | }
86 |
87 | func matches(_ expression: Expression) -> Bool {
88 | return match(for: expression) != nil
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/Demo/GroupedTokenAnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupedTokenAnalyzerViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class GroupedTokenAnalyzerViewController: AnalyzerViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
12 |
13 | @IBOutlet var tokenTree: NSOutlineView?
14 |
15 | var groupedToken: GroupedToken?
16 |
17 | override init() {
18 | super.init()
19 | title = "Grouped"
20 | }
21 |
22 | required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
23 |
24 | override func analyzeString(_ string: String) {
25 | let grouper = TokenGrouper(string: string)
26 | do {
27 | groupedToken = try grouper.group()
28 | } catch let e as MathParserError {
29 | groupedToken = nil
30 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
31 | } catch let other {
32 | fatalError("Unknown error grouping expression: \(other)")
33 | }
34 | tokenTree?.reloadItem(nil, reloadChildren: true)
35 | tokenTree?.expandItem(nil, expandChildren: true)
36 | }
37 |
38 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
39 | if item == nil {
40 | return groupedToken == nil ? 0 : 1
41 | }
42 | var maybeToken = item as? GroupedToken
43 | maybeToken = maybeToken ?? groupedToken
44 | guard let token = maybeToken else { return 0 }
45 |
46 | switch token.kind {
47 | case .function(_, let args): return args.count
48 | case .group(let args): return args.count
49 | default: return 0
50 | }
51 | }
52 |
53 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
54 | if item == nil, let t = groupedToken { return t }
55 | guard let token = item as? GroupedToken else { fatalError("should only have GroupedTokens") }
56 | switch token.kind {
57 | case .function(_, let arguments): return arguments[index]
58 | case .group(let arguments): return arguments[index]
59 | default: fatalError("only functions and groups have children")
60 | }
61 |
62 | }
63 |
64 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
65 | return self.outlineView(outlineView, numberOfChildrenOfItem: item) > 0
66 | }
67 |
68 | func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
69 | if item == nil { return ""}
70 | guard let token = item as? GroupedToken else { fatalError("should only have GroupedToken") }
71 | let info: String
72 | switch token.kind {
73 | case .function(let f, let args):
74 | let argInfo = args.count == 1 ? "1 argument" : "\(args.count) arguments"
75 | info = "\(f)(\(argInfo))"
76 | case .group(let args):
77 | let argInfo = args.count == 1 ? "1 argument" : "\(args.count) arguments"
78 | info = "Group: (\(argInfo))"
79 | case .number(let d): info = "Number: \(d)"
80 | case .variable(let v): info = "Variable: \(v)"
81 | case .operator(let o): info = "Operator: \(o)"
82 | }
83 |
84 | return "\(info) - range: \(token.range)"
85 | }
86 |
87 | func outlineViewSelectionDidChange(_ notification: Notification) {
88 | let row = tokenTree?.selectedRow ?? -1
89 |
90 | if row >= 0 {
91 | guard let item = tokenTree?.item(atRow: row) else { fatalError("missing item at row \(row)") }
92 | guard let token = item as? GroupedToken else { fatalError("only GroupedTokens should be in the tree") }
93 |
94 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [token.range])
95 | } else {
96 | // unhighlight everything in the textfield
97 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [])
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Demo/VariableAnalyzerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VariableAnalyzerViewController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 12/14/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | extension Expression {
12 | var variableName: String? {
13 | guard case .variable(let v) = kind else { return nil }
14 | return v
15 | }
16 | }
17 |
18 | class VariableAnalyzerViewController: AnalyzerViewController, NSTableViewDelegate, NSTableViewDataSource {
19 |
20 | var parsedExpression: Expression?
21 |
22 | var variableExpressions = Dictionary>()
23 | var variables = Array()
24 | var values = Dictionary()
25 |
26 | @IBOutlet var tableView: NSTableView?
27 | @IBOutlet var resultLabel: NSTextField?
28 |
29 | override init() {
30 | super.init()
31 | title = "Evaluation"
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | fatalError("init(coder:) has not been implemented")
36 | }
37 |
38 | override func analyzeString(_ string: String) {
39 | resultLabel?.stringValue = ""
40 |
41 | do {
42 | let e = try Expression(string: string)
43 | parsedExpression = e
44 |
45 | let collected = collectVariables(from: e)
46 |
47 | variableExpressions = Dictionary(grouping: collected, by: { $0.variableName! })
48 | variables = variableExpressions.keys.sorted()
49 |
50 | tableView?.reloadData()
51 | reevaluateExpression()
52 |
53 | } catch let e as MathParserError {
54 | parsedExpression = nil
55 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
56 | } catch let other {
57 | fatalError("Unknown error parsing expression: \(other)")
58 | }
59 | }
60 |
61 | func reevaluateExpression() {
62 | guard let expression = parsedExpression else { return }
63 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: nil)
64 |
65 | let evaluator = Evaluator.default
66 | do {
67 | let result = try evaluator.evaluate(expression, substitutions: values)
68 | resultLabel?.stringValue = "\(result)"
69 | } catch let e as MathParserError {
70 | analyzerDelegate?.analyzerViewController(self, wantsErrorPresented: e)
71 | } catch let e {
72 | fatalError("Unknown error evaluating expression: \(e)")
73 | }
74 | }
75 |
76 | func collectVariables(from expression: Expression) -> Array {
77 | var variables = Array()
78 |
79 | switch expression.kind {
80 | case .variable(_):
81 | variables.append(expression)
82 | case .function(_, let args):
83 | let subVariables = args.flatMap { collectVariables(from: $0) }
84 | variables.append(contentsOf: subVariables)
85 | default:
86 | break
87 | }
88 |
89 | return variables
90 | }
91 |
92 | func numberOfRows(in tableView: NSTableView) -> Int {
93 | return variables.count
94 | }
95 |
96 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
97 | guard row >= 0 && row < variables.count else { return nil }
98 | let name = variables[row]
99 |
100 | if tableColumn?.identifier.rawValue == "variable" { return name }
101 | return values[name]
102 | }
103 |
104 | func tableView(_ tableView: NSTableView, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
105 | return tableColumn?.identifier.rawValue != "variable"
106 | }
107 |
108 | func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) {
109 | guard row >= 0 && row < variables.count else { return }
110 | let value = object as? Double
111 | let name = variables[row]
112 | values[name] = value
113 | reevaluateExpression()
114 | }
115 |
116 | func tableViewSelectionDidChange(_ notification: Notification) {
117 | guard let row = tableView?.selectedRow, row >= 0 && row < variables.count else {
118 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: [])
119 | return
120 | }
121 | let name = variables[row]
122 | let expressions = variableExpressions[name] ?? []
123 | let ranges = expressions.map { $0.range }
124 | analyzerDelegate?.analyzerViewController(self, wantsHighlightedRanges: ranges)
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/Demo/DemoViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Demo/DemoViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoWindowController.swift
3 | // Demo
4 | //
5 | // Created by Dave DeLong on 11/21/17.
6 | //
7 |
8 | import Cocoa
9 | import MathParser
10 |
11 | class DemoViewController: NSViewController, AnalyzerDelegate, NSTextFieldDelegate {
12 |
13 | @IBOutlet var expressionField: NSTextField?
14 | @IBOutlet var errorLabel: NSTextField?
15 | @IBOutlet var flowContainer: NSView?
16 |
17 | var flowController: AnalyzerFlowViewController?
18 |
19 | private var controlChangeObserver: AnyObject?
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | let container = flowContainer! // must have outlet hooked up
25 |
26 | let flow = AnalyzerFlowViewController()
27 | flow.analyzerDelegate = self
28 | addChild(flow)
29 |
30 | flow.view.frame = container.bounds
31 | flow.view.autoresizingMask = [.width, .height]
32 | flow.view.translatesAutoresizingMaskIntoConstraints = true
33 | container.addSubview(flow.view)
34 |
35 | flowController = flow
36 | flowController?.analyzeString("")
37 |
38 | controlChangeObserver = NotificationCenter.default.addObserver(forName: NSControl.textDidChangeNotification,
39 | object: expressionField,
40 | queue: .main,
41 | using: { [weak self] _ in
42 | let text = self?.expressionField?.stringValue ?? ""
43 | self?.flowController?.analyzeString(text)
44 | })
45 | }
46 |
47 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsHighlightedRanges ranges: Array>) {
48 | highlightRanges(ranges, isError: false)
49 | }
50 |
51 | func analyzerViewController(_ analyzer: AnalyzerViewController, wantsErrorPresented error: MathParserError?) {
52 | guard let error = error else {
53 | errorLabel?.stringValue = "No error"
54 | return
55 | }
56 |
57 | highlightRanges([error.range], isError: true)
58 |
59 | let msg: String
60 | switch error.kind {
61 | case .ambiguousOperator: msg = "Unable to disambiguate operator"
62 | case .cannotParseExponent: msg = "Unable to parse exponent"
63 | case .cannotParseFractionalNumber: msg = "Unable to parse fraction"
64 | case .cannotParseNumber: msg = "Unable to parse number"
65 | case .cannotParseHexNumber: msg = "Unable to parse hexadecimal number"
66 | case .cannotParseOctalNumber: msg = "Unable to parse octal number"
67 | case .cannotParseIdentifier: msg = "Unable to parse identifier"
68 | case .cannotParseVariable: msg = "Unable to parse variable"
69 | case .cannotParseQuotedVariable: msg = "Unable to parse quoted variable"
70 | case .cannotParseOperator: msg = "Unable to parse operator"
71 | case .zeroLengthVariable: msg = "Variables must have at least one character"
72 | case .cannotParseLocalizedNumber: msg = "Unable to parse localized number"
73 | case .unknownOperator: msg = "Unknown operator"
74 | case .missingOpenParenthesis: msg = "Expression is missing open parenthesis"
75 | case .missingCloseParenthesis: msg = "Expression is missing closing parenthesis"
76 | case .emptyFunctionArgument: msg = "Function is missing argument"
77 | case .emptyGroup: msg = "Empty group"
78 | case .invalidFormat: msg = "Expression is likely missing an operator"
79 | case .missingLeftOperand(let o): msg = "Operator \(o.tokens.first!) is missing its left operand"
80 | case .missingRightOperand(let o): msg = "Operator \(o.tokens.first!) is missing its right operand"
81 | case .unknownFunction(let f): msg = "Unknown function '\(f)'"
82 | case .unknownVariable(let v): msg = "Unknown variable '\(v)'"
83 | case .divideByZero: msg = "Invalid division by zero"
84 | case .invalidArguments: msg = "Invalid arguments to function"
85 | }
86 | errorLabel?.stringValue = msg
87 | }
88 |
89 | private func highlightRanges(_ ranges: Array>, isError: Bool) {
90 | let string = expressionField?.stringValue ?? ""
91 | guard let attributed = expressionField?.attributedStringValue.mutableCopy() as? NSMutableAttributedString else { return }
92 |
93 | let wholeRange = NSRange(location: 0, length: attributed.length)
94 | attributed.setAttributes([.foregroundColor: NSColor.textColor], range: wholeRange)
95 |
96 | let color = isError ? NSColor.red : NSColor.systemPurple
97 |
98 | for range in ranges {
99 | let lower = string.index(string.startIndex, offsetBy: range.lowerBound)
100 | let upper = string.index(string.startIndex, offsetBy: range.upperBound)
101 |
102 | let nsRange = NSRange(lower ..< upper, in: string)
103 | attributed.setAttributes([.foregroundColor: color], range: nsRange)
104 |
105 | if range.isEmpty {
106 | // something needs to be "inserted" into the string
107 | } else {
108 | // something in the string is invalid
109 | }
110 | }
111 | expressionField?.attributedStringValue = attributed
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Demo/RawTokenAnalyzerViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Demo/ResolvedTokenAnalyzerViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Demo/ExpressionAnalyzerViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Demo/GroupedTokenAnalyzerViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Expression.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Expression.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/17/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public final class Expression {
12 | public enum Kind {
13 | case number(Double)
14 | case variable(String)
15 | case function(String, Array)
16 |
17 | public var isNumber: Bool { return number != nil }
18 | public var isVariable: Bool { return variable != nil }
19 | public var isFunction: Bool { return functionName != nil }
20 |
21 | public var number: Double? {
22 | guard case .number(let d) = self else { return nil }
23 | return d
24 | }
25 |
26 | public var variable: String? {
27 | guard case .variable(let v) = self else { return nil }
28 | return v
29 | }
30 |
31 | public var functionName: String? {
32 | guard case .function(let name, _) = self else { return nil }
33 | return name
34 | }
35 |
36 | public var functionArguments: Array? {
37 | guard case .function(_, let args) = self else { return nil }
38 | return args
39 | }
40 | }
41 |
42 | public let kind: Kind
43 | public let range: Range
44 |
45 | internal weak var parent: Expression?
46 |
47 | public init(kind: Kind, range: Range) {
48 | self.kind = kind
49 | self.range = range
50 |
51 | if case let .function(_, args) = kind {
52 | args.forEach { $0.parent = self }
53 | }
54 | }
55 |
56 | public convenience init(string: String, configuration: Configuration = .default) throws {
57 | let tokenizer = Tokenizer(string: string, configuration: configuration)
58 | let resolver = TokenResolver(tokenizer: tokenizer)
59 | let grouper = TokenGrouper(resolver: resolver)
60 | let expressionizer = Expressionizer(grouper: grouper)
61 |
62 | let e = try expressionizer.expression()
63 | self.init(kind: e.kind, range: e.range)
64 | }
65 |
66 | public func simplify(_ substitutions: Substitutions = [:], evaluator: Evaluator) -> Expression {
67 | switch kind {
68 | case .number(_): return Expression(kind: kind, range: range)
69 | case .variable(let varName):
70 | if let resolved = try? evaluator.evaluate(self, substitutions: substitutions) {
71 | return Expression(kind: .number(resolved), range: range)
72 | }
73 | if let exp = substitutions[varName]?.simplified(using: evaluator, substitutions: substitutions) as? Expression {
74 | return Expression(kind: exp.kind, range: range)
75 | }
76 | return Expression(kind: kind, range: range)
77 | case let .function(f, args):
78 | let newArgs = args.map { $0.simplify(substitutions, evaluator: evaluator) }
79 | let areAllArgsNumbers = newArgs.reduce(true) { $0 && $1.kind.isNumber }
80 |
81 | guard areAllArgsNumbers else {
82 | return Expression(kind: .function(f, newArgs), range: range)
83 | }
84 |
85 | guard let value = try? evaluator.evaluate(self) else {
86 | return Expression(kind: .function(f, newArgs), range: range)
87 | }
88 |
89 | return Expression(kind: .number(value), range: range)
90 | }
91 | }
92 |
93 | public func rewrite(_ substitutions: Substitutions = [:], rewriter: ExpressionRewriter = .default, evaluator: Evaluator = .default) -> Expression {
94 | return rewriter.rewriteExpression(self, substitutions: substitutions, evaluator: evaluator)
95 | }
96 | }
97 |
98 | extension Expression: Substitution {
99 | public func substitutionValue(using evaluator: Evaluator, substitutions: Substitutions) throws -> Double {
100 | return try evaluator.evaluate(self, substitutions: substitutions)
101 | }
102 |
103 | public func simplified(using evaluator: Evaluator, substitutions: Substitutions) -> Substitution {
104 | return simplify(substitutions, evaluator: evaluator)
105 | }
106 | }
107 |
108 | extension Expression: CustomStringConvertible {
109 |
110 | public var description: String {
111 | switch kind {
112 | case .number(let d): return d.description
113 | case .variable(let v):
114 | if v.contains(" ") { return "\"\(v)\"" }
115 | return "$\(v)"
116 | case .function(let f, let args):
117 | let params = args.map { $0.description }
118 | if let builtIn = BuiltInOperator(rawValue: f) {
119 | let op = Operator(builtInOperator: builtIn)
120 | guard let token = op.tokens.first else {
121 | fatalError("Built-in operator doesn't have any tokens")
122 | }
123 |
124 | if op.arity.argumentCount == params.count {
125 | switch (op.arity, op.associativity) {
126 | case (.binary, _):
127 | return "\(params[0]) \(token) \(params[1])"
128 | case (.unary, .left):
129 | return "\(params[0])\(token)"
130 | case (.unary, .right):
131 | return "\(token)\(params[0])"
132 | }
133 | }
134 | }
135 | let joined = params.joined(separator: ", ")
136 | return "\(f)(\(joined))"
137 | }
138 | }
139 |
140 | }
141 |
142 | extension Expression: Equatable { }
143 |
144 | public func ==(lhs: Expression, rhs: Expression) -> Bool {
145 | switch (lhs.kind, rhs.kind) {
146 | case (.number(let l), .number(let r)): return l == r
147 | case (.variable(let l), .variable(let r)): return l == r
148 | case (.function(let lf, let lArg), .function(let rf, let rArg)): return lf == rf && lArg == rArg
149 | default: return false
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/TokenGrouper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenGrouper.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/15/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | private extension GroupedToken {
12 |
13 | private var endIndex: Int {
14 | switch self.kind {
15 | case let .function(_, parameters):
16 | return parameters.last?.range.upperBound ?? range.upperBound
17 | case let .group(tokens):
18 | return tokens.last?.range.upperBound ?? range.upperBound
19 | default:
20 | return range.upperBound
21 | }
22 | }
23 |
24 | }
25 |
26 | public struct TokenGrouper {
27 | private let resolver: TokenResolver
28 | internal var operatorSet: OperatorSet { return resolver.operatorSet }
29 |
30 | public init(resolver: TokenResolver) {
31 | self.resolver = resolver
32 | }
33 |
34 | public init(string: String) {
35 | self.resolver = TokenResolver(string: string)
36 | }
37 |
38 | public func group() throws -> GroupedToken {
39 | let tokens = try resolver.resolve()
40 | let p = PeekingIterator(generator: tokens.makeIterator())
41 | let g = try rootTokenFromGenerator(p)
42 | return stripRedundantGroups(g)
43 | }
44 |
45 | private func stripRedundantGroups(_ t: GroupedToken) -> GroupedToken {
46 | switch t.kind {
47 | case .function(let f, let tokens):
48 | let stripped = tokens.map { stripRedundantGroups($0) }
49 | return GroupedToken(kind: .function(f, stripped), range: t.range)
50 | case .group(let tokens):
51 | let stripped = tokens.map { stripRedundantGroups($0) }
52 | if stripped.count == 1 { return stripped[0] }
53 | return GroupedToken(kind: .group(stripped), range: t.range)
54 | default:
55 | return t
56 | }
57 | }
58 |
59 | private func rootTokenFromGenerator(_ g: P) throws -> GroupedToken where P.Element == ResolvedToken {
60 |
61 | var rootTokens = Array()
62 |
63 | while let _ = g.peek() {
64 | let parameterToken = try tokenFromGenerator(g)
65 | rootTokens.append(parameterToken)
66 | }
67 |
68 | guard let first = rootTokens.first, let last = rootTokens.last else {
69 | throw MathParserError(kind: .emptyGroup, range: 0 ..< 0) //EmptyGroup
70 | }
71 | let range: Range = first.range.lowerBound ..< last.range.upperBound
72 |
73 | return GroupedToken(kind: .group(rootTokens), range: range)
74 | }
75 |
76 | private func tokenFromGenerator(_ generator: P) throws -> GroupedToken where P.Element == ResolvedToken {
77 | var g = generator
78 |
79 | guard let peek = g.peek() else {
80 | fatalError("Implementation flaw")
81 | }
82 |
83 | switch peek.kind {
84 | case .number(let d):
85 | let _ = g.next()
86 | return GroupedToken(kind: .number(d), range: peek.range)
87 | case .variable(let s):
88 | let _ = g.next()
89 | return GroupedToken(kind: .variable(s), range: peek.range)
90 | case .identifier(_):
91 | return try functionTokenFromGenerator(g)
92 | case .operator(let o) where o.builtInOperator == .parenthesisOpen:
93 | return try groupTokenFromGenerator(g)
94 | case .operator(let o) where o.builtInOperator == .parenthesisClose:
95 | // CloseParen, but no OpenParen
96 | throw MathParserError(kind: .missingOpenParenthesis, range: peek.range)
97 | case .operator(let o):
98 | let _ = g.next()
99 | return GroupedToken(kind: .operator(o), range: peek.range)
100 |
101 | }
102 | }
103 |
104 | private func functionTokenFromGenerator(_ generator: P) throws -> GroupedToken where P.Element == ResolvedToken {
105 | var g = generator
106 | guard let function = g.next() else {
107 | fatalError("Implementation flaw")
108 | }
109 |
110 | guard let open = g.next(), open.kind.builtInOperator == .parenthesisOpen else {
111 | throw MathParserError(kind: .missingOpenParenthesis, range: function.range.upperBound ..< function.range.upperBound)
112 | }
113 |
114 | var parameters = Array()
115 |
116 | while let p = g.peek(), p.kind.builtInOperator != .parenthesisClose {
117 | // read out all the arguments
118 | let parameter = try parameterGroupFromGenerator(g, parameterIndex: p.range.lowerBound)
119 | parameters.append(parameter)
120 | }
121 |
122 | guard let close = g.next(), close.kind.builtInOperator == .parenthesisClose else {
123 | let indexForMissingParen = parameters.last?.range.upperBound ?? open.range.upperBound
124 | throw MathParserError(kind: .missingCloseParenthesis, range: indexForMissingParen ..< indexForMissingParen)
125 | }
126 |
127 | let range: Range = function.range.lowerBound ..< close.range.upperBound
128 | return GroupedToken(kind: .function(function.string, parameters), range: range)
129 | }
130 |
131 | private func parameterGroupFromGenerator(_ generator: P, parameterIndex: Int) throws -> GroupedToken where P.Element == ResolvedToken {
132 | var g = generator
133 |
134 | var parameterTokens = Array()
135 |
136 | while let p = g.peek() {
137 | if p.kind.builtInOperator == .comma {
138 | let _ = g.next() // consume the comma
139 | break
140 | }
141 |
142 | if p.kind.builtInOperator == .parenthesisClose {
143 | break // don't consume
144 | }
145 |
146 | let parameterToken = try tokenFromGenerator(g)
147 | parameterTokens.append(parameterToken)
148 | }
149 |
150 | guard let first = parameterTokens.first, let last = parameterTokens.last else {
151 | throw MathParserError(kind: .emptyFunctionArgument, range: parameterIndex ..< parameterIndex) // EmptyFunctionArgument
152 | }
153 |
154 | let range: Range = first.range.lowerBound ..< last.range.upperBound
155 | return GroupedToken(kind: .group(parameterTokens), range: range)
156 | }
157 |
158 | private func groupTokenFromGenerator(_ generator: P) throws -> GroupedToken where P.Element == ResolvedToken {
159 | var g = generator
160 | guard let open = g.next(), open.kind.builtInOperator == .parenthesisOpen else {
161 | fatalError("Implementation flaw")
162 | }
163 |
164 | var tokens = Array()
165 |
166 | while let peek = g.peek(), peek.kind.builtInOperator != .parenthesisClose {
167 |
168 | tokens.append(try tokenFromGenerator(g))
169 | }
170 |
171 | guard let close = g.next(), close.kind.builtInOperator == .parenthesisClose else {
172 | let indexForMissingParen = tokens.last?.range.upperBound ?? open.range.upperBound
173 | throw MathParserError(kind: .missingCloseParenthesis, range: open.range.lowerBound ..< indexForMissingParen)
174 | }
175 |
176 | let range: Range = open.range.lowerBound ..< close.range.upperBound
177 |
178 | guard tokens.isEmpty == false else {
179 | throw MathParserError(kind: .emptyGroup, range: range) // Empty Group
180 | }
181 |
182 | return GroupedToken(kind: .group(tokens), range: range)
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Operator+Defaults.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Operator+Defaults.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/8/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum BuiltInOperator: String {
12 | case logicalOr = "l_or"
13 | case logicalAnd = "l_and"
14 | case logicalNot = "l_not"
15 | case logicalEqual = "l_eq"
16 | case logicalNotEqual = "l_neq"
17 | case logicalLessThan = "l_lt"
18 | case logicalGreaterThan = "l_gt"
19 | case logicalLessThanOrEqual = "l_ltoe"
20 | case logicalGreaterThanOrEqual = "l_gtoe"
21 | case bitwiseOr = "or"
22 | case bitwiseXor = "xor"
23 | case bitwiseAnd = "and"
24 | case leftShift = "lshift"
25 | case rightShift = "rshift"
26 | case minus = "subtract"
27 | case add = "add"
28 | case divide = "divide"
29 | case multiply = "multiply"
30 | case implicitMultiply = "implicitMultiply"
31 | case modulo = "mod"
32 | case bitwiseNot = "not"
33 | case factorial = "factorial"
34 | case doubleFactorial = "factorial2"
35 | case degree = "dtor"
36 | case percent = "percent"
37 | case power = "pow"
38 | case parenthesisOpen = "open_paren"
39 | case parenthesisClose = "close_paren"
40 | case comma = "comma"
41 | case unaryMinus = "negate"
42 | case unaryPlus = "positive"
43 | case squareRoot = "sqrt"
44 | case cubeRoot = "cuberoot"
45 | }
46 |
47 | extension Operator {
48 |
49 | public static let defaultPowerAssociativity: Associativity = {
50 |
51 | //determine what associativity NSPredicate/NSExpression is using
52 | //mathematically, it should be Right associative, but it's usually parsed as Left associative
53 | //rdar://problem/8692313
54 |
55 | let expression = NSExpression(format: "2 ** 3 ** 2")
56 | let result = expression.expressionValue(with: nil, context: nil) as? NSNumber
57 |
58 | if result?.int32Value == 512 {
59 | return .right
60 | } else {
61 | return .left
62 | }
63 | }()
64 |
65 | internal var builtInOperator: BuiltInOperator? { return BuiltInOperator(rawValue: self.function) }
66 |
67 | public convenience init(builtInOperator: BuiltInOperator) {
68 | let arity: Arity
69 | let associativity: Associativity
70 | let tokens: Set
71 |
72 | switch builtInOperator {
73 | case .logicalOr:
74 | arity = .binary
75 | associativity = .left
76 | tokens = ["||", "∨"]
77 | case .logicalAnd:
78 | arity = .binary
79 | associativity = .left
80 | tokens = ["&&", "∧"]
81 | case .logicalNot:
82 | arity = .unary
83 | associativity = .right
84 | tokens = ["!", "¬"]
85 | case .logicalEqual:
86 | arity = .binary
87 | associativity = .left
88 | tokens = ["==", "="]
89 | case .logicalNotEqual:
90 | arity = .binary
91 | associativity = .left
92 | tokens = ["!=", "≠"]
93 |
94 | case .logicalLessThan:
95 | arity = .binary
96 | associativity = .left
97 | tokens = ["<"]
98 | case .logicalGreaterThan:
99 | arity = .binary
100 | associativity = .left
101 | tokens = [">"]
102 | case .logicalLessThanOrEqual:
103 | arity = .binary
104 | associativity = .left
105 | tokens = ["<=", "=<", "≤", "≯"]
106 | case .logicalGreaterThanOrEqual:
107 | arity = .binary
108 | associativity = .left
109 | tokens = [">=", "=>", "≥", "≮"]
110 |
111 | case .bitwiseOr:
112 | arity = .binary
113 | associativity = .left
114 | tokens = ["|"]
115 | case .bitwiseXor:
116 | arity = .binary
117 | associativity = .left
118 | tokens = ["^"]
119 | case .bitwiseAnd:
120 | arity = .binary
121 | associativity = .left
122 | tokens = ["&"]
123 | case .leftShift:
124 | arity = .binary
125 | associativity = .left
126 | tokens = ["<<"]
127 | case .rightShift:
128 | arity = .binary
129 | associativity = .left
130 | tokens = [">>"]
131 |
132 | case .minus:
133 | arity = .binary
134 | associativity = .left
135 | tokens = ["-", "−"]
136 | case .add:
137 | arity = .binary
138 | associativity = .left
139 | tokens = ["+"]
140 |
141 | case .divide:
142 | arity = .binary
143 | associativity = .left
144 | tokens = ["/", "÷"]
145 | case .multiply:
146 | arity = .binary
147 | associativity = .left
148 | tokens = ["*", "×"]
149 | case .implicitMultiply:
150 | arity = .binary
151 | associativity = .left
152 | tokens = ["*", "×"]
153 |
154 | case .modulo:
155 | arity = .binary
156 | associativity = .left
157 | tokens = ["%"]
158 |
159 | case .bitwiseNot:
160 | arity = .unary
161 | associativity = .right
162 | tokens = ["~"]
163 |
164 | // Unary Left operators
165 | case .factorial:
166 | arity = .unary
167 | associativity = .left
168 | tokens = ["!"]
169 | case .doubleFactorial:
170 | arity = .unary
171 | associativity = .left
172 | tokens = ["!!"]
173 | case .degree:
174 | arity = .unary
175 | associativity = .left
176 | tokens = ["º", "°", "∘"]
177 | case .percent:
178 | arity = .unary
179 | associativity = .left
180 | tokens = ["%"]
181 |
182 | case .power:
183 | arity = .binary
184 | associativity = Operator.defaultPowerAssociativity
185 | tokens = ["**"]
186 |
187 | // Unary Right operators
188 | case .unaryMinus:
189 | arity = .unary
190 | associativity = .right
191 | tokens = ["-", "−"]
192 | case .unaryPlus:
193 | arity = .unary
194 | associativity = .right
195 | tokens = ["+"]
196 | case .squareRoot:
197 | arity = .unary
198 | associativity = .right
199 | tokens = ["√"]
200 | case .cubeRoot:
201 | arity = .unary
202 | associativity = .right
203 | tokens = ["∛"]
204 |
205 | // these are defined as .Unary .Right/.Left associative for convenience
206 | case .parenthesisOpen:
207 | arity = .unary
208 | associativity = .right
209 | tokens = ["("]
210 | case .parenthesisClose:
211 | arity = .unary
212 | associativity = .left
213 | tokens = [")"]
214 |
215 | case .comma:
216 | arity = .binary
217 | associativity = .left
218 | tokens = [","]
219 | }
220 |
221 | self.init(function: builtInOperator.rawValue, arity: arity, associativity: associativity)
222 | self.tokens = tokens
223 | self.precedence = nil
224 | }
225 |
226 | internal convenience init(builtInOperator: BuiltInOperator, precedence: Int) {
227 | self.init(builtInOperator: builtInOperator)
228 | self.precedence = precedence
229 | }
230 |
231 | }
232 |
--------------------------------------------------------------------------------
/MathParser/Tests/MathParserTests/GroupingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GroupingTests.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/15/15.
6 | //
7 | //
8 |
9 | import XCTest
10 | import MathParser
11 |
12 | class GroupingTests: XCTestCase {
13 |
14 | func testNumber() {
15 | let g = TokenGrouper(string: "1")
16 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
17 | switch t.kind {
18 | case .number(1.0): break
19 | default: XCTFail("Unexpected token kind")
20 | }
21 | }
22 |
23 | func testVariable() {
24 | let g = TokenGrouper(string: "$foo")
25 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
26 | switch t.kind {
27 | case .variable("foo"): break
28 | default: XCTFail("Unexpected token kind")
29 | }
30 | }
31 |
32 | func testIdentifier() {
33 | let g = TokenGrouper(string: "foo")
34 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
35 | switch t.kind {
36 | case .function("foo", _): break
37 | default: XCTFail("Unexpected token kind")
38 | }
39 | }
40 |
41 | func testNumberAndOperator() {
42 | let g = TokenGrouper(string: "1+1")
43 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
44 | switch t.kind {
45 | case .group(let tokens):
46 | XCTAssert(tokens.count == 3)
47 | default: XCTFail("Unexpected token kind")
48 | }
49 | }
50 |
51 | func testGroupedNumber() {
52 | let g = TokenGrouper(string: "(1)")
53 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
54 | switch t.kind {
55 | case .number(1.0): break
56 | default: XCTFail("Unexpected token kind")
57 | }
58 | }
59 |
60 | func testRedundantGroups() {
61 | let g = TokenGrouper(string: "(((foo())))")
62 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
63 |
64 | switch t.kind {
65 | case .function("foo", _): break
66 | default: XCTFail("Unexpected token kind")
67 | }
68 | }
69 |
70 | func testEmptyFunctionArgument() {
71 | let g = TokenGrouper(string: "foo(,1)")
72 |
73 | do {
74 | let _ = try g.group()
75 | XCTFail("Expected error")
76 | } catch let other {
77 | guard let error = other as? MathParserError else {
78 | XCTFail("Unexpected error \(other)")
79 | return
80 | }
81 | XCTAssert(error.kind == .emptyFunctionArgument)
82 | }
83 | }
84 |
85 | func testFunctionMissingOpenParenthesis() {
86 | let r = TokenResolver(string: "foo", configuration: .defaultWithEmptyOptions)
87 | let g = TokenGrouper(resolver: r)
88 |
89 | do {
90 | let _ = try g.group()
91 | XCTFail("Expected error")
92 | } catch let other {
93 | guard let error = other as? MathParserError else {
94 | XCTFail("Unexpected error \(other)")
95 | return
96 | }
97 | XCTAssert(error.kind == .missingOpenParenthesis)
98 | }
99 | }
100 |
101 | func testFunctionMissingCloseParenthesis() {
102 | let r = TokenResolver(string: "foo(", configuration: .defaultWithEmptyOptions)
103 | let g = TokenGrouper(resolver: r)
104 |
105 | do {
106 | let _ = try g.group()
107 | XCTFail("Expected error")
108 | } catch let other {
109 | guard let error = other as? MathParserError else {
110 | XCTFail("Unexpected error \(other)")
111 | return
112 | }
113 | XCTAssert(error.kind == .missingCloseParenthesis)
114 | }
115 | }
116 |
117 | func testGroupMissingCloseParenthesis() {
118 | let g = TokenGrouper(string: "(4")
119 |
120 | do {
121 | let _ = try g.group()
122 | XCTFail("Expected error")
123 | } catch let other {
124 | guard let error = other as? MathParserError else {
125 | XCTFail("Unexpected error \(other)")
126 | return
127 | }
128 | XCTAssert(error.kind == .missingCloseParenthesis)
129 | }
130 | }
131 |
132 | func testGroupMissingOpenParenthesis() {
133 | let g = TokenGrouper(string: "4)")
134 |
135 | do {
136 | let _ = try g.group()
137 | XCTFail("Expected error")
138 | } catch let other {
139 | guard let error = other as? MathParserError else {
140 | XCTFail("Unexpected error \(other)")
141 | return
142 | }
143 | XCTAssert(error.kind == .missingOpenParenthesis)
144 | }
145 | }
146 |
147 | func testFunctionParameterGrouping() {
148 | let g = TokenGrouper(string: "foo(1,2+3,-4)")
149 | guard let t = XCTAssertNoThrows(try g.group()) else { return }
150 |
151 | switch t.kind {
152 | case .function("foo", let parameters):
153 | XCTAssertEqual(parameters.count, 3)
154 | // first parameter
155 | guard case .number(1) = parameters[0].kind else { XCTFail("Unexpected parameter 1"); return }
156 |
157 | // second parameter
158 | guard case .group(let second) = parameters[1].kind else {
159 | XCTFail("Unexpected parameter 2"); return
160 | }
161 | XCTAssertEqual(second.count, 3)
162 | guard case .number(2) = second[0].kind else {
163 | XCTFail("Unexpected parameter 2,1"); return
164 | }
165 | guard case .operator(Operator(builtInOperator: .add)) = second[1].kind else {
166 | XCTFail("Unexpected parameter 2,2"); return
167 | }
168 | guard case .number(3) = second[2].kind else {
169 | XCTFail("Unexpected parameter 2,3"); return
170 | }
171 |
172 | guard case .group(let third) = parameters[2].kind else {
173 | XCTFail("Unexpected parameter 3"); return
174 | }
175 | XCTAssertEqual(third.count, 2)
176 |
177 | guard case .operator(Operator(builtInOperator: .unaryMinus)) = third[0].kind else {
178 | XCTFail("Unexpected parameter 3,1"); return
179 | }
180 | guard case .number(4) = third[1].kind else {
181 | XCTFail("Unexpected parameter 3,2"); return
182 | }
183 | default:
184 | XCTFail("Unexpected token kind")
185 | }
186 | }
187 |
188 | func testEmptyRootGroup() {
189 | let g = TokenGrouper(string: "")
190 |
191 | do {
192 | let _ = try g.group()
193 | XCTFail("Expected error")
194 | } catch let other {
195 | guard let error = other as? MathParserError else {
196 | XCTFail("Unexpected error \(other)")
197 | return
198 | }
199 | XCTAssert(error.kind == .emptyGroup)
200 | }
201 | }
202 |
203 | func testEmptyGroup() {
204 | let g = TokenGrouper(string: "1+()")
205 |
206 | do {
207 | let _ = try g.group()
208 | XCTFail("Expected error")
209 | } catch let other {
210 | guard let error = other as? MathParserError else {
211 | XCTFail("Unexpected error \(other)")
212 | return
213 | }
214 | XCTAssert(error.kind == .emptyGroup)
215 | }
216 | }
217 |
218 | func testUnaryPlus() {
219 | guard let g = XCTAssertNoThrows(try TokenGrouper(string: "+1").group()) else { return }
220 |
221 | guard case let .group(subterms) = g.kind else {
222 | XCTFail("Unexpected group: \(g)")
223 | return
224 | }
225 |
226 | XCTAssertEqual(subterms.count, 2)
227 | let unaryPlus = Operator(builtInOperator: .unaryPlus)
228 | guard case .operator(unaryPlus) = subterms[0].kind else {
229 | XCTFail("Unexpected token kind: \(subterms[0].kind)")
230 | return
231 | }
232 |
233 | guard case .number(1) = subterms[1].kind else {
234 | XCTFail("Unexpected token kind: \(subterms[1].kind)")
235 | return
236 | }
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/OperatorSet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OperatorSet.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/7/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public final class OperatorSet {
12 | public static let `default` = OperatorSet()
13 |
14 | public enum Relation {
15 | case lessThan
16 | case equalTo
17 | case greaterThan
18 | }
19 |
20 | public init(interpretsPercentSignAsModulo: Bool = true) {
21 | var ops = Array()
22 | var precedence = 1
23 |
24 | // LogicalDisjunction
25 | ops.append(Operator(builtInOperator: .logicalOr, precedence: precedence))
26 | precedence += 1
27 |
28 | // LogicalConjunction
29 | ops.append(Operator(builtInOperator: .logicalAnd, precedence: precedence))
30 | precedence += 1
31 |
32 | // == and != have the same precedence
33 |
34 | // ComparisonPrecedence
35 | ops.append(Operator(builtInOperator: .logicalEqual, precedence: precedence))
36 | ops.append(Operator(builtInOperator: .logicalNotEqual, precedence: precedence))
37 | ops.append(Operator(builtInOperator: .logicalLessThan, precedence: precedence))
38 | ops.append(Operator(builtInOperator: .logicalGreaterThan, precedence: precedence))
39 | ops.append(Operator(builtInOperator: .logicalLessThanOrEqual, precedence: precedence))
40 | ops.append(Operator(builtInOperator: .logicalGreaterThanOrEqual, precedence: precedence))
41 | precedence += 1
42 |
43 | precedence += 1
44 |
45 | // AdditionPrecedence
46 | precedence += 1
47 | ops.append(Operator(builtInOperator: .add, precedence: precedence))
48 | ops.append(Operator(builtInOperator: .minus, precedence: precedence))
49 | ops.append(Operator(builtInOperator: .bitwiseOr, precedence: precedence))
50 | ops.append(Operator(builtInOperator: .bitwiseXor, precedence: precedence))
51 | precedence += 1
52 |
53 | // MultiplicationPrecedence
54 | multiplyOperator = Operator(builtInOperator: .multiply, precedence: precedence)
55 | ops.append(multiplyOperator)
56 | ops.append(Operator(builtInOperator: .divide, precedence: precedence))
57 | ops.append(Operator(builtInOperator: .bitwiseAnd, precedence: precedence))
58 | precedence += 1
59 |
60 | implicitMultiplyOperator = Operator(builtInOperator: .implicitMultiply, precedence: precedence)
61 | ops.append(implicitMultiplyOperator)
62 | precedence += 1
63 |
64 | // NOTE: percent-as-modulo precedence goes here (after ImplicitMultiply)
65 |
66 | // BitwiseShiftPrecedence
67 | ops.append(Operator(builtInOperator: .leftShift, precedence: precedence))
68 | ops.append(Operator(builtInOperator: .rightShift, precedence: precedence))
69 | precedence += 1
70 |
71 | // all right associative unary operators have the same precedence
72 | ops.append(Operator(builtInOperator: .bitwiseNot, precedence: precedence))
73 | ops.append(Operator(builtInOperator: .unaryMinus, precedence: precedence))
74 | ops.append(Operator(builtInOperator: .unaryPlus, precedence: precedence))
75 | ops.append(Operator(builtInOperator: .squareRoot, precedence: precedence))
76 | ops.append(Operator(builtInOperator: .cubeRoot, precedence: precedence))
77 | ops.append(Operator(builtInOperator: .logicalNot, precedence: precedence))
78 | precedence += 1
79 |
80 | // all left associative unary operators have the same precedence
81 | ops.append(Operator(builtInOperator: .doubleFactorial, precedence: precedence))
82 | ops.append(Operator(builtInOperator: .factorial, precedence: precedence))
83 | // NOTE: percent-as-percent precedence goes here (same as Factorial)
84 | ops.append(Operator(builtInOperator: .degree, precedence: precedence))
85 | precedence += 1
86 |
87 | powerOperator = Operator(builtInOperator: .power, precedence: precedence)
88 | precedence += 1
89 | ops.append(powerOperator)
90 |
91 | addFractionOperator = Operator(builtInOperator: .add, precedence: precedence)
92 | precedence += 1
93 | ops.append(addFractionOperator)
94 |
95 | // these are defined as unary right/left associative for convenience
96 | ops.append(Operator(builtInOperator: .parenthesisOpen, precedence: precedence))
97 | ops.append(Operator(builtInOperator: .parenthesisClose, precedence: precedence))
98 | precedence += 1
99 |
100 | ops.append(Operator(builtInOperator: .comma, precedence: precedence))
101 | precedence += 1
102 |
103 | self.operators = ops
104 | self.interpretsPercentSignAsModulo = interpretsPercentSignAsModulo
105 | self.knownTokens = Set(ops.flatMap { $0.tokens })
106 |
107 | interpretPercentSignAsModulo(self.interpretsPercentSignAsModulo)
108 | }
109 |
110 | public var interpretsPercentSignAsModulo: Bool {
111 | didSet(oldValue) {
112 | if oldValue != interpretsPercentSignAsModulo {
113 | interpretPercentSignAsModulo(interpretsPercentSignAsModulo)
114 | }
115 | }
116 | }
117 | private func interpretPercentSignAsModulo(_ interpretAsModulo: Bool) {
118 | let percent = Operator(builtInOperator: .percent)
119 | let modulo = Operator(builtInOperator: .modulo)
120 |
121 | // remove the old one and add the new one
122 | if interpretAsModulo {
123 | removeOperator(percent)
124 | addOperator(modulo, relatedBy: .greaterThan, toOperator: Operator(builtInOperator: .implicitMultiply))
125 | } else {
126 | removeOperator(modulo)
127 | addOperator(percent, relatedBy: .equalTo, toOperator: Operator(builtInOperator: .factorial))
128 | }
129 | }
130 |
131 | private var _operatorTokenSet: OperatorTokenSet? = nil
132 | internal var operatorTokenSet: OperatorTokenSet {
133 | if _operatorTokenSet == nil {
134 | _operatorTokenSet = OperatorTokenSet(tokens: knownTokens)
135 | }
136 | guard let set = _operatorTokenSet else { fatalError("Missing operator token set") }
137 | return set
138 | }
139 |
140 | public private(set) var operators: Array {
141 | didSet {
142 | operatorsDidChange()
143 | }
144 | }
145 |
146 | private func operatorsDidChange() {
147 | knownTokens = Set(operators.flatMap { $0.tokens })
148 | _operatorTokenSet = nil
149 | }
150 |
151 | internal let addFractionOperator: Operator
152 | internal let multiplyOperator: Operator
153 | internal let implicitMultiplyOperator: Operator
154 | internal let powerOperator: Operator
155 |
156 | private var knownTokens: Set
157 |
158 | private func removeOperator(_ op: Operator) {
159 | guard let index = operators.firstIndex(of: op) else { return }
160 | operators.remove(at: index)
161 | operatorsDidChange()
162 | }
163 |
164 | public func addTokens(_ tokens: Array, forOperator op: Operator) {
165 | let allowed = tokens.map { $0.lowercased() }.filter {
166 | self.operatorForToken($0).isEmpty
167 | }
168 |
169 | guard let existing = existingOperator(op) else { return }
170 | existing.tokens.formUnion(allowed)
171 | operatorsDidChange()
172 | }
173 |
174 | public func addOperator(_ op: Operator, relatedBy: Relation, toOperator existingOp: Operator) {
175 | guard let existing = existingOperator(existingOp) else { return }
176 | guard let existingP = existing.precedence else { fatalError("Existing operator missing precedence \(existing)") }
177 |
178 | let newOperator = op
179 | newOperator.precedence = existing.precedence
180 |
181 | let sorter: (Operator) -> Bool
182 |
183 | switch relatedBy {
184 | case .equalTo:
185 | sorter = { _ in return false }
186 | case .lessThan:
187 | sorter = { other in
188 | guard let otherP = other.precedence else { fatalError("Operator missing precedence: \(other)") }
189 | return otherP >= existingP
190 | }
191 | case .greaterThan:
192 | sorter = { other in
193 | guard let otherP = other.precedence else { fatalError("Operator missing precedence: \(other)") }
194 | return otherP > existingP
195 | }
196 | }
197 |
198 | processOperator(newOperator, sorter: sorter)
199 |
200 | }
201 |
202 | private func existingOperator(_ op: Operator) -> Operator? {
203 | let matches = operators.filter { $0 == op }
204 | return matches.first
205 | }
206 |
207 | private func processOperator(_ op: Operator, sorter: (Operator) -> Bool) {
208 | if let existing = existingOperator(op) {
209 | existing.tokens.formUnion(op.tokens)
210 | operatorsDidChange()
211 | } else {
212 | let overlap = knownTokens.intersection(op.tokens)
213 | guard overlap.isEmpty == true else {
214 | NSLog("cannot add operator with conflicting tokens: \(overlap)")
215 | return
216 | }
217 |
218 | let newOperators = operators.map { orig -> Operator in
219 | let new = Operator(function: orig.function, arity: orig.arity, associativity: orig.associativity)
220 | new.tokens = orig.tokens
221 |
222 | var precedence = orig.precedence ?? 0
223 | if sorter(orig) { precedence += 1 }
224 | new.precedence = precedence
225 | return new
226 | }
227 | operators = newOperators
228 | operators.append(op)
229 | operatorsDidChange()
230 | }
231 | }
232 |
233 | public func operatorForToken(_ token: String, arity: Operator.Arity? = nil, associativity: Operator.Associativity? = nil) -> Array {
234 |
235 | return operators.filter {
236 | guard $0.tokens.contains(token) else { return false }
237 |
238 | if let arity = arity {
239 | if $0.arity != arity { return false }
240 | }
241 |
242 | if let associativity = associativity {
243 | if $0.associativity != associativity { return false }
244 | }
245 |
246 | return true
247 | }
248 | }
249 |
250 | }
251 |
--------------------------------------------------------------------------------
/Demo/VariableAnalyzerViewController.xib:
--------------------------------------------------------------------------------
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 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/MathParser/Tests/MathParserTests/ExpressionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExpressionTests.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/20/15.
6 | //
7 | //
8 |
9 | import XCTest
10 | import MathParser
11 |
12 | class ExpressionTests: XCTestCase {
13 |
14 | func testNumber() {
15 | guard let e = XCTAssertNoThrows(try Expression(string: "4")) else { return }
16 |
17 | guard case .number(4) = e.kind else {
18 | XCTFail("Unexpected expression kind: \(e.kind)")
19 | return
20 | }
21 | }
22 |
23 | func testVariable() {
24 | guard let e = XCTAssertNoThrows(try Expression(string: "$foo")) else { return }
25 |
26 | guard case .variable("foo") = e.kind else {
27 | XCTFail("Unexpected expression kind: \(e.kind)")
28 | return
29 | }
30 | }
31 |
32 | func testSimpleFunction() {
33 | guard let e = XCTAssertNoThrows(try Expression(string: "foo()")) else { return }
34 |
35 | guard case let .function("foo", args) = e.kind else {
36 | XCTFail("Unexpected expression kind: \(e.kind)")
37 | return
38 | }
39 |
40 | XCTAssertEqual(args.count, 0)
41 | }
42 |
43 | func testFunctionWithArguments() {
44 | guard let e = XCTAssertNoThrows(try Expression(string: "foo(1)")) else { return }
45 |
46 | guard case let .function("foo", args) = e.kind else {
47 | XCTFail("Unexpected expression kind: \(e.kind)")
48 | return
49 | }
50 |
51 | XCTAssertEqual(args.count, 1)
52 |
53 | let arg = args[0]
54 | guard case .number(1) = arg.kind else {
55 | XCTFail("Unexpected argument: \(arg.kind)")
56 | return
57 | }
58 | }
59 |
60 | func testRightUnaryOperator() {
61 | guard let e = XCTAssertNoThrows(try Expression(string: "-42")) else { return }
62 |
63 | guard case let .function("negate", args) = e.kind else {
64 | XCTFail("Unexpected expression kind: \(e.kind)")
65 | return
66 | }
67 |
68 | XCTAssertEqual(args.count, 1)
69 |
70 | let arg = args[0]
71 | guard case .number(42) = arg.kind else {
72 | XCTFail("Unexpected argument: \(arg.kind)")
73 | return
74 | }
75 | }
76 |
77 | func testRecursiveRightUnaryOperator() {
78 | guard let e = XCTAssertNoThrows(try Expression(string: "---42")) else { return }
79 |
80 | guard case let .function("negate", args) = e.kind else {
81 | XCTFail("Unexpected expression kind: \(e.kind)")
82 | return
83 | }
84 |
85 | XCTAssertEqual(args.count, 1)
86 | let e2 = args[0]
87 | guard case let .function("negate", args2) = e2.kind else {
88 | XCTFail("Unexpected expression kind: \(e2.kind)")
89 | return
90 | }
91 |
92 | XCTAssertEqual(args2.count, 1)
93 | let e3 = args2[0]
94 | guard case let .function("negate", args3) = e3.kind else {
95 | XCTFail("Unexpected expression kind: \(e3.kind)")
96 | return
97 | }
98 |
99 | XCTAssertEqual(args3.count, 1)
100 | guard case .number(42) = args3[0].kind else {
101 | XCTFail("Unexpected expression kind: \(args3[0].kind)")
102 | return
103 | }
104 | }
105 |
106 | func testLeftUnaryOperator() {
107 | guard let e = XCTAssertNoThrows(try Expression(string: "4!")) else {
108 | return
109 | }
110 |
111 | guard case let .function("factorial", args) = e.kind else {
112 | XCTFail("Unexpected expression kind: \(e.kind)")
113 | return
114 | }
115 |
116 | XCTAssertEqual(args.count, 1)
117 |
118 | let arg = args[0]
119 | guard case .number(4) = arg.kind else {
120 | XCTFail("Unexpected argument: \(arg.kind)")
121 | return
122 | }
123 | }
124 |
125 | func testRecursiveLeftUnaryOperator() {
126 | guard let e = XCTAssertNoThrows(try Expression(string: "4°!")) else {
127 | return
128 | }
129 |
130 | guard case let .function("factorial", args) = e.kind else {
131 | XCTFail("Unexpected expression kind: \(e.kind)")
132 | return
133 | }
134 |
135 | XCTAssertEqual(args.count, 1)
136 |
137 | let arg = args[0]
138 | guard case let .function("dtor", args2) = arg.kind else {
139 | XCTFail("Unexpected expression kind: \(arg.kind)")
140 | return
141 | }
142 |
143 | XCTAssertEqual(args2.count, 1)
144 | guard case .number(4) = args2[0].kind else {
145 | XCTFail("Unexpected argument: \(args2[0].kind)")
146 | return
147 | }
148 | }
149 |
150 | func testMissingLeftOperand() {
151 | do {
152 | let _ = try Expression(string: "°")
153 | XCTFail("Unexpected expression")
154 | } catch let e {
155 | guard let error = e as? MathParserError else {
156 | XCTFail("Unexpected error: \(e)")
157 | return
158 | }
159 |
160 | guard case .missingLeftOperand(_) = error.kind else {
161 | XCTFail("Unexpected error kind: \(error.kind)")
162 | return
163 | }
164 | }
165 | }
166 |
167 | func testMissingRightOperand() {
168 | do {
169 | let _ = try Expression(string: "-")
170 | XCTFail("Unexpected expression")
171 | } catch let e {
172 | guard let error = e as? MathParserError else {
173 | XCTFail("Unexpected error: \(e)")
174 | return
175 | }
176 |
177 | guard case .missingRightOperand(_) = error.kind else {
178 | XCTFail("Unexpected error kind: \(error.kind)")
179 | return
180 | }
181 | }
182 | }
183 |
184 | func testBinaryOperator() {
185 | guard let e = XCTAssertNoThrows(try Expression(string: "1+2")) else { return }
186 |
187 | guard case let .function("add", args) = e.kind else {
188 | XCTFail("Unexpected expression kind: \(e.kind)")
189 | return
190 | }
191 |
192 | XCTAssertEqual(args.count, 2)
193 |
194 | guard case .number(1) = args[0].kind else {
195 | XCTFail("Unexpected argument: \(args[0].kind)")
196 | return
197 | }
198 | guard case .number(2) = args[1].kind else {
199 | XCTFail("Unexpected argument: \(args[1].kind)")
200 | return
201 | }
202 | }
203 |
204 | func testBinaryOperatorCollapsingLeftOperands() {
205 | guard let e = XCTAssertNoThrows(try Expression(string: "2!**2")) else { return }
206 |
207 | guard case let .function("pow", args) = e.kind else {
208 | XCTFail("Unexpected expression kind: \(e.kind)")
209 | return
210 | }
211 |
212 | XCTAssertEqual(args.count, 2)
213 |
214 | guard case .function("factorial", _) = args[0].kind else {
215 | XCTFail("Unexpected argument: \(args[0].kind)")
216 | return
217 | }
218 | guard case .number(2) = args[1].kind else {
219 | XCTFail("Unexpected argument: \(args[1].kind)")
220 | return
221 | }
222 | }
223 |
224 | func testBinaryOperatorCollapsingRightOperands() {
225 | guard let e = XCTAssertNoThrows(try Expression(string: "2**-2")) else { return }
226 |
227 | guard case let .function("pow", args) = e.kind else {
228 | XCTFail("Unexpected expression kind: \(e.kind)")
229 | return
230 | }
231 |
232 | XCTAssertEqual(args.count, 2)
233 |
234 | guard case .number(2) = args[0].kind else {
235 | XCTFail("Unexpected argument: \(args[0].kind)")
236 | return
237 | }
238 | guard case .function("negate", _) = args[1].kind else {
239 | XCTFail("Unexpected argument: \(args[1].kind)")
240 | return
241 | }
242 | }
243 |
244 | func testBinaryOperatorMissingLeftOperand() {
245 | do {
246 | let _ = try Expression(string: "**2")
247 | XCTFail("Unexpected expression")
248 | } catch let e {
249 | guard let error = e as? MathParserError else {
250 | XCTFail("Unexpected error: \(e)")
251 | return
252 | }
253 |
254 | guard case .missingLeftOperand(_) = error.kind else {
255 | XCTFail("Unexpected error kind: \(error.kind)")
256 | return
257 | }
258 | }
259 | }
260 |
261 | func testBinaryOperatorMissingRightOperand() {
262 | do {
263 | let _ = try Expression(string: "2**")
264 | XCTFail("Unexpected expression")
265 | } catch let e {
266 | guard let error = e as? MathParserError else {
267 | XCTFail("Unexpected error: \(e)")
268 | return
269 | }
270 |
271 | guard case .missingRightOperand(_) = error.kind else {
272 | XCTFail("Unexpected error kind: \(error.kind)")
273 | return
274 | }
275 | }
276 | }
277 |
278 | func testGroup() {
279 | guard let e = XCTAssertNoThrows(try Expression(string: "1+(2+3)")) else { return }
280 |
281 | guard case let .function("add", args) = e.kind else {
282 | XCTFail("Unexpected expression kind: \(e.kind)")
283 | return
284 | }
285 |
286 | XCTAssertEqual(args.count, 2)
287 |
288 | guard case .number(1) = args[0].kind else {
289 | XCTFail("Unexpected argument: \(args[0].kind)")
290 | return
291 | }
292 | guard case .function("add", _) = args[1].kind else {
293 | XCTFail("Unexpected argument: \(args[1].kind)")
294 | return
295 | }
296 | }
297 |
298 | func testMissingOperator() {
299 | do {
300 | let tokenizer = Tokenizer(string: "1 2", configuration: .defaultWithEmptyOptions)
301 | let resolver = TokenResolver(tokenizer: tokenizer)
302 | let grouper = TokenGrouper(resolver: resolver)
303 | let expressionizer = Expressionizer(grouper: grouper)
304 | let _ = try expressionizer.expression()
305 | XCTFail("Unexpected expression")
306 | } catch let e {
307 | guard let error = e as? MathParserError else {
308 | XCTFail("Unexpected error: \(e)")
309 | return
310 | }
311 |
312 | guard case .invalidFormat = error.kind else {
313 | XCTFail("Unexpected error kind: \(error.kind)")
314 | return
315 | }
316 | }
317 | }
318 |
319 | func testUnaryPlus() {
320 | guard let e = XCTAssertNoThrows(try Expression(string: "+1")) else { return }
321 |
322 | guard case .number(1) = e.kind else {
323 | XCTFail("Unexpected expression: \(e)")
324 | return
325 | }
326 | }
327 |
328 | func testSimplifyWithNestedExpressions() {
329 | guard let e = XCTAssertNoThrows(try Expression(string: "$foo * 2 + $bar")) else { return }
330 | guard let foo = XCTAssertNoThrows(try Expression(string: "$bar + 2")) else { return }
331 |
332 | let simplified = e.simplify(["foo": foo], evaluator: .default)
333 | guard let expectedResult = XCTAssertNoThrows(try Expression(string: "($bar + 2) * 2 + $bar")) else { return }
334 | XCTAssertEqual(simplified, expectedResult)
335 | }
336 |
337 | func testInvalidFormat() {
338 | var c = Configuration.default
339 | c.allowImplicitMultiplication = false
340 | guard let e = XCTAssertNoThrows(try Expression(string: "3$x", configuration: c)) else { return }
341 | print("\(e)")
342 | }
343 |
344 | }
345 |
--------------------------------------------------------------------------------
/MathParser/Sources/MathParser/Expressionizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Expressionizer.swift
3 | // DDMathParser
4 | //
5 | // Created by Dave DeLong on 8/17/15.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension GroupedToken {
12 | fileprivate var groupedOperator: Operator? {
13 | guard case .operator(let o) = self.kind else { return nil }
14 | return o
15 | }
16 | }
17 |
18 | private enum TokenOrExpression {
19 | case token(GroupedToken)
20 | case expression(Expression)
21 |
22 | var token: GroupedToken? {
23 | guard case .token(let t) = self else { return nil }
24 | return t
25 | }
26 |
27 | var expression: Expression? {
28 | guard case .expression(let e) = self else { return nil }
29 | return e
30 | }
31 |
32 | var isToken: Bool { return token != nil }
33 |
34 | var range: Range {
35 | switch self {
36 | case .token(let t): return t.range
37 | case .expression(let e): return e.range
38 | }
39 | }
40 | }
41 |
42 | public struct Expressionizer {
43 | private let grouper: TokenGrouper
44 |
45 | public init(grouper: TokenGrouper) {
46 | self.grouper = grouper
47 | }
48 |
49 | public func expression() throws -> Expression {
50 | let rootToken = try grouper.group()
51 |
52 | return try expressionizeToken(rootToken)
53 | }
54 |
55 | internal func expressionizeToken(_ token: GroupedToken) throws -> Expression {
56 | switch token.kind {
57 | case .number(let d):
58 | return Expression(kind: .number(d), range: token.range)
59 | case .variable(let v):
60 | return Expression(kind: .variable(v), range: token.range)
61 |
62 | case .function(let f, let parameters):
63 | var parameterExpressions = Array()
64 | for parameter in parameters {
65 | let info = try expressionizeToken(parameter)
66 | parameterExpressions.append(info)
67 | }
68 | return Expression(kind: .function(f, parameterExpressions), range: token.range)
69 |
70 | case .operator(_):
71 | // this will ultimately result in an error,
72 | // but we'll let the group logic take care of that
73 | let newGroup = GroupedToken(kind: .group([token]), range: token.range)
74 | return try expressionizeToken(newGroup)
75 |
76 | case .group(let tokens):
77 | return try expressionizeGroup(tokens)
78 | }
79 | }
80 |
81 | private func expressionizeGroup(_ tokens: Array) throws -> Expression {
82 | var wrappers = tokens.map { TokenOrExpression.token($0) }
83 |
84 | while wrappers.count > 1 || wrappers.first?.isToken == true {
85 | let (indices, maybeOp) = operatorWithHighestPrecedence(wrappers)
86 | guard let first = indices.first, let last = indices.last else {
87 | let range: Range = wrappers.first?.range ?? 0 ..< 0
88 | throw MathParserError(kind: .invalidFormat, range: range)
89 | }
90 | guard let op = maybeOp else { fatalError("Indices but no operator??") }
91 |
92 | let index = op.associativity == .left ? first : last
93 | wrappers = try collapseWrappers(wrappers, aroundOperatorAtIndex: index)
94 | }
95 |
96 | guard let wrapper = wrappers.first else {
97 | fatalError("Implementation flaw")
98 | }
99 |
100 | switch wrapper {
101 | case .token(let t):
102 | return try expressionizeToken(t)
103 | case .expression(let e):
104 | return e
105 | }
106 | }
107 |
108 | private func operatorWithHighestPrecedence(_ wrappers: Array) -> (Array, Operator?) {
109 | var indices = Array()
110 |
111 | var precedence = Int.min
112 | var op: Operator?
113 |
114 | wrappers.enumerated().forEach { (index, wrapper) in
115 | guard let token = wrapper.token else { return }
116 | guard case let .operator(o) = token.kind else { return }
117 | guard let p = o.precedence else {
118 | fatalError("Operator with unknown precedence")
119 | }
120 |
121 | if p == precedence {
122 | indices.append(index)
123 | } else if p > precedence {
124 | precedence = p
125 | op = o
126 | indices.removeAll()
127 | indices.append(index)
128 | }
129 | }
130 |
131 | return (indices, op)
132 | }
133 |
134 | private func collapseWrappers(_ wrappers: Array, aroundOperatorAtIndex index: Int) throws -> Array {
135 | guard let op = wrappers[index].token?.groupedOperator else { fatalError("Implementation flaw") }
136 | switch (op.arity, op.associativity) {
137 | case (.binary, _):
138 | return try collapseWrappers(wrappers, aroundBinaryOperator: op, atIndex: index)
139 | case (.unary, .left):
140 | var inoutIndex = index
141 | return try collapseWrappers(wrappers, aroundLeftUnaryOperator: op, atIndex: &inoutIndex)
142 | case (.unary, .right):
143 | return try collapseWrappers(wrappers, aroundRightUnaryOperator: op, atIndex: index)
144 |
145 | }
146 | }
147 |
148 | private func collapseWrappers(_ wrappers: Array, aroundBinaryOperator op: Operator, atIndex index: Int) throws -> Array {
149 | let operatorWrapper = wrappers[index]
150 |
151 | guard index > 0 else {
152 | throw MathParserError(kind: .missingLeftOperand(op), range: operatorWrapper.range)
153 | }
154 | guard index < wrappers.count - 1 else {
155 | throw MathParserError(kind: .missingRightOperand(op), range: operatorWrapper.range)
156 | }
157 |
158 | var operatorIndex = index
159 | var rightIndex = operatorIndex + 1
160 | var rightWrapper = wrappers[rightIndex]
161 |
162 | var collapsedWrappers = wrappers
163 | if let t = rightWrapper.token {
164 | if let o = t.groupedOperator, o.associativity == .right && o.arity == .unary {
165 | collapsedWrappers = try collapseWrappers(collapsedWrappers, aroundRightUnaryOperator: o, atIndex: rightIndex)
166 |
167 | rightWrapper = collapsedWrappers[rightIndex]
168 | } else {
169 | rightWrapper = .expression(try expressionizeToken(t))
170 | }
171 | }
172 | collapsedWrappers[rightIndex] = rightWrapper
173 |
174 | var leftIndex = index - 1
175 | var leftWrapper = collapsedWrappers[leftIndex]
176 | if let t = leftWrapper.token {
177 | if let o = t.groupedOperator, o.associativity == .left && o.arity == .unary {
178 | collapsedWrappers = try collapseWrappers(collapsedWrappers, aroundLeftUnaryOperator: o, atIndex: &leftIndex)
179 |
180 | leftWrapper = collapsedWrappers[leftIndex]
181 | operatorIndex = leftIndex + 1
182 | rightIndex = operatorIndex + 1
183 | } else {
184 | leftWrapper = .expression(try expressionizeToken(t))
185 | }
186 | }
187 |
188 | guard let leftOperand = leftWrapper.expression else { fatalError("Never resolved left operand") }
189 | guard let rightOperand = rightWrapper.expression else { fatalError("Never resolved right operand") }
190 |
191 | let range: Range = leftOperand.range.lowerBound ..< rightOperand.range.upperBound
192 | let expression = Expression(kind: .function(op.function, [leftOperand, rightOperand]), range: range)
193 |
194 | let replacementRange = leftIndex ... rightIndex
195 | collapsedWrappers.replaceSubrange(replacementRange, with: [.expression(expression)])
196 |
197 | return collapsedWrappers
198 | }
199 |
200 | private func collapseWrappers(_ wrappers: Array, aroundLeftUnaryOperator op: Operator, atIndex index: inout Int) throws -> Array {
201 | var operatorIndex = index
202 | let operatorWrapper = wrappers[operatorIndex]
203 |
204 | guard operatorIndex > 0 else {
205 | throw MathParserError(kind: .missingLeftOperand(op), range: operatorWrapper.range) // Missing operand
206 | }
207 |
208 | var operandIndex = operatorIndex - 1
209 | var operandWrapper = wrappers[operandIndex]
210 |
211 | var collapsedWrappers = wrappers
212 | if let t = operandWrapper.token {
213 | if let o = t.groupedOperator, o.associativity == .left && o.arity == .unary {
214 | // recursively collapse left unary operators
215 | // technically, this should never happen, because left unary operators
216 | // are left-associative, which means they evaluate from left-to-right
217 | // This means that a left-assoc unary operator should never have another
218 | // left-assoc unary operator to its left, because it would've already
219 | // have been resolved
220 | // Regardless, this is here for completeness
221 | var newOperandIndex = operandIndex
222 | collapsedWrappers = try collapseWrappers(collapsedWrappers, aroundLeftUnaryOperator: o, atIndex: &newOperandIndex)
223 |
224 | let indexDelta = operandIndex - newOperandIndex
225 | operatorIndex = operatorIndex - indexDelta
226 | operandIndex = operandIndex - 1
227 | } else {
228 | operandWrapper = .expression(try expressionizeToken(t))
229 | }
230 | }
231 |
232 | guard let operand = operandWrapper.expression else {
233 | fatalError("Implementation flaw")
234 | }
235 |
236 | let range: Range = operandWrapper.range.lowerBound ..< operatorWrapper.range.upperBound
237 | let expression = Expression(kind: .function(op.function, [operand]), range: range)
238 |
239 | let replacementRange = operandIndex ... operatorIndex
240 | collapsedWrappers.replaceSubrange(replacementRange, with: [.expression(expression)])
241 |
242 | index = operandIndex
243 | return collapsedWrappers
244 | }
245 |
246 | private func collapseWrappers(_ wrappers: Array, aroundRightUnaryOperator op: Operator, atIndex index: Int) throws -> Array {
247 | var collapsedWrappers = wrappers
248 |
249 | let operatorWrapper = collapsedWrappers[index]
250 | let operandIndex = index + 1
251 |
252 | guard operandIndex < wrappers.count else {
253 | throw MathParserError(kind: .missingRightOperand(op), range: operatorWrapper.range) // Missing operand
254 | }
255 |
256 |
257 | var operandWrapper = collapsedWrappers[operandIndex];
258 |
259 | if let t = operandWrapper.token {
260 | if let o = t.groupedOperator, o.associativity == .right && o.arity == .unary {
261 | // recursively collapse right unary operators
262 | // technically, this should never happen, because right unary operators
263 | // are right-associative, which means they evaluate from right-to-left
264 | // This means that a right-assoc unary operator should never have another
265 | // right-assoc unary operator to its right, because it would've already
266 | // have been resolved
267 | // Regardless, this is here for completeness
268 | collapsedWrappers = try collapseWrappers(collapsedWrappers, aroundRightUnaryOperator: o, atIndex: operandIndex)
269 | operandWrapper = collapsedWrappers[operandIndex]
270 | } else {
271 | operandWrapper = .expression(try expressionizeToken(t))
272 | }
273 | }
274 |
275 | guard let operand = operandWrapper.expression else {
276 | fatalError("Implementation flaw")
277 | }
278 |
279 | let range: Range = operatorWrapper.range.lowerBound ..< operand.range.upperBound
280 | let expression: Expression
281 |
282 | if op.builtInOperator == .unaryPlus {
283 | // the Unary Plus operator does nothing and should be ignored
284 | expression = operand
285 | } else {
286 | expression = Expression(kind: .function(op.function, [operand]), range: range)
287 | }
288 |
289 | let replacementExpressionRange = index ... operandIndex
290 | collapsedWrappers.replaceSubrange(replacementExpressionRange, with: [.expression(expression)])
291 |
292 | return collapsedWrappers
293 | }
294 | }
295 |
--------------------------------------------------------------------------------