├── 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 | 58 | 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 | 58 | 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 | 58 | 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 | 58 | 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 | 80 | 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 | --------------------------------------------------------------------------------