├── .gitignore ├── Expression.playground ├── Contents.swift ├── Pages │ ├── ArithmeticExpression.xcplaygroundpage │ │ └── Contents.swift │ ├── BinarySearchTree.xcplaygroundpage │ │ └── Contents.swift │ ├── LogicalExpression.xcplaygroundpage │ │ └── Contents.swift │ └── RedBlackTree.xcplaygroundpage │ │ └── Contents.swift ├── Sources │ ├── AnimateEvaluation.swift │ └── Extensions │ │ └── UIImage.swift └── contents.xcplayground ├── Expression.xcodeproj ├── Expression.xcworkspace │ └── contents.xcworkspacedata ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Expression.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── WorkspaceSettings.xcsettings ├── Expression ├── Expression.h └── Info.plist ├── Images ├── ArithmeticExpression Evaluation.gif ├── ArithmeticExpression.png ├── Banner.png └── LogicalExpression Evaluation.gif ├── LICENSE ├── README.md └── Sources ├── Expressions ├── ArithmeticExpression.swift ├── ExpressionNodeKind.swift ├── FloatingPointArithmeticExpression.swift ├── LogicalExpression.swift └── Protocols │ ├── ArithmeticExpressionProtocol.swift │ ├── Evaluatable.swift │ ├── EvaluatableExpressionProtocol.swift │ └── LogicalExpressionProtocol.swift ├── Extensions ├── Divisible.swift └── Numeric.swift ├── Operators ├── Binary │ ├── BinaryOperatorAssociativity.swift │ ├── BinaryOperatorPrecedence.swift │ ├── ComparativeOperator.swift │ ├── LogicalBinaryOperator.swift │ ├── NumericBinaryOperator.swift │ └── Protocols │ │ ├── BinaryOperatorProtocol.swift │ │ ├── ComparativeOperatorProtocol.swift │ │ ├── EquatableOperatorProtocol.swift │ │ ├── LogicalBinaryOperatorProtocol.swift │ │ ├── NumericBinaryOperatorProtocol.swift │ │ └── ReferenceEquatableOperatorProtocol.swift ├── OperatorNodeKind.swift ├── Protocols │ ├── OperandProtocol.swift │ └── OperatorProtocol.swift └── Unary │ ├── LogicalUnaryOperator.swift │ ├── NumericUnaryOperator.swift │ └── Protocols │ ├── LogicalUnaryOperatorProtocol.swift │ ├── NumericUnaryOperatorProtocol.swift │ └── UnaryOperatorProtocol.swift ├── Trees ├── BinarySearchTree.swift ├── NeverEmptyTreeNode.swift ├── Protocols │ ├── BinarySearchTreeProtocol.swift │ ├── BinaryTreeProtocol.swift │ ├── NeverEmptyTreeProtocol.swift │ ├── SingleTypeBinaryTreeProtocol.swift │ ├── SingleTypeTreeProtocol.swift │ └── TreeProtocol.swift ├── RedBlackTree.swift └── TreeNode.swift └── Visualization ├── BinaryTreeNodeView.swift ├── CustomPlaygroundQuickLookableBinaryTreeProtocol.swift ├── CustomVisualAttributes.swift ├── EvaluatableExpressionViewFactory.swift ├── Extensions ├── CGContext.swift ├── CGRect.swift ├── String.swift └── UIColor.swift ├── LineView.swift ├── NodeVisualAttributes.swift └── PositionedBinaryTree.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /Expression.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | -------------------------------------------------------------------------------- /Expression.playground/Pages/ArithmeticExpression.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NOTE: If you're using Xcode and seeing raw markdown contents, go to the 3 | // Editor menu -> Show Rendered Markup. 4 | // 5 | 6 | /*: 7 | ## Visualizing Arithmetic Expressions 8 | Create the expression you want to visualize by typing it naturally, using integer literals 9 | and operators. Its tree representation is readily available through the Playground QuickLook. 10 | */ 11 | import Expression 12 | 13 | let expression: ArithmeticExpression = 2*(1+3)-8/4 14 | /*: 15 | Integer expressions can utilize any of the standard operators you might encounter in Swift, 16 | including the bitwise operators and the arithmetic operators that ignore overflow. 17 | */ 18 | let fancyExpression: ArithmeticExpression = 2<<3|8&*(5&3) 19 | /*: 20 | Use `FloatingPointArithmeticExpression` for expressions with operands of floating point types. 21 | */ 22 | let floatingPointExpression: FloatingPointArithmeticExpression = 1.5*2.0-4.5/3.0 23 | /*: 24 | Animate the evaluation of an expression using `animateEvaluation(of:)`. The animation will be presented 25 | in the Playground's Live View, which is visible in the Assistant Editor. 26 | */ 27 | animateEvaluation(of: expression) 28 | -------------------------------------------------------------------------------- /Expression.playground/Pages/BinarySearchTree.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NOTE: If you're using Xcode and seeing raw markdown contents, go to the 3 | // Editor menu -> Show Rendered Markup. 4 | // 5 | 6 | /*: 7 | ## Visualizing Binary Search Trees 8 | While the initial aim of this project was to visualize expressions as trees, 9 | protocol-oriented programming allows for easy implementation of other tree structures--and 10 | we get visualization through Playground QuickLook for free. 11 | */ 12 | import Expression 13 | 14 | let tree = BinarySearchTree("the quick brown fox jumps over the lazy dog") 15 | /*: 16 | Changes to a tree's structure can be visualized via comparison before/after an insertion or deletion. 17 | */ 18 | var tree2 = BinarySearchTree([2, 3, 5, 1, 4, 6, 7, 8, 10, 9]) 19 | tree2.insert(11) 20 | tree2.delete(5) 21 | /*: 22 | You may notice that the node positioning algorithm lines up a node's single child directly below it. 23 | This can make it difficult to distinguish between right and left child nodes. To better visualize this, 24 | you can use the tree's `renderWide()` method. However--as the name suggests--these images can 25 | easily grow wide, as the width of the image grows exponentially with the tree's height. A future 26 | update to this project will use a different positioning algorithm to remedy this. 27 | */ 28 | tree2.renderWide() 29 | -------------------------------------------------------------------------------- /Expression.playground/Pages/LogicalExpression.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NOTE: If you're using Xcode and seeing raw markdown contents, go to the 3 | // Editor menu -> Show Rendered Markup. 4 | // 5 | 6 | /*: 7 | ## Visualizing Logical Expressions 8 | Akin to arithmetic expressions, logical expressions can be written 9 | and visualized using natural Swift. 10 | */ 11 | import Expression 12 | 13 | let expression: LogicalExpression = !(true || false) && false || !true 14 | /*: 15 | And, of course, the evaluation of these expressions can be animated in the Playground's live view. 16 | */ 17 | animateEvaluation(of: expression) 18 | -------------------------------------------------------------------------------- /Expression.playground/Pages/RedBlackTree.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NOTE: If you're using Xcode and seeing raw markdown contents, go to the 3 | // Editor menu -> Show Rendered Markup. 4 | // 5 | 6 | /*: 7 | ## Visualizing Red-Black Trees 8 | A red-black tree is a form of self-balancing binary search tree where each node 9 | is labeled "red" or "black" according to a certain set of rules. 10 | The color structure of these trees can be seen through the Playground QuickLook. 11 | */ 12 | import Expression 13 | 14 | let tree = RedBlackTree("the quick brown fox jumps over the lazy dog") 15 | /*: 16 | Changes to a tree's structure can be visualized by comparing before 17 | and after an insertion. A future update to this project will support deletion 18 | in red-black trees. 19 | */ 20 | var tree2 = RedBlackTree(1...10) 21 | tree2.insert(11) 22 | -------------------------------------------------------------------------------- /Expression.playground/Sources/AnimateEvaluation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import Expression 4 | 5 | 6 | private let animationDuration = 3.0 7 | 8 | public func animateEvaluation(of expression: T) where T: EvaluatableExpressionProtocol { 9 | let (liveView, rootNodeView) = EvaluatableExpressionViewFactory.makeView(of: expression) 10 | PlaygroundPage.current.liveView = liveView 11 | animateEvaluation(of: rootNodeView) 12 | } 13 | 14 | private func animateEvaluation(of nodeView: BinaryTreeNodeView, completion: (() -> Void)? = nil) { 15 | switch nodeView.childNodeViews.count { 16 | case 0: 17 | completion?() 18 | return 19 | case 1: 20 | animateEvaluation(of: nodeView.childNodeViews[0]) { 21 | UIView.animate(withDuration: animationDuration, 22 | animations: nodeView.bringInChildNodes, 23 | completion: { _ in 24 | nodeView.updateToNextState() 25 | completion?() 26 | }) 27 | } 28 | case 2: 29 | let leftNodeView = nodeView.childNodeViews[0] 30 | let rightNodeView = nodeView.childNodeViews[1] 31 | animateEvaluation(of: leftNodeView) { 32 | animateEvaluation(of: rightNodeView) { 33 | UIView.animate(withDuration: animationDuration, 34 | animations: nodeView.bringInChildNodes, 35 | completion: { _ in 36 | nodeView.updateToNextState() 37 | completion?() 38 | }) 39 | } 40 | } 41 | default: 42 | fatalError("A binary tree node can have no more than two children.") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Expression.playground/Sources/Extensions/UIImage.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | 5 | extension UIImage { 6 | public func saveToDocumentsDirectory(as name: String) { 7 | guard let data = UIImagePNGRepresentation(self) else { print("Could not obtain image data."); return } 8 | // To save properly, there must be a "Shared Playground Data" folder in Documents with the appropriate permissions set. 9 | let filename = playgroundSharedDataDirectory.appendingPathComponent(name) 10 | try! data.write(to: filename) // This will spit out the appropriate error in the Playground console if it occurs. 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Expression.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Expression.xcodeproj/Expression.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Expression.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 891DE57C1FECBA2100DEB654 /* LineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891DE57B1FECBA2100DEB654 /* LineView.swift */; }; 11 | 893133D81FE5BF2F00FE926E /* Expression.h in Headers */ = {isa = PBXBuildFile; fileRef = 893133CA1FE5BF2F00FE926E /* Expression.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 893133FA1FE5BF3700FE926E /* TreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133E21FE5BF3700FE926E /* TreeNode.swift */; }; 13 | 893133FC1FE5BF3700FE926E /* ArithmeticExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133E41FE5BF3700FE926E /* ArithmeticExpression.swift */; }; 14 | 893133FD1FE5BF3700FE926E /* Numeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133E61FE5BF3700FE926E /* Numeric.swift */; }; 15 | 893133FF1FE5BF3700FE926E /* BinaryOperatorPrecedence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133E81FE5BF3700FE926E /* BinaryOperatorPrecedence.swift */; }; 16 | 893134001FE5BF3700FE926E /* BinaryOperatorAssociativity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133E91FE5BF3700FE926E /* BinaryOperatorAssociativity.swift */; }; 17 | 893134021FE5BF3700FE926E /* OperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133ED1FE5BF3700FE926E /* OperatorProtocol.swift */; }; 18 | 893134031FE5BF3700FE926E /* BinaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133EE1FE5BF3700FE926E /* BinaryOperatorProtocol.swift */; }; 19 | 893134051FE5BF3700FE926E /* NumericBinaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F01FE5BF3700FE926E /* NumericBinaryOperatorProtocol.swift */; }; 20 | 893134081FE5BF3700FE926E /* Evaluatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F31FE5BF3700FE926E /* Evaluatable.swift */; }; 21 | 893134091FE5BF3700FE926E /* BinaryTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F41FE5BF3700FE926E /* BinaryTreeProtocol.swift */; }; 22 | 8931340A1FE5BF3700FE926E /* ArithmeticExpressionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F51FE5BF3700FE926E /* ArithmeticExpressionProtocol.swift */; }; 23 | 8931340C1FE5BF3700FE926E /* TreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F71FE5BF3700FE926E /* TreeProtocol.swift */; }; 24 | 8931340D1FE5BF3700FE926E /* Divisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F81FE5BF3700FE926E /* Divisible.swift */; }; 25 | 8931340E1FE5BF3700FE926E /* OperandProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893133F91FE5BF3700FE926E /* OperandProtocol.swift */; }; 26 | 893BF6A61FEDDF7500C626F0 /* NeverEmptyTreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893BF6A51FEDDF7500C626F0 /* NeverEmptyTreeNode.swift */; }; 27 | 8945BD721FF60DC1004AB1ED /* NumericBinaryOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8945BD711FF60DC1004AB1ED /* NumericBinaryOperator.swift */; }; 28 | 896801991FE7172200BAAC2C /* SingleTypeTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896801981FE7172200BAAC2C /* SingleTypeTreeProtocol.swift */; }; 29 | 8968019B1FE717E300BAAC2C /* SingleTypeBinaryTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8968019A1FE717E300BAAC2C /* SingleTypeBinaryTreeProtocol.swift */; }; 30 | 8968019D1FE71C0300BAAC2C /* NodeVisualAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8968019C1FE71C0300BAAC2C /* NodeVisualAttributes.swift */; }; 31 | 8968019F1FE71C5500BAAC2C /* CustomPlaygroundQuickLookableBinaryTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8968019E1FE71C5500BAAC2C /* CustomPlaygroundQuickLookableBinaryTreeProtocol.swift */; }; 32 | 896801A31FE726E300BAAC2C /* BinarySearchTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896801A21FE726E300BAAC2C /* BinarySearchTreeProtocol.swift */; }; 33 | 896801A51FE72C1A00BAAC2C /* RedBlackTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896801A41FE72C1A00BAAC2C /* RedBlackTree.swift */; }; 34 | 896D89381FE8F88700801D73 /* ComparativeOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896D89371FE8F88700801D73 /* ComparativeOperator.swift */; }; 35 | 8971D0DB1FE6ECD900F14793 /* FloatingPointArithmeticExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8971D0DA1FE6ECD900F14793 /* FloatingPointArithmeticExpression.swift */; }; 36 | 8983E9441FE9AA6A005618C8 /* BinarySearchTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8983E9431FE9AA6A005618C8 /* BinarySearchTree.swift */; }; 37 | 8983E9461FE9B51D005618C8 /* CustomVisualAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8983E9451FE9B51D005618C8 /* CustomVisualAttributes.swift */; }; 38 | 8987ED391FEA126300963A1F /* BinaryTreeNodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8987ED381FEA126300963A1F /* BinaryTreeNodeView.swift */; }; 39 | 898A98821FE8863100594C49 /* LogicalBinaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98811FE8863100594C49 /* LogicalBinaryOperatorProtocol.swift */; }; 40 | 898A98841FE8877E00594C49 /* EquatableOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98831FE8877E00594C49 /* EquatableOperatorProtocol.swift */; }; 41 | 898A98861FE88BBA00594C49 /* ReferenceEquatableOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98851FE88BBA00594C49 /* ReferenceEquatableOperatorProtocol.swift */; }; 42 | 898A98881FE890EE00594C49 /* ComparativeOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98871FE890EE00594C49 /* ComparativeOperatorProtocol.swift */; }; 43 | 898A988A1FE8956200594C49 /* EvaluatableExpressionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98891FE8956200594C49 /* EvaluatableExpressionProtocol.swift */; }; 44 | 898A988C1FE8977800594C49 /* LogicalExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A988B1FE8977800594C49 /* LogicalExpression.swift */; }; 45 | 898A988E1FE897BF00594C49 /* LogicalBinaryOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A988D1FE897BF00594C49 /* LogicalBinaryOperator.swift */; }; 46 | 898A98921FE89E4300594C49 /* LogicalExpressionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A98911FE89E4300594C49 /* LogicalExpressionProtocol.swift */; }; 47 | 89B323C11FEDB93400710AAD /* EvaluatableExpressionViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B323C01FEDB93400710AAD /* EvaluatableExpressionViewFactory.swift */; }; 48 | 89CDE0791FF37C0400F53AB3 /* UnaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE0781FF37C0400F53AB3 /* UnaryOperatorProtocol.swift */; }; 49 | 89CDE07B1FF37C8000F53AB3 /* LogicalUnaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE07A1FF37C8000F53AB3 /* LogicalUnaryOperatorProtocol.swift */; }; 50 | 89CDE07D1FF3898700F53AB3 /* ExpressionNodeKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE07C1FF3898700F53AB3 /* ExpressionNodeKind.swift */; }; 51 | 89CDE07F1FF46CD900F53AB3 /* OperatorNodeKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE07E1FF46CD800F53AB3 /* OperatorNodeKind.swift */; }; 52 | 89CDE0811FF47E1000F53AB3 /* LogicalUnaryOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE0801FF47E1000F53AB3 /* LogicalUnaryOperator.swift */; }; 53 | 89CDE0851FF5880F00F53AB3 /* NumericUnaryOperatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE0841FF5880F00F53AB3 /* NumericUnaryOperatorProtocol.swift */; }; 54 | 89CDE0891FF58E8D00F53AB3 /* NumericUnaryOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CDE0881FF58E8D00F53AB3 /* NumericUnaryOperator.swift */; }; 55 | 89F5D5031FE7453E00812AC2 /* NeverEmptyTreeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F5D5021FE7453E00812AC2 /* NeverEmptyTreeProtocol.swift */; }; 56 | 89F7D2601FE5CD0700160A4B /* PositionedBinaryTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7D25F1FE5CD0700160A4B /* PositionedBinaryTree.swift */; }; 57 | 89F7D26B1FE5D1F300160A4B /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7D2671FE5D1F300160A4B /* UIColor.swift */; }; 58 | 89F7D26C1FE5D1F300160A4B /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7D2681FE5D1F300160A4B /* String.swift */; }; 59 | 89F7D26D1FE5D1F300160A4B /* CGRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7D2691FE5D1F300160A4B /* CGRect.swift */; }; 60 | 89F7D26E1FE5D1F300160A4B /* CGContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7D26A1FE5D1F300160A4B /* CGContext.swift */; }; 61 | /* End PBXBuildFile section */ 62 | 63 | /* Begin PBXFileReference section */ 64 | 891DE57B1FECBA2100DEB654 /* LineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineView.swift; sourceTree = ""; }; 65 | 893133C71FE5BF2F00FE926E /* Expression.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Expression.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 893133CA1FE5BF2F00FE926E /* Expression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Expression.h; sourceTree = ""; }; 67 | 893133CB1FE5BF2F00FE926E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 893133E21FE5BF3700FE926E /* TreeNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeNode.swift; sourceTree = ""; }; 69 | 893133E41FE5BF3700FE926E /* ArithmeticExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArithmeticExpression.swift; sourceTree = ""; }; 70 | 893133E61FE5BF3700FE926E /* Numeric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Numeric.swift; sourceTree = ""; }; 71 | 893133E81FE5BF3700FE926E /* BinaryOperatorPrecedence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryOperatorPrecedence.swift; sourceTree = ""; }; 72 | 893133E91FE5BF3700FE926E /* BinaryOperatorAssociativity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryOperatorAssociativity.swift; sourceTree = ""; }; 73 | 893133ED1FE5BF3700FE926E /* OperatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorProtocol.swift; sourceTree = ""; }; 74 | 893133EE1FE5BF3700FE926E /* BinaryOperatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryOperatorProtocol.swift; sourceTree = ""; }; 75 | 893133F01FE5BF3700FE926E /* NumericBinaryOperatorProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericBinaryOperatorProtocol.swift; sourceTree = ""; }; 76 | 893133F31FE5BF3700FE926E /* Evaluatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Evaluatable.swift; sourceTree = ""; }; 77 | 893133F41FE5BF3700FE926E /* BinaryTreeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinaryTreeProtocol.swift; sourceTree = ""; }; 78 | 893133F51FE5BF3700FE926E /* ArithmeticExpressionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArithmeticExpressionProtocol.swift; sourceTree = ""; }; 79 | 893133F71FE5BF3700FE926E /* TreeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeProtocol.swift; sourceTree = ""; }; 80 | 893133F81FE5BF3700FE926E /* Divisible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Divisible.swift; sourceTree = ""; }; 81 | 893133F91FE5BF3700FE926E /* OperandProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperandProtocol.swift; sourceTree = ""; }; 82 | 893BF6A51FEDDF7500C626F0 /* NeverEmptyTreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeverEmptyTreeNode.swift; sourceTree = ""; }; 83 | 8945BD711FF60DC1004AB1ED /* NumericBinaryOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericBinaryOperator.swift; sourceTree = ""; }; 84 | 896801981FE7172200BAAC2C /* SingleTypeTreeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTypeTreeProtocol.swift; sourceTree = ""; }; 85 | 8968019A1FE717E300BAAC2C /* SingleTypeBinaryTreeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTypeBinaryTreeProtocol.swift; sourceTree = ""; }; 86 | 8968019C1FE71C0300BAAC2C /* NodeVisualAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeVisualAttributes.swift; sourceTree = ""; }; 87 | 8968019E1FE71C5500BAAC2C /* CustomPlaygroundQuickLookableBinaryTreeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPlaygroundQuickLookableBinaryTreeProtocol.swift; sourceTree = ""; }; 88 | 896801A21FE726E300BAAC2C /* BinarySearchTreeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinarySearchTreeProtocol.swift; sourceTree = ""; }; 89 | 896801A41FE72C1A00BAAC2C /* RedBlackTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedBlackTree.swift; sourceTree = ""; }; 90 | 896D89371FE8F88700801D73 /* ComparativeOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComparativeOperator.swift; sourceTree = ""; }; 91 | 8971D0DA1FE6ECD900F14793 /* FloatingPointArithmeticExpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPointArithmeticExpression.swift; sourceTree = ""; }; 92 | 8983E9431FE9AA6A005618C8 /* BinarySearchTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinarySearchTree.swift; sourceTree = ""; }; 93 | 8983E9451FE9B51D005618C8 /* CustomVisualAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomVisualAttributes.swift; sourceTree = ""; }; 94 | 8987ED381FEA126300963A1F /* BinaryTreeNodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryTreeNodeView.swift; sourceTree = ""; }; 95 | 898A98811FE8863100594C49 /* LogicalBinaryOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalBinaryOperatorProtocol.swift; sourceTree = ""; }; 96 | 898A98831FE8877E00594C49 /* EquatableOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EquatableOperatorProtocol.swift; sourceTree = ""; }; 97 | 898A98851FE88BBA00594C49 /* ReferenceEquatableOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferenceEquatableOperatorProtocol.swift; sourceTree = ""; }; 98 | 898A98871FE890EE00594C49 /* ComparativeOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComparativeOperatorProtocol.swift; sourceTree = ""; }; 99 | 898A98891FE8956200594C49 /* EvaluatableExpressionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvaluatableExpressionProtocol.swift; sourceTree = ""; }; 100 | 898A988B1FE8977800594C49 /* LogicalExpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalExpression.swift; sourceTree = ""; }; 101 | 898A988D1FE897BF00594C49 /* LogicalBinaryOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalBinaryOperator.swift; sourceTree = ""; }; 102 | 898A98911FE89E4300594C49 /* LogicalExpressionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalExpressionProtocol.swift; sourceTree = ""; }; 103 | 89B323C01FEDB93400710AAD /* EvaluatableExpressionViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvaluatableExpressionViewFactory.swift; sourceTree = ""; }; 104 | 89CDE0781FF37C0400F53AB3 /* UnaryOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnaryOperatorProtocol.swift; sourceTree = ""; }; 105 | 89CDE07A1FF37C8000F53AB3 /* LogicalUnaryOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalUnaryOperatorProtocol.swift; sourceTree = ""; }; 106 | 89CDE07C1FF3898700F53AB3 /* ExpressionNodeKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressionNodeKind.swift; sourceTree = ""; }; 107 | 89CDE07E1FF46CD800F53AB3 /* OperatorNodeKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatorNodeKind.swift; sourceTree = ""; }; 108 | 89CDE0801FF47E1000F53AB3 /* LogicalUnaryOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogicalUnaryOperator.swift; sourceTree = ""; }; 109 | 89CDE0841FF5880F00F53AB3 /* NumericUnaryOperatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericUnaryOperatorProtocol.swift; sourceTree = ""; }; 110 | 89CDE0881FF58E8D00F53AB3 /* NumericUnaryOperator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericUnaryOperator.swift; sourceTree = ""; }; 111 | 89F5D5021FE7453E00812AC2 /* NeverEmptyTreeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeverEmptyTreeProtocol.swift; sourceTree = ""; }; 112 | 89F7D25F1FE5CD0700160A4B /* PositionedBinaryTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionedBinaryTree.swift; sourceTree = ""; }; 113 | 89F7D2671FE5D1F300160A4B /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 114 | 89F7D2681FE5D1F300160A4B /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 115 | 89F7D2691FE5D1F300160A4B /* CGRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGRect.swift; sourceTree = ""; }; 116 | 89F7D26A1FE5D1F300160A4B /* CGContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGContext.swift; sourceTree = ""; }; 117 | /* End PBXFileReference section */ 118 | 119 | /* Begin PBXFrameworksBuildPhase section */ 120 | 893133C31FE5BF2F00FE926E /* Frameworks */ = { 121 | isa = PBXFrameworksBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXFrameworksBuildPhase section */ 128 | 129 | /* Begin PBXGroup section */ 130 | 893133BD1FE5BF2F00FE926E = { 131 | isa = PBXGroup; 132 | children = ( 133 | 893133E11FE5BF3700FE926E /* Sources */, 134 | 893133C91FE5BF2F00FE926E /* Expression */, 135 | 893133C81FE5BF2F00FE926E /* Products */, 136 | ); 137 | sourceTree = ""; 138 | }; 139 | 893133C81FE5BF2F00FE926E /* Products */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 893133C71FE5BF2F00FE926E /* Expression.framework */, 143 | ); 144 | name = Products; 145 | sourceTree = ""; 146 | }; 147 | 893133C91FE5BF2F00FE926E /* Expression */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 893133CA1FE5BF2F00FE926E /* Expression.h */, 151 | 893133CB1FE5BF2F00FE926E /* Info.plist */, 152 | ); 153 | path = Expression; 154 | sourceTree = ""; 155 | }; 156 | 893133E11FE5BF3700FE926E /* Sources */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 8989A71B1FF5CE26002CDB1F /* Expressions */, 160 | 893133E51FE5BF3700FE926E /* Extensions */, 161 | 89DAED561FF3445800BE78A0 /* Operators */, 162 | 8989A71C1FF5CE7B002CDB1F /* Trees */, 163 | 89F7D25E1FE5CA5700160A4B /* Visualization */, 164 | ); 165 | path = Sources; 166 | sourceTree = ""; 167 | }; 168 | 893133E51FE5BF3700FE926E /* Extensions */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 893133F81FE5BF3700FE926E /* Divisible.swift */, 172 | 893133E61FE5BF3700FE926E /* Numeric.swift */, 173 | ); 174 | path = Extensions; 175 | sourceTree = ""; 176 | }; 177 | 893133EB1FE5BF3700FE926E /* Protocols */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 893133F91FE5BF3700FE926E /* OperandProtocol.swift */, 181 | 893133ED1FE5BF3700FE926E /* OperatorProtocol.swift */, 182 | ); 183 | path = Protocols; 184 | sourceTree = ""; 185 | }; 186 | 896D89331FE8EE4800801D73 /* Protocols */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 893133F31FE5BF3700FE926E /* Evaluatable.swift */, 190 | 893133F51FE5BF3700FE926E /* ArithmeticExpressionProtocol.swift */, 191 | 898A98891FE8956200594C49 /* EvaluatableExpressionProtocol.swift */, 192 | 898A98911FE89E4300594C49 /* LogicalExpressionProtocol.swift */, 193 | ); 194 | path = Protocols; 195 | sourceTree = ""; 196 | }; 197 | 896D89341FE8EE6800801D73 /* Protocols */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 896801A21FE726E300BAAC2C /* BinarySearchTreeProtocol.swift */, 201 | 893133F41FE5BF3700FE926E /* BinaryTreeProtocol.swift */, 202 | 89F5D5021FE7453E00812AC2 /* NeverEmptyTreeProtocol.swift */, 203 | 8968019A1FE717E300BAAC2C /* SingleTypeBinaryTreeProtocol.swift */, 204 | 896801981FE7172200BAAC2C /* SingleTypeTreeProtocol.swift */, 205 | 893133F71FE5BF3700FE926E /* TreeProtocol.swift */, 206 | ); 207 | path = Protocols; 208 | sourceTree = ""; 209 | }; 210 | 8989A71B1FF5CE26002CDB1F /* Expressions */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | 893133E41FE5BF3700FE926E /* ArithmeticExpression.swift */, 214 | 89CDE07C1FF3898700F53AB3 /* ExpressionNodeKind.swift */, 215 | 8971D0DA1FE6ECD900F14793 /* FloatingPointArithmeticExpression.swift */, 216 | 898A988B1FE8977800594C49 /* LogicalExpression.swift */, 217 | 896D89331FE8EE4800801D73 /* Protocols */, 218 | ); 219 | path = Expressions; 220 | sourceTree = ""; 221 | }; 222 | 8989A71C1FF5CE7B002CDB1F /* Trees */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 8983E9431FE9AA6A005618C8 /* BinarySearchTree.swift */, 226 | 893BF6A51FEDDF7500C626F0 /* NeverEmptyTreeNode.swift */, 227 | 896801A41FE72C1A00BAAC2C /* RedBlackTree.swift */, 228 | 893133E21FE5BF3700FE926E /* TreeNode.swift */, 229 | 896D89341FE8EE6800801D73 /* Protocols */, 230 | ); 231 | path = Trees; 232 | sourceTree = ""; 233 | }; 234 | 8989A71D1FF5CED1002CDB1F /* Binary */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 893133E91FE5BF3700FE926E /* BinaryOperatorAssociativity.swift */, 238 | 893133E81FE5BF3700FE926E /* BinaryOperatorPrecedence.swift */, 239 | 896D89371FE8F88700801D73 /* ComparativeOperator.swift */, 240 | 898A988D1FE897BF00594C49 /* LogicalBinaryOperator.swift */, 241 | 8945BD711FF60DC1004AB1ED /* NumericBinaryOperator.swift */, 242 | 8989A71E1FF5CEE9002CDB1F /* Protocols */, 243 | ); 244 | path = Binary; 245 | sourceTree = ""; 246 | }; 247 | 8989A71E1FF5CEE9002CDB1F /* Protocols */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | 893133EE1FE5BF3700FE926E /* BinaryOperatorProtocol.swift */, 251 | 898A98871FE890EE00594C49 /* ComparativeOperatorProtocol.swift */, 252 | 898A98831FE8877E00594C49 /* EquatableOperatorProtocol.swift */, 253 | 898A98811FE8863100594C49 /* LogicalBinaryOperatorProtocol.swift */, 254 | 893133F01FE5BF3700FE926E /* NumericBinaryOperatorProtocol.swift */, 255 | 898A98851FE88BBA00594C49 /* ReferenceEquatableOperatorProtocol.swift */, 256 | ); 257 | path = Protocols; 258 | sourceTree = ""; 259 | }; 260 | 8989A71F1FF5CF04002CDB1F /* Unary */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 89CDE0801FF47E1000F53AB3 /* LogicalUnaryOperator.swift */, 264 | 89CDE0881FF58E8D00F53AB3 /* NumericUnaryOperator.swift */, 265 | 8989A7201FF5CF0B002CDB1F /* Protocols */, 266 | ); 267 | path = Unary; 268 | sourceTree = ""; 269 | }; 270 | 8989A7201FF5CF0B002CDB1F /* Protocols */ = { 271 | isa = PBXGroup; 272 | children = ( 273 | 89CDE0781FF37C0400F53AB3 /* UnaryOperatorProtocol.swift */, 274 | 89CDE07A1FF37C8000F53AB3 /* LogicalUnaryOperatorProtocol.swift */, 275 | 89CDE0841FF5880F00F53AB3 /* NumericUnaryOperatorProtocol.swift */, 276 | ); 277 | path = Protocols; 278 | sourceTree = ""; 279 | }; 280 | 89DAED561FF3445800BE78A0 /* Operators */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | 89CDE07E1FF46CD800F53AB3 /* OperatorNodeKind.swift */, 284 | 8989A71D1FF5CED1002CDB1F /* Binary */, 285 | 893133EB1FE5BF3700FE926E /* Protocols */, 286 | 8989A71F1FF5CF04002CDB1F /* Unary */, 287 | ); 288 | path = Operators; 289 | sourceTree = ""; 290 | }; 291 | 89F7D25E1FE5CA5700160A4B /* Visualization */ = { 292 | isa = PBXGroup; 293 | children = ( 294 | 8987ED381FEA126300963A1F /* BinaryTreeNodeView.swift */, 295 | 8968019E1FE71C5500BAAC2C /* CustomPlaygroundQuickLookableBinaryTreeProtocol.swift */, 296 | 89B323C01FEDB93400710AAD /* EvaluatableExpressionViewFactory.swift */, 297 | 891DE57B1FECBA2100DEB654 /* LineView.swift */, 298 | 8968019C1FE71C0300BAAC2C /* NodeVisualAttributes.swift */, 299 | 89F7D25F1FE5CD0700160A4B /* PositionedBinaryTree.swift */, 300 | 8983E9451FE9B51D005618C8 /* CustomVisualAttributes.swift */, 301 | 89F7D2661FE5D1F300160A4B /* Extensions */, 302 | ); 303 | path = Visualization; 304 | sourceTree = ""; 305 | }; 306 | 89F7D2661FE5D1F300160A4B /* Extensions */ = { 307 | isa = PBXGroup; 308 | children = ( 309 | 89F7D2671FE5D1F300160A4B /* UIColor.swift */, 310 | 89F7D2681FE5D1F300160A4B /* String.swift */, 311 | 89F7D2691FE5D1F300160A4B /* CGRect.swift */, 312 | 89F7D26A1FE5D1F300160A4B /* CGContext.swift */, 313 | ); 314 | path = Extensions; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXGroup section */ 318 | 319 | /* Begin PBXHeadersBuildPhase section */ 320 | 893133C41FE5BF2F00FE926E /* Headers */ = { 321 | isa = PBXHeadersBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | 893133D81FE5BF2F00FE926E /* Expression.h in Headers */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXHeadersBuildPhase section */ 329 | 330 | /* Begin PBXNativeTarget section */ 331 | 893133C61FE5BF2F00FE926E /* Expression */ = { 332 | isa = PBXNativeTarget; 333 | buildConfigurationList = 893133DB1FE5BF2F00FE926E /* Build configuration list for PBXNativeTarget "Expression" */; 334 | buildPhases = ( 335 | 893133C21FE5BF2F00FE926E /* Sources */, 336 | 893133C31FE5BF2F00FE926E /* Frameworks */, 337 | 893133C41FE5BF2F00FE926E /* Headers */, 338 | 893133C51FE5BF2F00FE926E /* Resources */, 339 | ); 340 | buildRules = ( 341 | ); 342 | dependencies = ( 343 | ); 344 | name = Expression; 345 | productName = Expression; 346 | productReference = 893133C71FE5BF2F00FE926E /* Expression.framework */; 347 | productType = "com.apple.product-type.framework"; 348 | }; 349 | /* End PBXNativeTarget section */ 350 | 351 | /* Begin PBXProject section */ 352 | 893133BE1FE5BF2F00FE926E /* Project object */ = { 353 | isa = PBXProject; 354 | attributes = { 355 | LastSwiftUpdateCheck = 0920; 356 | LastUpgradeCheck = 0920; 357 | ORGANIZATIONNAME = "Michael Pangburn"; 358 | TargetAttributes = { 359 | 893133C61FE5BF2F00FE926E = { 360 | CreatedOnToolsVersion = 9.2; 361 | ProvisioningStyle = Automatic; 362 | }; 363 | }; 364 | }; 365 | buildConfigurationList = 893133C11FE5BF2F00FE926E /* Build configuration list for PBXProject "Expression" */; 366 | compatibilityVersion = "Xcode 8.0"; 367 | developmentRegion = en; 368 | hasScannedForEncodings = 0; 369 | knownRegions = ( 370 | en, 371 | ); 372 | mainGroup = 893133BD1FE5BF2F00FE926E; 373 | productRefGroup = 893133C81FE5BF2F00FE926E /* Products */; 374 | projectDirPath = ""; 375 | projectRoot = ""; 376 | targets = ( 377 | 893133C61FE5BF2F00FE926E /* Expression */, 378 | ); 379 | }; 380 | /* End PBXProject section */ 381 | 382 | /* Begin PBXResourcesBuildPhase section */ 383 | 893133C51FE5BF2F00FE926E /* Resources */ = { 384 | isa = PBXResourcesBuildPhase; 385 | buildActionMask = 2147483647; 386 | files = ( 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | /* End PBXResourcesBuildPhase section */ 391 | 392 | /* Begin PBXSourcesBuildPhase section */ 393 | 893133C21FE5BF2F00FE926E /* Sources */ = { 394 | isa = PBXSourcesBuildPhase; 395 | buildActionMask = 2147483647; 396 | files = ( 397 | 898A98841FE8877E00594C49 /* EquatableOperatorProtocol.swift in Sources */, 398 | 896801A31FE726E300BAAC2C /* BinarySearchTreeProtocol.swift in Sources */, 399 | 898A988C1FE8977800594C49 /* LogicalExpression.swift in Sources */, 400 | 893134021FE5BF3700FE926E /* OperatorProtocol.swift in Sources */, 401 | 896D89381FE8F88700801D73 /* ComparativeOperator.swift in Sources */, 402 | 89F7D26D1FE5D1F300160A4B /* CGRect.swift in Sources */, 403 | 893BF6A61FEDDF7500C626F0 /* NeverEmptyTreeNode.swift in Sources */, 404 | 89B323C11FEDB93400710AAD /* EvaluatableExpressionViewFactory.swift in Sources */, 405 | 89CDE07D1FF3898700F53AB3 /* ExpressionNodeKind.swift in Sources */, 406 | 89F7D2601FE5CD0700160A4B /* PositionedBinaryTree.swift in Sources */, 407 | 89CDE07B1FF37C8000F53AB3 /* LogicalUnaryOperatorProtocol.swift in Sources */, 408 | 89F7D26B1FE5D1F300160A4B /* UIColor.swift in Sources */, 409 | 893133FC1FE5BF3700FE926E /* ArithmeticExpression.swift in Sources */, 410 | 8983E9461FE9B51D005618C8 /* CustomVisualAttributes.swift in Sources */, 411 | 8931340C1FE5BF3700FE926E /* TreeProtocol.swift in Sources */, 412 | 89CDE07F1FF46CD900F53AB3 /* OperatorNodeKind.swift in Sources */, 413 | 8945BD721FF60DC1004AB1ED /* NumericBinaryOperator.swift in Sources */, 414 | 898A98881FE890EE00594C49 /* ComparativeOperatorProtocol.swift in Sources */, 415 | 898A98861FE88BBA00594C49 /* ReferenceEquatableOperatorProtocol.swift in Sources */, 416 | 896801A51FE72C1A00BAAC2C /* RedBlackTree.swift in Sources */, 417 | 8971D0DB1FE6ECD900F14793 /* FloatingPointArithmeticExpression.swift in Sources */, 418 | 8968019B1FE717E300BAAC2C /* SingleTypeBinaryTreeProtocol.swift in Sources */, 419 | 8931340D1FE5BF3700FE926E /* Divisible.swift in Sources */, 420 | 898A98921FE89E4300594C49 /* LogicalExpressionProtocol.swift in Sources */, 421 | 893134031FE5BF3700FE926E /* BinaryOperatorProtocol.swift in Sources */, 422 | 89F5D5031FE7453E00812AC2 /* NeverEmptyTreeProtocol.swift in Sources */, 423 | 8983E9441FE9AA6A005618C8 /* BinarySearchTree.swift in Sources */, 424 | 89F7D26C1FE5D1F300160A4B /* String.swift in Sources */, 425 | 8931340E1FE5BF3700FE926E /* OperandProtocol.swift in Sources */, 426 | 898A98821FE8863100594C49 /* LogicalBinaryOperatorProtocol.swift in Sources */, 427 | 8968019D1FE71C0300BAAC2C /* NodeVisualAttributes.swift in Sources */, 428 | 891DE57C1FECBA2100DEB654 /* LineView.swift in Sources */, 429 | 893133FF1FE5BF3700FE926E /* BinaryOperatorPrecedence.swift in Sources */, 430 | 89CDE0791FF37C0400F53AB3 /* UnaryOperatorProtocol.swift in Sources */, 431 | 8931340A1FE5BF3700FE926E /* ArithmeticExpressionProtocol.swift in Sources */, 432 | 896801991FE7172200BAAC2C /* SingleTypeTreeProtocol.swift in Sources */, 433 | 8987ED391FEA126300963A1F /* BinaryTreeNodeView.swift in Sources */, 434 | 893133FA1FE5BF3700FE926E /* TreeNode.swift in Sources */, 435 | 893134051FE5BF3700FE926E /* NumericBinaryOperatorProtocol.swift in Sources */, 436 | 8968019F1FE71C5500BAAC2C /* CustomPlaygroundQuickLookableBinaryTreeProtocol.swift in Sources */, 437 | 89CDE0811FF47E1000F53AB3 /* LogicalUnaryOperator.swift in Sources */, 438 | 89F7D26E1FE5D1F300160A4B /* CGContext.swift in Sources */, 439 | 893134091FE5BF3700FE926E /* BinaryTreeProtocol.swift in Sources */, 440 | 893133FD1FE5BF3700FE926E /* Numeric.swift in Sources */, 441 | 893134001FE5BF3700FE926E /* BinaryOperatorAssociativity.swift in Sources */, 442 | 89CDE0891FF58E8D00F53AB3 /* NumericUnaryOperator.swift in Sources */, 443 | 898A988A1FE8956200594C49 /* EvaluatableExpressionProtocol.swift in Sources */, 444 | 898A988E1FE897BF00594C49 /* LogicalBinaryOperator.swift in Sources */, 445 | 893134081FE5BF3700FE926E /* Evaluatable.swift in Sources */, 446 | 89CDE0851FF5880F00F53AB3 /* NumericUnaryOperatorProtocol.swift in Sources */, 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | /* End PBXSourcesBuildPhase section */ 451 | 452 | /* Begin XCBuildConfiguration section */ 453 | 893133D91FE5BF2F00FE926E /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ALWAYS_SEARCH_USER_PATHS = NO; 457 | CLANG_ANALYZER_NONNULL = YES; 458 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 459 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 460 | CLANG_CXX_LIBRARY = "libc++"; 461 | CLANG_ENABLE_MODULES = YES; 462 | CLANG_ENABLE_OBJC_ARC = YES; 463 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 464 | CLANG_WARN_BOOL_CONVERSION = YES; 465 | CLANG_WARN_COMMA = YES; 466 | CLANG_WARN_CONSTANT_CONVERSION = YES; 467 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 468 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 469 | CLANG_WARN_EMPTY_BODY = YES; 470 | CLANG_WARN_ENUM_CONVERSION = YES; 471 | CLANG_WARN_INFINITE_RECURSION = YES; 472 | CLANG_WARN_INT_CONVERSION = YES; 473 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 475 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 476 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 477 | CLANG_WARN_STRICT_PROTOTYPES = YES; 478 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 479 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | CODE_SIGN_IDENTITY = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | CURRENT_PROJECT_VERSION = 1; 485 | DEBUG_INFORMATION_FORMAT = dwarf; 486 | ENABLE_STRICT_OBJC_MSGSEND = YES; 487 | ENABLE_TESTABILITY = YES; 488 | GCC_C_LANGUAGE_STANDARD = gnu11; 489 | GCC_DYNAMIC_NO_PIC = NO; 490 | GCC_NO_COMMON_BLOCKS = YES; 491 | GCC_OPTIMIZATION_LEVEL = 0; 492 | GCC_PREPROCESSOR_DEFINITIONS = ( 493 | "DEBUG=1", 494 | "$(inherited)", 495 | ); 496 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 497 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 498 | GCC_WARN_UNDECLARED_SELECTOR = YES; 499 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 500 | GCC_WARN_UNUSED_FUNCTION = YES; 501 | GCC_WARN_UNUSED_VARIABLE = YES; 502 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 503 | MTL_ENABLE_DEBUG_INFO = YES; 504 | ONLY_ACTIVE_ARCH = YES; 505 | SDKROOT = iphoneos; 506 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 507 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 508 | VERSIONING_SYSTEM = "apple-generic"; 509 | VERSION_INFO_PREFIX = ""; 510 | }; 511 | name = Debug; 512 | }; 513 | 893133DA1FE5BF2F00FE926E /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | ALWAYS_SEARCH_USER_PATHS = NO; 517 | CLANG_ANALYZER_NONNULL = YES; 518 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 519 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 520 | CLANG_CXX_LIBRARY = "libc++"; 521 | CLANG_ENABLE_MODULES = YES; 522 | CLANG_ENABLE_OBJC_ARC = YES; 523 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 524 | CLANG_WARN_BOOL_CONVERSION = YES; 525 | CLANG_WARN_COMMA = YES; 526 | CLANG_WARN_CONSTANT_CONVERSION = YES; 527 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 528 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 529 | CLANG_WARN_EMPTY_BODY = YES; 530 | CLANG_WARN_ENUM_CONVERSION = YES; 531 | CLANG_WARN_INFINITE_RECURSION = YES; 532 | CLANG_WARN_INT_CONVERSION = YES; 533 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 534 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 535 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 536 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 537 | CLANG_WARN_STRICT_PROTOTYPES = YES; 538 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 539 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 540 | CLANG_WARN_UNREACHABLE_CODE = YES; 541 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 542 | CODE_SIGN_IDENTITY = "iPhone Developer"; 543 | COPY_PHASE_STRIP = NO; 544 | CURRENT_PROJECT_VERSION = 1; 545 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 546 | ENABLE_NS_ASSERTIONS = NO; 547 | ENABLE_STRICT_OBJC_MSGSEND = YES; 548 | GCC_C_LANGUAGE_STANDARD = gnu11; 549 | GCC_NO_COMMON_BLOCKS = YES; 550 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 551 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 552 | GCC_WARN_UNDECLARED_SELECTOR = YES; 553 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 554 | GCC_WARN_UNUSED_FUNCTION = YES; 555 | GCC_WARN_UNUSED_VARIABLE = YES; 556 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 557 | MTL_ENABLE_DEBUG_INFO = NO; 558 | SDKROOT = iphoneos; 559 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 560 | VALIDATE_PRODUCT = YES; 561 | VERSIONING_SYSTEM = "apple-generic"; 562 | VERSION_INFO_PREFIX = ""; 563 | }; 564 | name = Release; 565 | }; 566 | 893133DC1FE5BF2F00FE926E /* Debug */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | CODE_SIGN_IDENTITY = ""; 570 | CODE_SIGN_STYLE = Automatic; 571 | DEFINES_MODULE = YES; 572 | DEVELOPMENT_TEAM = 5Q5Q8W9ATZ; 573 | DYLIB_COMPATIBILITY_VERSION = 1; 574 | DYLIB_CURRENT_VERSION = 1; 575 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 576 | INFOPLIST_FILE = Expression/Info.plist; 577 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 579 | PRODUCT_BUNDLE_IDENTIFIER = com.mpangburn.Expression; 580 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 581 | SKIP_INSTALL = YES; 582 | SWIFT_VERSION = 4.0; 583 | TARGETED_DEVICE_FAMILY = "1,2"; 584 | }; 585 | name = Debug; 586 | }; 587 | 893133DD1FE5BF2F00FE926E /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | CODE_SIGN_IDENTITY = ""; 591 | CODE_SIGN_STYLE = Automatic; 592 | DEFINES_MODULE = YES; 593 | DEVELOPMENT_TEAM = 5Q5Q8W9ATZ; 594 | DYLIB_COMPATIBILITY_VERSION = 1; 595 | DYLIB_CURRENT_VERSION = 1; 596 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 597 | INFOPLIST_FILE = Expression/Info.plist; 598 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 599 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 600 | PRODUCT_BUNDLE_IDENTIFIER = com.mpangburn.Expression; 601 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 602 | SKIP_INSTALL = YES; 603 | SWIFT_VERSION = 4.0; 604 | TARGETED_DEVICE_FAMILY = "1,2"; 605 | }; 606 | name = Release; 607 | }; 608 | /* End XCBuildConfiguration section */ 609 | 610 | /* Begin XCConfigurationList section */ 611 | 893133C11FE5BF2F00FE926E /* Build configuration list for PBXProject "Expression" */ = { 612 | isa = XCConfigurationList; 613 | buildConfigurations = ( 614 | 893133D91FE5BF2F00FE926E /* Debug */, 615 | 893133DA1FE5BF2F00FE926E /* Release */, 616 | ); 617 | defaultConfigurationIsVisible = 0; 618 | defaultConfigurationName = Release; 619 | }; 620 | 893133DB1FE5BF2F00FE926E /* Build configuration list for PBXNativeTarget "Expression" */ = { 621 | isa = XCConfigurationList; 622 | buildConfigurations = ( 623 | 893133DC1FE5BF2F00FE926E /* Debug */, 624 | 893133DD1FE5BF2F00FE926E /* Release */, 625 | ); 626 | defaultConfigurationIsVisible = 0; 627 | defaultConfigurationName = Release; 628 | }; 629 | /* End XCConfigurationList section */ 630 | }; 631 | rootObject = 893133BE1FE5BF2F00FE926E /* Project object */; 632 | } 633 | -------------------------------------------------------------------------------- /Expression.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Expression.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Expression.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Expression/Expression.h: -------------------------------------------------------------------------------- 1 | // 2 | // Expression.h 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Expression. 12 | FOUNDATION_EXPORT double ExpressionVersionNumber; 13 | 14 | //! Project version string for Expression. 15 | FOUNDATION_EXPORT const unsigned char ExpressionVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Expression/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Images/ArithmeticExpression Evaluation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpangburn/Expressions/626e44e3f736dc150ef6938693ee0d57e85a4678/Images/ArithmeticExpression Evaluation.gif -------------------------------------------------------------------------------- /Images/ArithmeticExpression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpangburn/Expressions/626e44e3f736dc150ef6938693ee0d57e85a4678/Images/ArithmeticExpression.png -------------------------------------------------------------------------------- /Images/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpangburn/Expressions/626e44e3f736dc150ef6938693ee0d57e85a4678/Images/Banner.png -------------------------------------------------------------------------------- /Images/LogicalExpression Evaluation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpangburn/Expressions/626e44e3f736dc150ef6938693ee0d57e85a4678/Images/LogicalExpression Evaluation.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michael Pangburn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Expressions 3 |

4 | 5 |

6 | 7 | 8 | 9 | Twitter: @pangburnout 10 | 11 |

12 | 13 | Arithmetic and logical expressions elegantly modeled and visualized using protocol-oriented binary trees with value semantics. 14 | 15 | ## Features 16 | - [x] Model arithmetic and logical expressions using binary trees. 17 | - [x] Write expressions in code in the same manner as they would be written for evaluation, i.e. making full use of literals and both unary and binary operators. 18 | - [x] Visualize expressions by rendering image representations, which can be easily seen using the Xcode Playground QuickLook feature. 19 | - [x] Animate the evaluation of expressions using `UIView`s, and observe these animations using the Xcode Playground Live View feature. 20 | - [x] Demonstrate the power of protocol-oriented programming by creating other simple tree structures, such as binary search trees, which gain QuickLook visualization for free. 21 | 22 | ## Contents 23 | The principal focus of this project is to demonstrate the structure and evaluation of arithmetic and logical expressions in an elegant, expressive way. Thanks to Swift's powerful `ExpressibleBy*Literal` protocols and operator overloading, we can write code like this: 24 | 25 | ```swift 26 | let expression: ArithmeticExpression = 2*(1+3)-8/4 27 | ``` 28 | 29 | and in doing so create the full tree representing this expression, which in turn can be visualized in an Xcode Playground via QuickLook: 30 | 31 | 32 | 33 | Furthermore, we can animate the evaluation of this expression by calling `animateEvaluation(of:)` in a Playground page. We can observe this animation in the Playground's Live View: 34 | 35 | 36 | 37 | In addition to arithmetic expressions, logical expressions can be similarly created, viewed, and animated. 38 | 39 | ```swift 40 | let expression: LogicalExpression = !(true || false) && false || !true 41 | ``` 42 | 43 | 44 | 45 | As a simple demonstration of the power of protocol-oriented programming, I've also implemented a couple of other tree structures, including a traditional binary search tree and a red-black tree, which can be visualized with QuickLook. 46 | 47 | ## Getting Started 48 | While a brief outline of the project's contents is provided in the section above, this is a Playground-based project and ultimately better demonstrated than explained: 49 | 50 | 1. Clone the project. 51 | 2. Open **Expression.xcworkspace**. 52 | 3. Build the project. 53 | 4. Within the Expression Playground, navigate to the ArithmeticExpression page to begin. 54 | 5. See the magic through Xcode Playground's QuickLook and Live View features. Each Playground Page demonstrates a type of expression or other tree-based structure. 55 | 56 | ## License 57 | Expressions is released under the MIT license. See [LICENSE](https://github.com/mpangburn/Expressions/blob/master/LICENSE) for details. 58 | 59 | If you find this project to be a useful tool in learning or teaching expressions or binary trees, please reach out to me [on Twitter](https://twitter.com/pangburnout)--I'd love to hear from you. 60 | 61 | -------------------------------------------------------------------------------- /Sources/Expressions/ArithmeticExpression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArithmeticExpression.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// An arithmetic expression modeled as a binary tree. 10 | /// For floating point operands, see FloatingPointArithmeticExpression. 11 | public enum ArithmeticExpression: ArithmeticExpressionProtocol { 12 | public typealias Operand = T 13 | public typealias UnaryOperator = NumericUnaryOperator 14 | public typealias BinaryOperator = NumericBinaryOperator 15 | 16 | case operand(Operand) 17 | indirect case unaryExpression(operator: UnaryOperator, operand: ArithmeticExpression) 18 | indirect case binaryExpression(left: ArithmeticExpression, operator: BinaryOperator, right: ArithmeticExpression) 19 | } 20 | 21 | // MARK: - Required conformance to expression protocols 22 | 23 | extension ArithmeticExpression { 24 | public var expressionNodeKind: ExpressionNodeKind { 25 | switch self { 26 | case let .operand(operand): 27 | return .operand(operand) 28 | case let .unaryExpression(`operator`, _): 29 | return .unaryOperator(`operator`) 30 | case let .binaryExpression(_, `operator`, _): 31 | return .binaryOperator(`operator`) 32 | } 33 | } 34 | 35 | public static func makeExpression(operand: Operand) -> ArithmeticExpression { 36 | return .operand(operand) 37 | } 38 | 39 | public static func makeExpression(unaryOperator: UnaryOperator, expression: ArithmeticExpression) -> ArithmeticExpression { 40 | return .unaryExpression(operator: unaryOperator, operand: expression) 41 | } 42 | 43 | public static func makeExpression(left: ArithmeticExpression, binaryOperator: BinaryOperator, right: ArithmeticExpression) -> ArithmeticExpression { 44 | return .binaryExpression(left: left, operator: binaryOperator, right: right) 45 | } 46 | } 47 | 48 | // MARK: - Required conformance to tree protocols 49 | 50 | extension ArithmeticExpression { 51 | public typealias Leaf = Operand 52 | public typealias Node = OperatorNodeKind 53 | 54 | public var left: ArithmeticExpression? { 55 | switch self { 56 | case .operand: 57 | return nil 58 | case let .unaryExpression(_, operand): 59 | return operand 60 | case let .binaryExpression(left, _, _): 61 | return left 62 | } 63 | } 64 | 65 | public var right: ArithmeticExpression? { 66 | guard case let .binaryExpression(_, _, right) = self else { return nil } 67 | return right 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Expressions/ExpressionNodeKind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpressionNodeKind.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/26/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// The kind of an expression node--either an operand, a unary operator, or a binary operator. 10 | public enum ExpressionNodeKind where UnaryOperator.Operand == BinaryOperator.Operand { 11 | public typealias Operand = UnaryOperator.Operand 12 | 13 | case operand(Operand) 14 | case unaryOperator(UnaryOperator) 15 | case binaryOperator(BinaryOperator) 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Expressions/FloatingPointArithmeticExpression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatingPointArithmeticExpression.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | // Once we have conditional conformance, FloatingPointArithmeticExpression need no longer exist! 10 | /* extension ArithmeticExpression: ExpressibleByFloatLiteral where Operand: FloatingPoint & _ExpressibleByBuiltinFloatLiteral { } */ 11 | 12 | /// An arithmetic expression of floating point numbers modeled as a binary tree. 13 | /// For integer operands, see ArithmeticExpression. 14 | public enum FloatingPointArithmeticExpression: ArithmeticExpressionProtocol, ExpressibleByFloatLiteral { 15 | public typealias Operand = T 16 | public typealias UnaryOperator = NumericUnaryOperator 17 | public typealias BinaryOperator = NumericBinaryOperator 18 | 19 | case operand(Operand) 20 | indirect case unaryExpression(operator: UnaryOperator, operand: FloatingPointArithmeticExpression) 21 | indirect case binaryExpression(left: FloatingPointArithmeticExpression, operator: BinaryOperator, right: FloatingPointArithmeticExpression) 22 | } 23 | 24 | // MARK: Required conformance to expression protocols 25 | 26 | extension FloatingPointArithmeticExpression { 27 | public var expressionNodeKind: ExpressionNodeKind { 28 | switch self { 29 | case let .operand(operand): 30 | return .operand(operand) 31 | case let .unaryExpression(`operator`, _): 32 | return .unaryOperator(`operator`) 33 | case let .binaryExpression(_, `operator`, _): 34 | return .binaryOperator(`operator`) 35 | } 36 | } 37 | 38 | public static func makeExpression(operand: Operand) -> FloatingPointArithmeticExpression { 39 | return .operand(operand) 40 | } 41 | 42 | public static func makeExpression(unaryOperator: UnaryOperator, expression: FloatingPointArithmeticExpression) -> FloatingPointArithmeticExpression { 43 | return .unaryExpression(operator: unaryOperator, operand: expression) 44 | } 45 | 46 | public static func makeExpression(left: FloatingPointArithmeticExpression, binaryOperator: BinaryOperator, right: FloatingPointArithmeticExpression) -> FloatingPointArithmeticExpression { 47 | return .binaryExpression(left: left, operator: binaryOperator, right: right) 48 | } 49 | } 50 | 51 | // MARK: - Required conformance to tree protocols 52 | 53 | extension FloatingPointArithmeticExpression { 54 | public typealias Leaf = Operand 55 | public typealias Node = OperatorNodeKind 56 | 57 | public var left: FloatingPointArithmeticExpression? { 58 | switch self { 59 | case .operand: 60 | return nil 61 | case let .unaryExpression(_, operand): 62 | return operand 63 | case let .binaryExpression(left, _, _): 64 | return left 65 | } 66 | } 67 | 68 | public var right: FloatingPointArithmeticExpression? { 69 | guard case let .binaryExpression(_, _, right) = self else { return nil } 70 | return right 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Expressions/LogicalExpression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalExpression.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// An expression of boolean logic. 10 | //public typealias LogicalExpression = LogicalExpression 11 | 12 | /// An expression of boolean logic modeled as a binary tree. 13 | /// Use the typealias LogicalExpression rather than working with this type directly. 14 | public enum LogicalExpression: LogicalExpressionProtocol { 15 | public typealias Operand = Bool 16 | public typealias UnaryOperator = LogicalUnaryOperator 17 | public typealias BinaryOperator = LogicalBinaryOperator 18 | 19 | case operand(Operand) 20 | indirect case unaryExpression(operator: UnaryOperator, operand: LogicalExpression) 21 | indirect case binaryExpression(left: LogicalExpression, operator: BinaryOperator, right: LogicalExpression) 22 | } 23 | 24 | // MARK: - Required conformance to expression protocols 25 | 26 | extension LogicalExpression { 27 | public var expressionNodeKind: ExpressionNodeKind { 28 | switch self { 29 | case let .operand(operand): 30 | return .operand(operand) 31 | case let .unaryExpression(`operator`, _): 32 | return .unaryOperator(`operator`) 33 | case let .binaryExpression(_, `operator`, _): 34 | return .binaryOperator(`operator`) 35 | } 36 | } 37 | 38 | public static func makeExpression(operand: Operand) -> LogicalExpression { 39 | return .operand(operand) 40 | } 41 | 42 | public static func makeExpression(unaryOperator: UnaryOperator, expression: LogicalExpression) -> LogicalExpression { 43 | return .unaryExpression(operator: unaryOperator, operand: expression) 44 | } 45 | 46 | public static func makeExpression(left: LogicalExpression, binaryOperator: BinaryOperator, right: LogicalExpression) -> LogicalExpression { 47 | return .binaryExpression(left: left, operator: binaryOperator, right: right) 48 | } 49 | } 50 | 51 | // MARK: - Required conformance to tree protocols 52 | 53 | extension LogicalExpression { 54 | public typealias Leaf = Operand 55 | public typealias Node = OperatorNodeKind 56 | 57 | public var left: LogicalExpression? { 58 | switch self { 59 | case .operand: 60 | return nil 61 | case let .unaryExpression(_, operand): 62 | return operand 63 | case let .binaryExpression(left, _, _): 64 | return left 65 | } 66 | } 67 | 68 | public var right: LogicalExpression? { 69 | guard case let .binaryExpression(_, _, right) = self else { return nil } 70 | return right 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Expressions/Protocols/ArithmeticExpressionProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArithmeticExpressionProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A type representing an arithmetic expression. 10 | public protocol ArithmeticExpressionProtocol: EvaluatableExpressionProtocol, Numeric where UnaryOperator: NumericUnaryOperatorProtocol, BinaryOperator: NumericBinaryOperatorProtocol { } 11 | 12 | // MARK: - Default implementations 13 | 14 | extension ArithmeticExpressionProtocol { 15 | public init(integerLiteral operand: Operand) { 16 | self = .makeExpression(operand: operand) 17 | } 18 | } 19 | 20 | extension ArithmeticExpressionProtocol where Self: ExpressibleByFloatLiteral { 21 | public init(floatLiteral operand: Operand) { 22 | self = .makeExpression(operand: operand) 23 | } 24 | } 25 | 26 | extension ArithmeticExpressionProtocol { 27 | public init?(exactly source: T) where T: BinaryInteger { 28 | guard let operand = Operand(exactly: source) else { return nil } 29 | self = .makeExpression(operand: operand) 30 | } 31 | 32 | /// Evaluates the expression by applying its operators to its operands. 33 | /// - Returns: The value of the expression. 34 | public var magnitude: Operand { 35 | return evaluate() 36 | } 37 | } 38 | 39 | // MARK: - Operators 40 | 41 | extension ArithmeticExpressionProtocol { 42 | public static prefix func + (expression: Self) -> Self { 43 | return makeExpression(unaryOperator: .unaryPlus, expression: expression) 44 | } 45 | 46 | public static func + (lhs: Self, rhs: Self) -> Self { 47 | return makeExpression(left: lhs, binaryOperator: .add, right: rhs) 48 | } 49 | 50 | public static func - (lhs: Self, rhs: Self) -> Self { 51 | return makeExpression(left: lhs, binaryOperator: .subtract, right: rhs) 52 | } 53 | 54 | public static func * (lhs: Self, rhs: Self) -> Self { 55 | return makeExpression(left: lhs, binaryOperator: .multiply, right: rhs) 56 | } 57 | } 58 | 59 | extension ArithmeticExpressionProtocol /*: Divisible */ where BinaryOperator.Operand: Divisible { 60 | public static func / (lhs: Self, rhs: Self) -> Self { 61 | return makeExpression(left: lhs, binaryOperator: .divide, right: rhs) 62 | } 63 | 64 | public static func /= (lhs: inout Self, rhs: Self) { 65 | lhs = lhs / rhs 66 | } 67 | } 68 | 69 | extension ArithmeticExpressionProtocol where UnaryOperator.Operand: SignedNumeric { 70 | public static prefix func - (expression: Self) -> Self { 71 | return makeExpression(unaryOperator: .unaryMinus, expression: expression) 72 | } 73 | } 74 | 75 | extension ArithmeticExpressionProtocol where UnaryOperator.Operand: BinaryInteger { 76 | public static prefix func ~ (expression: Self) -> Self { 77 | return makeExpression(unaryOperator: .bitwiseNOT, expression: expression) 78 | } 79 | } 80 | 81 | extension ArithmeticExpressionProtocol where BinaryOperator.Operand: BinaryInteger { 82 | public static func % (lhs: Self, rhs: Self) -> Self { 83 | return makeExpression(left: lhs, binaryOperator: .remainder, right: rhs) 84 | } 85 | 86 | public static func %= (lhs: inout Self, rhs: Self) { 87 | lhs = lhs % rhs 88 | } 89 | 90 | public static func & (lhs: Self, rhs: Self) -> Self { 91 | return makeExpression(left: lhs, binaryOperator: .bitwiseAND, right: rhs) 92 | } 93 | 94 | public static func &= (lhs: inout Self, rhs: Self) { 95 | lhs = lhs & rhs 96 | } 97 | 98 | public static func | (lhs: Self, rhs: Self) -> Self { 99 | return makeExpression(left: lhs, binaryOperator: .bitwiseOR, right: rhs) 100 | } 101 | 102 | public static func |= (lhs: inout Self, rhs: Self) { 103 | lhs = lhs | rhs 104 | } 105 | 106 | public static func ^ (lhs: Self, rhs: Self) -> Self { 107 | return makeExpression(left: lhs, binaryOperator: .bitwiseXOR, right: rhs) 108 | } 109 | 110 | public static func ^= (lhs: inout Self, rhs: Self) { 111 | lhs = lhs ^ rhs 112 | } 113 | 114 | public static func << (lhs: Self, rhs: Self) -> Self { 115 | return makeExpression(left: lhs, binaryOperator: .bitwiseLeftShift, right: rhs) 116 | } 117 | 118 | public static func <<= (lhs: inout Self, rhs: Self) { 119 | lhs = lhs << rhs 120 | } 121 | 122 | public static func >> (lhs: Self, rhs: Self) -> Self { 123 | return makeExpression(left: lhs, binaryOperator: .bitwiseRightShift, right: rhs) 124 | } 125 | 126 | public static func >>= (lhs: inout Self, rhs: Self) { 127 | lhs = lhs >> rhs 128 | } 129 | } 130 | 131 | extension ArithmeticExpressionProtocol where BinaryOperator.Operand: FixedWidthInteger { 132 | public static func &+ (lhs: Self, rhs: Self) -> Self { 133 | return makeExpression(left: lhs, binaryOperator: .addIgnoringOverflow, right: rhs) 134 | } 135 | 136 | public static func &- (lhs: Self, rhs: Self) -> Self { 137 | return makeExpression(left: lhs, binaryOperator: .subtractIgnoringOverflow, right: rhs) 138 | } 139 | 140 | public static func &* (lhs: Self, rhs: Self) -> Self { 141 | return makeExpression(left: lhs, binaryOperator: .multiplyIgnoringOverflow, right: rhs) 142 | } 143 | 144 | public static func &<< (lhs: Self, rhs: Self) -> Self { 145 | return makeExpression(left: lhs, binaryOperator: .bitwiseLeftMaskingShift, right: rhs) 146 | } 147 | 148 | public static func &>> (lhs: Self, rhs: Self) -> Self { 149 | return makeExpression(left: lhs, binaryOperator: .bitwiseRightMaskingShift, right: rhs) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/Expressions/Protocols/Evaluatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Evaluatable.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A type that can be evaluated. 10 | public protocol Evaluatable { 11 | /// The result of the evaluation. 12 | associatedtype Result 13 | 14 | /// Evaluates the instance and returns the result. 15 | func evaluate() -> Result 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Expressions/Protocols/EvaluatableExpressionProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EvaluatableExpressionProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A type representing an evaluatable expression, e.g. arithmetic or logical. 10 | public protocol EvaluatableExpressionProtocol: CustomPlaygroundQuickLookableBinaryTreeProtocol, NeverEmptyTreeProtocol, Evaluatable, CustomStringConvertible where Result == Leaf { 11 | 12 | /// The type of the operands used in the expression. 13 | typealias Operand = Leaf 14 | 15 | /// The type of the unary operators used in the expression. 16 | associatedtype UnaryOperator: UnaryOperatorProtocol where UnaryOperator.Operand == Operand, UnaryOperator.Result == Operand 17 | 18 | /// The type of the binary operators used in the expression. 19 | associatedtype BinaryOperator: BinaryOperatorProtocol where BinaryOperator.Operand == Operand, BinaryOperator.Result == Operand 20 | 21 | /// Returns an expression consisting of the single operand. 22 | /// - Parameter operand: The operand used to create the expression. 23 | /// - Returns: An expression consisting of the single operand. 24 | static func makeExpression(operand: Operand) -> Self 25 | 26 | /// Returns an expression consisting of a unary operator applied to an expression. 27 | /// - Parameters: 28 | /// - unaryOperator: The operator to apply to the expression. 29 | /// - expression: The expression to which to apply the operator. 30 | /// - Returns: An expression consisting of a unary operator applied to an expression. 31 | static func makeExpression(unaryOperator: UnaryOperator, expression: Self) -> Self 32 | 33 | /// Returns an expression consisting of the left expression and the right expression combined by the operator. 34 | /// - Parameters: 35 | /// - left: The left side of the expression. 36 | /// - binaryOperator: The operator combining the two sides of the expression. 37 | /// - right: The right side of the expression. 38 | /// - Returns: An expression consisting of the left expression and the right expression combined by the operator. 39 | static func makeExpression(left: Self, binaryOperator: BinaryOperator, right: Self) -> Self 40 | 41 | var expressionNodeKind: ExpressionNodeKind { get } 42 | 43 | /// The text and color of an evaluated node, for use in displaying in the animated evaluation of the expression. 44 | /// Defaults to the `visualAttributes.text` and `visualAttributes.color` properties of an operand created 45 | /// from the evaluation of the expression. 46 | var evaluatedNodeAttributes: (text: String, color: UIColor) { get } 47 | 48 | /// Determines whether operands should be spaced from their operators in the expression's description. 49 | /// Defaults to false. 50 | var shouldSpaceDescription: Bool { get } 51 | } 52 | 53 | // MARK: - Default implementations 54 | 55 | extension EvaluatableExpressionProtocol { 56 | public typealias Node = OperatorNodeKind 57 | 58 | public var neverEmptyNodeKind: NeverEmptyTreeNode { 59 | switch expressionNodeKind { 60 | case let .operand(operand): 61 | return .leaf(operand) 62 | case let .unaryOperator(`operator`): 63 | return .node(.unary(`operator`)) 64 | case let .binaryOperator(`operator`): 65 | return .node(.binary(`operator`)) 66 | } 67 | } 68 | 69 | public func evaluate() -> Operand { 70 | switch expressionNodeKind { 71 | case let .operand(operand): 72 | return operand 73 | case let .unaryOperator(`operator`): 74 | switch (left, right) { 75 | case (.none, .none), (.some, .some): 76 | fatalError("A unary operator must have exactly one operand.") 77 | case let (.some(expression), .none): 78 | return `operator`.apply(expression.evaluate()) 79 | case let (.none, .some(expression)): 80 | return `operator`.apply(expression.evaluate()) 81 | } 82 | case let .binaryOperator(`operator`): 83 | guard let left = left, let right = right else { fatalError("A binary operator must have two operands.") } 84 | return `operator`.apply(left.evaluate(), right.evaluate()) 85 | } 86 | } 87 | 88 | public var evaluatedNodeAttributes: (text: String, color: UIColor) { 89 | let result = evaluate() 90 | guard let attributes = Self.makeExpression(operand: result).visualAttributes else { fatalError("A single operand must have visual attributes.") } 91 | return (attributes.text, attributes.color) 92 | } 93 | 94 | public var shouldSpaceDescription: Bool { 95 | return false 96 | } 97 | } 98 | 99 | extension EvaluatableExpressionProtocol where Self: Equatable { 100 | /// Tests the expressions for effective equality. 101 | /// Does not compare the underlying tree structures for equality. 102 | public static func == (lhs: Self, rhs: Self) -> Bool { 103 | return lhs.description == rhs.description 104 | } 105 | } 106 | 107 | // TODO: Consider associativity 108 | extension EvaluatableExpressionProtocol { 109 | public var description: String { 110 | switch expressionNodeKind { 111 | case let .operand(operand): 112 | return String(describing: operand) 113 | case let .unaryOperator(`operator`): 114 | let subexpression: Self 115 | switch (left, right) { 116 | case (.none, .none), (.some, .some): 117 | fatalError("A unary operator must have exactly one operand.") 118 | case let (.some(expression), .none): 119 | subexpression = expression 120 | case let (.none, .some(expression)): 121 | subexpression = expression 122 | } 123 | 124 | if case .binaryOperator = subexpression.expressionNodeKind { 125 | return "\(`operator`)(\(subexpression))" 126 | } else { 127 | return "\(`operator`)\(subexpression)" 128 | } 129 | case let .binaryOperator(`operator`): 130 | guard let left = left, let right = right else { fatalError("A binary operator must have two operands.") } 131 | 132 | let leftString: String 133 | if case let .binaryOperator(leftOperator) = left.expressionNodeKind, leftOperator.precedence < `operator`.precedence { 134 | leftString = "(\(left.description))" 135 | } else { 136 | leftString = left.description 137 | } 138 | 139 | let rightString: String 140 | if case let .binaryOperator(rightOperator) = right.expressionNodeKind, rightOperator.precedence < `operator`.precedence { 141 | rightString = "(\(right.description))" 142 | } else { 143 | rightString = right.description 144 | } 145 | 146 | if shouldSpaceDescription { 147 | return "\(leftString) \(`operator`) \(rightString)" 148 | } else { 149 | return "\(leftString)\(`operator`)\(rightString)" 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Sources/Expressions/Protocols/LogicalExpressionProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalExpressionProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A type representing a logical expression. 10 | public protocol LogicalExpressionProtocol: EvaluatableExpressionProtocol, ExpressibleByBooleanLiteral where UnaryOperator: LogicalUnaryOperatorProtocol, BinaryOperator: LogicalBinaryOperatorProtocol { } 11 | 12 | // MARK: - Default implementations 13 | 14 | extension LogicalExpressionProtocol { 15 | public init(booleanLiteral boolean: Bool) { 16 | self = .makeExpression(operand: boolean) 17 | } 18 | } 19 | 20 | extension LogicalExpressionProtocol { 21 | public var shouldSpaceDescription: Bool { 22 | return true 23 | } 24 | } 25 | 26 | // MARK: - Operators 27 | 28 | extension LogicalExpressionProtocol { 29 | public static prefix func ! (expression: Self) -> Self { 30 | return makeExpression(unaryOperator: .logicalNOT, expression: expression) 31 | } 32 | 33 | public static func && (lhs: Self, rhs: Self) -> Self { 34 | return makeExpression(left: lhs, binaryOperator: .logicalAND, right: rhs) 35 | } 36 | 37 | public static func || (lhs: Self, rhs: Self) -> Self { 38 | return makeExpression(left: lhs, binaryOperator: .logicalOR, right: rhs) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Extensions/Divisible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Divisible.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/14/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// Declares the division operator and its mutating counterpart. 10 | public protocol Divisible: Numeric { 11 | static func / (lhs: Self, rhs: Self) -> Self 12 | static func /= (lhs: inout Self, rhs: Self) 13 | } 14 | 15 | extension Divisible { 16 | public static func /= (lhs: inout Self, rhs: Self) { 17 | lhs = lhs / rhs 18 | } 19 | } 20 | 21 | extension Int: Divisible { } 22 | extension Int8: Divisible { } 23 | extension Int16: Divisible { } 24 | extension Int32: Divisible { } 25 | extension Int64: Divisible { } 26 | 27 | extension UInt: Divisible { } 28 | extension UInt8: Divisible { } 29 | extension UInt16: Divisible { } 30 | extension UInt32: Divisible { } 31 | extension UInt64: Divisible { } 32 | 33 | extension Double: Divisible { } 34 | extension Float: Divisible { } 35 | extension Float80: Divisible { } 36 | extension CGFloat: Divisible { } 37 | -------------------------------------------------------------------------------- /Sources/Extensions/Numeric.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Numeric.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | extension Numeric { 10 | // Would expect these to have default implementations 11 | public static func += (lhs: inout Self, rhs: Self) { 12 | lhs = lhs + rhs 13 | } 14 | 15 | public static func -= (lhs: inout Self, rhs: Self) { 16 | lhs = lhs - rhs 17 | } 18 | 19 | public static func *= (lhs: inout Self, rhs: Self) { 20 | lhs = lhs * rhs 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/BinaryOperatorAssociativity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryOperatorAssociativity.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// Contains the possible values for a binary operator's associativity, 10 | /// which determines how operators of the same precedence are grouped in the absence of parentheses. 11 | public enum BinaryOperatorAssociativity { 12 | case left 13 | case right 14 | case none 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/BinaryOperatorPrecedence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryOperatorPrecedence.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// Contains the possible values for a binary operator's precedence, 10 | /// which is used to determine the order of operations when combined with other operators. 11 | /// c.f. https://developer.apple.com/documentation/swift/operator_declarations 12 | public enum BinaryOperatorPrecedence: Int, Comparable { 13 | case assignment 14 | case ternary 15 | case logicalDisjunction 16 | case logicalConjunction 17 | case comparison 18 | case nilCoalescing 19 | case casting 20 | case rangeFormation 21 | case addition 22 | case multiplication 23 | case bitwiseShift 24 | 25 | public static func < (lhs: BinaryOperatorPrecedence, rhs: BinaryOperatorPrecedence) -> Bool { 26 | return lhs.rawValue < rhs.rawValue 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/ComparativeOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComparativeOperator.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// An operator for comparing Comparable types. 10 | public struct ComparativeOperator: ComparativeOperatorProtocol { 11 | public typealias Operand = T 12 | public typealias Result = Bool 13 | 14 | public let identifier: String 15 | public let apply: (Operand, Operand) -> Result 16 | public let precedence: BinaryOperatorPrecedence 17 | public let associativity: BinaryOperatorAssociativity 18 | public let isCommutative: Bool 19 | 20 | public init(identifier: String, apply: @escaping (Operand, Operand) -> Result, precedence: BinaryOperatorPrecedence, associativity: BinaryOperatorAssociativity, isCommutative: Bool) { 21 | self.identifier = identifier 22 | self.apply = apply 23 | self.precedence = precedence 24 | self.associativity = associativity 25 | self.isCommutative = isCommutative 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/LogicalBinaryOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalBinaryOperator.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator for performing boolean logic. 10 | public struct LogicalBinaryOperator: LogicalBinaryOperatorProtocol { 11 | public typealias Operand = Bool 12 | public typealias Result = Bool 13 | 14 | public let identifier: String 15 | public let apply: (Operand, Operand) -> Result 16 | public let precedence: BinaryOperatorPrecedence 17 | public let associativity: BinaryOperatorAssociativity 18 | public let isCommutative: Bool 19 | 20 | public init(identifier: String, apply: @escaping (Operand, Operand) -> Result, precedence: BinaryOperatorPrecedence, associativity: BinaryOperatorAssociativity, isCommutative: Bool) { 21 | self.identifier = identifier 22 | self.apply = apply 23 | self.precedence = precedence 24 | self.associativity = associativity 25 | self.isCommutative = isCommutative 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/NumericBinaryOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericBinaryOperator.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/28/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator for performing operations on Numeric types. 10 | public struct NumericBinaryOperator: NumericBinaryOperatorProtocol { 11 | public typealias Operand = T 12 | public typealias Result = T 13 | 14 | public let identifier: String 15 | public let apply: (Operand, Operand) -> Result 16 | public let precedence: BinaryOperatorPrecedence 17 | public let associativity: BinaryOperatorAssociativity 18 | public let isCommutative: Bool 19 | 20 | public init(identifier: String, apply: @escaping (Operand, Operand) -> Result, precedence: BinaryOperatorPrecedence, associativity: BinaryOperatorAssociativity, isCommutative: Bool) { 21 | self.identifier = identifier 22 | self.apply = apply 23 | self.precedence = precedence 24 | self.associativity = associativity 25 | self.isCommutative = isCommutative 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/BinaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// An operator that performs an operation on exactly two operands. 10 | public protocol BinaryOperatorProtocol: OperatorProtocol { 11 | init(identifier: String, apply: @escaping (Operand, Operand) -> Result, precedence: BinaryOperatorPrecedence, 12 | associativity: BinaryOperatorAssociativity, isCommutative: Bool) 13 | 14 | /// The function to apply to two operands to produce the result. 15 | var apply: (Operand, Operand) -> Result { get } 16 | 17 | /// The operator's precedence, which is used to determine the order of operations when combined with other operators. 18 | var precedence: BinaryOperatorPrecedence { get } 19 | 20 | /// A property that determines how operators of the same precedence are grouped in the absence of parentheses. 21 | var associativity: BinaryOperatorAssociativity { get } 22 | 23 | /// A boolean value representing whether the operator is commutative, 24 | /// i.e. whether the order of the operands affects the result of the operation. 25 | var isCommutative: Bool { get } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/ComparativeOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComparativeOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator that operates on comparable types. 10 | public protocol ComparativeOperatorProtocol: EquatableOperatorProtocol where Operand: Comparable { } 11 | 12 | // MARK: - Operators 13 | 14 | extension ComparativeOperatorProtocol { 15 | public static var lessThan: Self { return .init(identifier: "<", apply: <, precedence: .comparison, associativity: .none, isCommutative: false) } 16 | public static var lessThanOrEqual: Self { return .init(identifier: "<=", apply: <=, precedence: .comparison, associativity: .none, isCommutative: false) } 17 | public static var greaterThan: Self { return .init(identifier: ">", apply: >, precedence: .comparison, associativity: .none, isCommutative: false) } 18 | public static var greaterThanOrEqual: Self { return .init(identifier: ">=", apply: >=, precedence: .comparison, associativity: .none, isCommutative: false) } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/EquatableOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EquatableOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator that operates on equatable types. 10 | public protocol EquatableOperatorProtocol: BinaryOperatorProtocol where Operand: Equatable, Result == Bool { } 11 | 12 | // MARK: - Operators 13 | 14 | extension EquatableOperatorProtocol { 15 | public static var equal: Self { return .init(identifier: "==", apply: ==, precedence: .comparison, associativity: .none, isCommutative: true) } 16 | public static var notEqual: Self { return .init(identifier: "!=", apply: !=, precedence: .comparison, associativity: .none, isCommutative: true) } 17 | public static var valueEqual: Self { return .init(identifier: "~=", apply: ~=, precedence: .comparison, associativity: .none, isCommutative: true) } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/LogicalBinaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalBinaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | 10 | /// A binary operator that applies boolean logic. 11 | public protocol LogicalBinaryOperatorProtocol: BinaryOperatorProtocol where Operand == Bool, Result == Bool { } 12 | 13 | // MARK: - Operators 14 | 15 | extension LogicalBinaryOperatorProtocol { 16 | // Since the type of && and || as operators in Swift is actually `(Bool, @autoclosure () throws -> Bool) throws -> Bool` 17 | // (for short-circuiting purposes), we must wrap these operators in closures. 18 | public static var logicalAND: Self { return .init(identifier: "&&", apply: { $0 && $1 }, precedence: .logicalConjunction, associativity: .left, isCommutative: true) } 19 | public static var logicalOR: Self { return .init(identifier: "||", apply: { $0 || $1 }, precedence: .logicalDisjunction, associativity: .left, isCommutative: true) } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/NumericBinaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericBinaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator that operates on numeric types. 10 | public protocol NumericBinaryOperatorProtocol: BinaryOperatorProtocol where Operand: Numeric & _ExpressibleByBuiltinIntegerLiteral, Result == Operand { } 11 | 12 | // MARK: - Operators 13 | 14 | extension NumericBinaryOperatorProtocol { 15 | public static var add: Self { return .init(identifier: "+", apply: +, precedence: .addition, associativity: .left, isCommutative: true) } 16 | public static var subtract: Self { return .init(identifier: "-", apply: -, precedence: .addition, associativity: .left, isCommutative: false) } 17 | public static var multiply: Self { return .init(identifier: "*", apply: *, precedence: .multiplication, associativity: .left, isCommutative: true) } 18 | } 19 | 20 | extension NumericBinaryOperatorProtocol where Operand: Divisible { 21 | public static var divide: Self { return .init(identifier: "/", apply: /, precedence: .multiplication, associativity: .left, isCommutative: false) } 22 | } 23 | 24 | extension NumericBinaryOperatorProtocol where Operand: BinaryInteger { 25 | public static var remainder: Self { return .init(identifier: "%", apply: %, precedence: .multiplication, associativity: .left, isCommutative: false) } 26 | public static var bitwiseAND: Self { return .init(identifier: "&", apply: &, precedence: .multiplication, associativity: .left, isCommutative: true) } 27 | public static var bitwiseOR: Self { return .init(identifier: "|", apply: |, precedence: .addition, associativity: .left, isCommutative: true) } 28 | public static var bitwiseXOR: Self { return .init(identifier: "^", apply: ^, precedence: .addition, associativity: .left, isCommutative: true) } 29 | public static var bitwiseLeftShift: Self { return .init(identifier: "<<", apply: <<, precedence: .bitwiseShift, associativity: .none, isCommutative: false) } 30 | public static var bitwiseRightShift: Self { return .init(identifier: ">>", apply: >>, precedence: .bitwiseShift, associativity: .none, isCommutative: false) } 31 | } 32 | 33 | extension NumericBinaryOperatorProtocol where Operand: FixedWidthInteger { 34 | public static var addIgnoringOverflow: Self { return .init(identifier: "&+", apply: &+, precedence: .addition, associativity: .left, isCommutative: true) } 35 | public static var subtractIgnoringOverflow: Self { return .init(identifier: "&-", apply: &-, precedence: .addition, associativity: .left, isCommutative: true) } 36 | public static var multiplyIgnoringOverflow: Self { return .init(identifier: "&*", apply: &*, precedence: .multiplication, associativity: .left, isCommutative: true) } 37 | public static var bitwiseLeftMaskingShift: Self { return .init(identifier: "&<<", apply: &<<, precedence: .bitwiseShift, associativity: .none, isCommutative: false) } 38 | public static var bitwiseRightMaskingShift: Self { return .init(identifier: "&>>", apply: &>>, precedence: .bitwiseShift, associativity: .none, isCommutative: false) } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Operators/Binary/Protocols/ReferenceEquatableOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectReferenceBinaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/18/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary operator that operates on reference equatable types. 10 | public protocol ReferenceEquatableOperatorProtocol: BinaryOperatorProtocol where Operand: AnyObject, Result == Bool { } 11 | 12 | // MARK: - Operators 13 | 14 | extension ReferenceEquatableOperatorProtocol { 15 | public static var identical: Self { return .init(identifier: "===", apply: ===, precedence: .comparison, associativity: .none, isCommutative: true) } 16 | public static var notIdentical: Self { return .init(identifier: "!==", apply: !==, precedence: .comparison, associativity: .none, isCommutative: true) } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Operators/OperatorNodeKind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperatorNodeKind.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/27/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// The kind of an operator node--either a unary or a binary operator. 10 | public enum OperatorNodeKind { 11 | case unary(UnaryOperator) 12 | case binary(BinaryOperator) 13 | } 14 | 15 | extension OperatorNodeKind: CustomStringConvertible { 16 | public var description: String { 17 | switch self { 18 | case let .unary(`operator`): 19 | return String(describing: `operator`) 20 | case let .binary(`operator`): 21 | return String(describing: `operator`) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Operators/Protocols/OperandProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperandProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | // This typealias parallels the Operand constraints for the Numeric Operator protocols 10 | public typealias NumericOperandProtocol = Numeric & Comparable & _ExpressibleByBuiltinIntegerLiteral 11 | -------------------------------------------------------------------------------- /Sources/Operators/Protocols/OperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A type that can perform an operation on another. 10 | public protocol OperatorProtocol: Equatable, CustomStringConvertible { 11 | /// The type on which the operation is performed. 12 | associatedtype Operand 13 | 14 | /// The type returned by the operation. 15 | associatedtype Result 16 | 17 | /// The string identifying the operator. 18 | var identifier: String { get } 19 | } 20 | 21 | // MARK: - Default implementations 22 | 23 | extension OperatorProtocol { 24 | public static func == (lhs: Self, rhs: Self) -> Bool { 25 | return lhs.identifier == rhs.identifier 26 | } 27 | } 28 | 29 | extension OperatorProtocol { 30 | public var description: String { 31 | return identifier 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Operators/Unary/LogicalUnaryOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalUnaryOperator.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/27/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A unary operator for performing boolean logic. 10 | public struct LogicalUnaryOperator: LogicalUnaryOperatorProtocol { 11 | public typealias Operand = Bool 12 | public typealias Result = Bool 13 | 14 | public let identifier: String 15 | public let apply: (Operand) -> Result 16 | 17 | public init(identifier: String, apply: @escaping (Operand) -> Result) { 18 | self.identifier = identifier 19 | self.apply = apply 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Operators/Unary/NumericUnaryOperator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericUnaryOperator.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/28/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A unary operator for performing arithmetic on numeric types. 10 | public struct NumericUnaryOperator: NumericUnaryOperatorProtocol { 11 | public typealias Operand = T 12 | public typealias Result = T 13 | 14 | public let identifier: String 15 | public let apply: (Operand) -> Result 16 | 17 | public init(identifier: String, apply: @escaping (Operand) -> Result) { 18 | self.identifier = identifier 19 | self.apply = apply 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Operators/Unary/Protocols/LogicalUnaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogicalUnaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/26/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A unary operator that applies boolean logic. 10 | public protocol LogicalUnaryOperatorProtocol: UnaryOperatorProtocol where Operand == Bool, Result == Bool { } 11 | 12 | // MARK: - Operators 13 | 14 | extension LogicalUnaryOperatorProtocol { 15 | public static var logicalNOT: Self { return .init(identifier: "!", apply: !) } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Operators/Unary/Protocols/NumericUnaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumericUnaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/28/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A unary operator for performing operations on Numeric types. 10 | public protocol NumericUnaryOperatorProtocol: UnaryOperatorProtocol where Operand: Numeric, Result == Operand { } 11 | 12 | // MARK: - Operators 13 | 14 | extension NumericUnaryOperatorProtocol { 15 | public static var unaryPlus: Self { return .init(identifier: "+", apply: +) } 16 | } 17 | 18 | extension NumericUnaryOperatorProtocol where Operand: SignedNumeric { 19 | public static var unaryMinus: Self { return .init(identifier: "-", apply: -) } 20 | } 21 | 22 | extension NumericUnaryOperatorProtocol where Operand: BinaryInteger { 23 | public static var bitwiseNOT: Self { return .init(identifier: "~", apply: ~) } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Operators/Unary/Protocols/UnaryOperatorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnaryOperatorProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/26/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// An operator that performs an operation on a single operand. 10 | public protocol UnaryOperatorProtocol: OperatorProtocol { 11 | init(identifier: String, apply: @escaping (Operand) -> Result) 12 | 13 | /// The function to apply to a single operand to produce the result. 14 | var apply: (Operand) -> Result { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Trees/BinarySearchTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinarySearchTree.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/19/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A traditional non-self-balancing binary search tree. 10 | public enum BinarySearchTree: BinarySearchTreeProtocol { 11 | case empty 12 | indirect case node(left: BinarySearchTree, value: Element, right: BinarySearchTree) 13 | 14 | public init() { 15 | self = .empty 16 | } 17 | 18 | public mutating func insert(_ element: Element) { 19 | self = inserting(element) 20 | } 21 | 22 | public func inserting(_ element: Element) -> BinarySearchTree { 23 | guard case let .node(left, value, right) = self else { 24 | return .node(left: .empty, value: element, right: .empty) 25 | } 26 | 27 | if element < value { 28 | return .node(left: left.inserting(element), value: value, right: right) 29 | } else if element > value { 30 | return .node(left: left, value: value, right: right.inserting(element)) 31 | } else { 32 | return self 33 | } 34 | } 35 | 36 | public mutating func delete(_ element: Element) { 37 | self = deleting(element) 38 | } 39 | 40 | public func deleting(_ element: Element) -> BinarySearchTree { 41 | guard case let .node(left, value, right) = self else { 42 | return .empty 43 | } 44 | 45 | if element < value { 46 | return .node(left: left.deleting(element), value: value, right: right) 47 | } else if element > value { 48 | return .node(left: left, value: value, right: right.deleting(element)) 49 | } else { 50 | switch (left, right) { 51 | case (.empty, .empty): 52 | return .empty 53 | case (.empty, _): 54 | return right 55 | case (_, .empty): 56 | return left 57 | default: 58 | guard let successor = right.min() else { fatalError("Unreachable--the right side cannot be empty.") } 59 | return .node(left: left, value: successor, right: right.deleting(successor)) 60 | } 61 | } 62 | } 63 | } 64 | 65 | // MARK: - Required conformance to tree protocols 66 | 67 | extension BinarySearchTree { 68 | public var nodeKind: TreeNode { 69 | switch self { 70 | case .empty: 71 | return .empty 72 | case let .node(left, value, right) where left.isEmpty && right.isEmpty: 73 | return .leaf(value) 74 | case let .node(_, value, _): 75 | return .node(value) 76 | } 77 | } 78 | 79 | public var left: BinarySearchTree? { 80 | guard case let .node(left, _, _) = self, !left.isEmpty else { return nil } 81 | return left 82 | } 83 | 84 | public var right: BinarySearchTree? { 85 | guard case let .node(_, _, right) = self, !right.isEmpty else { return nil } 86 | return right 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Trees/NeverEmptyTreeNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeverEmptyTreeNode.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/22/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// The kind of a tree node--either a leaf node or a non-leaf node. 10 | /// The enum case's associated value contains the node's data. 11 | public enum NeverEmptyTreeNode { 12 | case leaf(A) 13 | case node(B) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/BinarySearchTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinarySearchTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary tree that subscribes to the following set of rules: 10 | /// - Any node to a node's left is "less than" that node. 11 | /// - Any node to a node's right is "greater than" that node. 12 | public protocol BinarySearchTreeProtocol: SingleTypeBinaryTreeProtocol where Node: Comparable { 13 | /// Creates a new, empty tree. 14 | init() 15 | 16 | /// Returns the node containing the element if it appears in the tree, or nil if it does not. 17 | /// - Parameter element: The element to find in the tree. 18 | /// - Returns: The node containing the element if it appears in the tree, or nil if it does not. 19 | func search(for element: Element) -> Self? 20 | 21 | /// Returns a boolean value indicating whether the element appears in the tree. 22 | /// - Parameter element: The element to search for. 23 | /// - Returns: A boolean value indicating whether the element appears in the tree. 24 | func contains(_ element: Element) -> Bool 25 | 26 | /// Inserts the element into the tree. 27 | /// - Parameter element: The element to insert into the tree. 28 | mutating func insert(_ element: Element) 29 | } 30 | 31 | // MARK: - Default implementations 32 | 33 | extension BinarySearchTreeProtocol { 34 | public func search(for element: Element) -> Self? { 35 | guard let value = value else { return nil } 36 | if element < value { 37 | return left?.search(for: element) 38 | } else if element > value { 39 | return right?.search(for: element) 40 | } else { 41 | return self 42 | } 43 | } 44 | 45 | public func contains(_ element: Element) -> Bool { 46 | return search(for: element) != nil 47 | } 48 | } 49 | 50 | // MARK: - Initializers 51 | 52 | extension BinarySearchTreeProtocol { 53 | public init(_ source: S) where S.Element == Element { 54 | self = source.reduce(into: .init()) { $0.insert($1) } 55 | } 56 | } 57 | 58 | // MARK: - Methods 59 | 60 | extension BinarySearchTreeProtocol { 61 | /// Returns the minimum element in the tree, or nil if the tree is empty. 62 | /// - Returns: The minimum element in the tree, or nil if the tree is empty. 63 | public func min() -> Element? { 64 | if let left = left { 65 | return left.min() 66 | } else { 67 | return value 68 | } 69 | } 70 | 71 | /// Returns the maximum element in the tree, or nil if the tree is empty. 72 | /// - Returns: The maximum element in the tree, or nil if the tree is empty. 73 | public func max() -> Element? { 74 | if let right = right { 75 | return right.max() 76 | } else { 77 | return value 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/BinaryTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A tree with at most two children. 10 | public protocol BinaryTreeProtocol: TreeProtocol { 11 | /// The node's left child. 12 | /// This property is nil if the node has no left child. 13 | var left: Self? { get } 14 | 15 | /// The node's right child. 16 | /// This property is nil if the node has no right child. 17 | var right: Self? { get } 18 | } 19 | 20 | // MARK: - Default implementations 21 | 22 | extension BinaryTreeProtocol { 23 | /// A list containing the tree's left and right children, if present. 24 | public var children: [Self] { 25 | return [left, right].flatMap { $0 } 26 | } 27 | } 28 | 29 | // MARK: - Methods 30 | 31 | extension BinaryTreeProtocol { 32 | /// Processes the node's left chlid recursively, then itself, then its right child recursively. 33 | /// - Parameters: 34 | /// - process: The process to apply to each node. 35 | /// - kind: The kind of the node, which contains its data. 36 | public func traverseInOrder(process: (_ kind: TreeNode ) -> Void) { 37 | left.map { $0.traverseInOrder(process: process) } 38 | process(nodeKind) 39 | right.map { $0.traverseInOrder(process: process) } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/NeverEmptyTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeverEmptyTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A tree that cannot be empty. 10 | public protocol NeverEmptyTreeProtocol: TreeProtocol { 11 | /// The kind of a tree node--either a leaf node or a non-leaf node. 12 | /// The enum case's associated value contains the node's data. 13 | var neverEmptyNodeKind: NeverEmptyTreeNode { get } 14 | } 15 | 16 | extension NeverEmptyTreeProtocol { 17 | public var nodeKind: TreeNode { 18 | switch neverEmptyNodeKind { 19 | case let .leaf(value): 20 | return .leaf(value) 21 | case let .node(value): 22 | return .node(value) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/SingleTypeBinaryTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleTypeBinaryTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A binary tree whose leaf and non-leaf nodes contain values of the same type. 10 | public protocol SingleTypeBinaryTreeProtocol: SingleTypeTreeProtocol, BinaryTreeProtocol { } 11 | 12 | // MARK: - Methods 13 | 14 | extension SingleTypeBinaryTreeProtocol { 15 | /// Processes the node's left chlid recursively, then itself, then its right child recursively. 16 | /// - Parameters: 17 | /// - process: The process to apply to each node. 18 | /// - element: The data contained by the node. 19 | public func traverseInOrder(process: (_ element: Element) -> Void) { 20 | guard let value = value else { return } 21 | left.map { $0.traverseInOrder(process: process) } 22 | process(value) 23 | right.map { $0.traverseInOrder(process: process) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/SingleTypeTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleTypeTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A tree whose leaf and non-leaf nodes contain values of the same type. 10 | public protocol SingleTypeTreeProtocol: TreeProtocol where Leaf == Node { 11 | /// The single type of element contained by the tree. 12 | typealias Element = Node 13 | } 14 | 15 | // MARK: - Computed properties 16 | 17 | extension SingleTypeTreeProtocol { 18 | /// The value of the node. 19 | /// This property is nil if the tree is empty. 20 | public var value: Element? { 21 | switch nodeKind { 22 | case .empty: 23 | return nil 24 | case let .leaf(value): 25 | return value 26 | case let .node(value): 27 | return value 28 | } 29 | } 30 | } 31 | 32 | // MARK: - Methods 33 | 34 | extension SingleTypeTreeProtocol { 35 | /// Processes the node, then each of its children recursively. 36 | /// - Parameters: 37 | /// - process: The process to apply to each node. 38 | /// - element: The data contained by the node. 39 | public func traversePreOrder(process: (_ element: Element) -> Void) { 40 | guard let value = value else { return } 41 | process(value) 42 | children.forEach { $0.traversePreOrder(process: process) } 43 | } 44 | 45 | /// Processes each of the node's children recursively, then itself. 46 | /// - Parameters: 47 | /// - process: The process to apply to each node. 48 | /// - element: The data contained by the node. 49 | public func traversePostOrder(process: (_ element: Element) -> Void) { 50 | guard let value = value else { return } 51 | children.forEach { $0.traversePostOrder(process: process) } 52 | process(value) 53 | } 54 | 55 | /// Processes every node on a level, left-to-right, before continuing to the next level. 56 | /// In other words, performs a breadth-first search. 57 | /// - Parameters: 58 | /// - process: The process to apply to each node. 59 | /// - element: The data contained by the node. 60 | public func traverseLevelOrder(process: (_ element: Element) -> Void) { 61 | var queue = [self] 62 | while !queue.isEmpty { 63 | let nextNode = queue.removeFirst() 64 | guard let value = nextNode.value else { return } 65 | process(value) 66 | queue += nextNode.children 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Trees/Protocols/TreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A tree whose leaf nodes and non-leaf nodes can hold values of different types. 10 | public protocol TreeProtocol { 11 | /// The type contained by the tree's leaf nodes. 12 | associatedtype Leaf 13 | 14 | /// The type contained by the tree's non-leaf nodes. 15 | associatedtype Node 16 | 17 | /// The kind of a tree node--empty, a leaf node, or a non-leaf node. 18 | /// In either node case, the associated value contains the node's data. 19 | var nodeKind: TreeNode { get } 20 | 21 | /// A list containing the tree's children in the order of leftmost to rightmost. 22 | /// If the list is empty, the node is either a leaf or the tree is empty. 23 | var children: [Self] { get } 24 | } 25 | 26 | // MARK: - Computed properties 27 | 28 | extension TreeProtocol { 29 | /// A boolean value indicating whether the tree is empty. 30 | public var isEmpty: Bool { 31 | if case .empty = nodeKind { return true } 32 | return false 33 | } 34 | 35 | /// The number of nodes in the tree. 36 | public var count: Int { 37 | return 1 + children.map { $0.count }.reduce(0, +) 38 | } 39 | 40 | /// The zero-based height of the tree, i.e. the length of the longest path from this node to a leaf. 41 | public var height: Int { 42 | switch nodeKind { 43 | case .empty, .leaf: 44 | return 0 45 | case .node: 46 | return 1 + (children.map { $0.height }.max() ?? 0) 47 | } 48 | } 49 | } 50 | 51 | // MARK: - Methods 52 | 53 | extension TreeProtocol { 54 | /// Processes the node, then each of its children recursively. 55 | /// - Parameters: 56 | /// - process: The process to apply to each node. 57 | /// - kind: The kind of the node, which contains its data. 58 | public func traversePreOrder(process: (_ kind: TreeNode) -> Void) { 59 | process(nodeKind) 60 | children.forEach { $0.traversePreOrder(process: process) } 61 | } 62 | 63 | /// Processes each of the node's children recursively, then itself. 64 | /// - Parameters: 65 | /// - process: The process to apply to each node. 66 | /// - kind: The kind of the node, which contains its data. 67 | public func traversePostOrder(process: (_ kind: TreeNode) -> Void) { 68 | children.forEach { $0.traversePostOrder(process: process) } 69 | process(nodeKind) 70 | } 71 | 72 | /// Processes every node on a level, left-to-right, before continuing to the next level. 73 | /// In other words, performs a breadth-first search. 74 | /// - Parameters: 75 | /// - process: The process to apply to each node. 76 | /// - kind: The kind of the node, which contains its data. 77 | public func traverseLevelOrder(process: (_ kind: TreeNode) -> Void) { 78 | var queue = [self] 79 | while !queue.isEmpty { 80 | let nextNode = queue.removeFirst() 81 | process(nodeKind) 82 | queue += nextNode.children 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/Trees/RedBlackTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RedBlackTree.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | /// A self-balancing binary search tree following traditional red-black tree rules. 10 | public enum RedBlackTree: BinarySearchTreeProtocol, SingleTypeTreeProtocol { 11 | public enum Color { case red, black } 12 | 13 | case empty 14 | indirect case node(color: Color, left: RedBlackTree, value: Element, right: RedBlackTree) 15 | 16 | // MARK: Public 17 | 18 | public init() { 19 | self = .empty 20 | } 21 | 22 | public mutating func insert(_ element: Element) { 23 | self = inserting(element) 24 | } 25 | 26 | public func inserting(_ element: Element) -> RedBlackTree { 27 | guard case let .node(_, left, value, right) = insertHelper(element) else { 28 | fatalError("A tree can never be empty after an insertion.") 29 | } 30 | 31 | return .node(color: .black, left: left, value: value, right: right) 32 | } 33 | 34 | // MARK: Private 35 | 36 | private func insertHelper(_ element: Element) -> RedBlackTree { 37 | guard case let .node(color, left, value, right) = self else { 38 | return .node(color: .red, left: .empty, value: element, right: .empty) 39 | } 40 | 41 | if element < value { 42 | return RedBlackTree.node(color: color, left: left.insertHelper(element), value: value, right: right).rebalanced() 43 | } else if element > value { 44 | return RedBlackTree.node(color: color, left: left, value: value, right: right.insertHelper(element)).rebalanced() 45 | } else { 46 | return self 47 | } 48 | } 49 | 50 | private func rebalanced() -> RedBlackTree { 51 | switch self { 52 | case let .node(.black, .node(.red, .node(.red, a, x, b), y, c), z, d): 53 | return .node(color: .red, left: .node(color: .black, left: a, value: x, right: b), value: y, right: .node(color: .black, left: c, value: z, right: d)) 54 | case let .node(.black, .node(.red, a, x, .node(.red, b, y, c)), z, d): 55 | return .node(color: .red, left: .node(color: .black, left: a, value: x, right: b), value: y, right: .node(color: .black, left: c, value: z, right: d)) 56 | case let .node(.black, a, x, .node(.red, .node(.red, b, y, c), z, d)): 57 | return .node(color: .red, left: .node(color: .black, left: a, value: x, right: b), value: y, right: .node(color: .black, left: c, value: z, right: d)) 58 | case let .node(.black, a, x, .node(.red, b, y, .node(.red, c, z, d))): 59 | return .node(color: .red, left: .node(color: .black, left: a, value: x, right: b), value: y, right: .node(color: .black, left: c, value: z, right: d)) 60 | default: 61 | return self 62 | } 63 | } 64 | } 65 | 66 | // MARK: - Required conformance to tree protocols 67 | 68 | extension RedBlackTree { 69 | public var nodeKind: TreeNode { 70 | switch self { 71 | case .empty: 72 | return .empty 73 | case let .node(_, left, value, right) where left.isEmpty && right.isEmpty: 74 | return .leaf(value) 75 | case let .node(_, _, value, _): 76 | return .node(value) 77 | } 78 | } 79 | 80 | public var left: RedBlackTree? { 81 | guard case let .node(_, left, _, _) = self, !left.isEmpty else { return nil } 82 | return left 83 | } 84 | 85 | public var right: RedBlackTree? { 86 | guard case let .node(_, _, _, right) = self, !right.isEmpty else { return nil } 87 | return right 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Trees/TreeNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeNode.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | 10 | /// The kind of a tree node--empty, a leaf node, or a non-leaf node. 11 | /// In either node case, the associated value contains the node's data. 12 | public enum TreeNode { 13 | case empty 14 | case leaf(A) 15 | case node(B) 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Visualization/BinaryTreeNodeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryTreeNodeView.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/19/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// A view representing a node of a binary tree. 13 | public class BinaryTreeNodeView: UIView { 14 | public var visualAttributes: NodeVisualAttributes? 15 | public var childNodeViews: [BinaryTreeNodeView] = [] 16 | public var childLineViews: [LineView] = [] 17 | public var nextLabelText = "" 18 | public var nextColor = UIColor.clear 19 | 20 | public private(set) lazy var label: UILabel = { 21 | let label = UILabel(frame: bounds) 22 | label.textAlignment = .center 23 | addSubview(label) 24 | return label 25 | }() 26 | 27 | override public init(frame: CGRect) { 28 | super.init(frame: frame) 29 | layer.cornerRadius = bounds.width / 2 30 | clipsToBounds = true 31 | label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 32 | label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 33 | } 34 | 35 | required public init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | public func bringInChildNodes() { 40 | func applyShrinkingTranslation(toLineView lineView: LineView, x: CGFloat, y: CGFloat) { 41 | // Scaling to zero does not produce the desired effect when animated, so use a very small value instead. 42 | lineView.transform = CGAffineTransform(translationX: x, y: y).scaledBy(x: 0.0001, y: 0.0001) 43 | } 44 | 45 | switch childNodeViews.count { 46 | case 0: 47 | break 48 | case 1: 49 | let childNodeView = childNodeViews[0] 50 | guard let childLineView = childLineViews.first else { fatalError("Mismatch in the counts of child node views and line views.") } 51 | label.alpha = 0 52 | childNodeView.frame = frame 53 | applyShrinkingTranslation(toLineView: childLineView, x: 0, y: -childNodeView.frame.height / 2) 54 | case 2: 55 | let (leftNodeView, rightNodeView) = (childNodeViews[0], childNodeViews[1]) 56 | guard let leftLineView = childLineViews.first, let rightLineView = childLineViews.last else { 57 | fatalError("Mismatch in the counts of child node views and line views.") 58 | } 59 | label.alpha = 0 60 | leftNodeView.frame = frame 61 | rightNodeView.frame = frame 62 | applyShrinkingTranslation(toLineView: leftLineView, x: leftLineView.frame.width / 2, y: -leftLineView.frame.height / 2) 63 | applyShrinkingTranslation(toLineView: rightLineView, x: -rightLineView.frame.width / 2, y: -rightLineView.frame.height / 2) 64 | default: 65 | fatalError("A binary tree node cannot have more than two children.") 66 | } 67 | 68 | } 69 | 70 | public func updateToNextState() { 71 | guard let attributes = visualAttributes else { return } 72 | label.attributedText = NSAttributedString(string: nextLabelText, attributes: attributes.textAttributes) 73 | backgroundColor = nextColor 74 | for childNodeView in childNodeViews { 75 | childNodeView.alpha = 0 76 | } 77 | 78 | UIView.animate(withDuration: 0.5) { 79 | self.label.alpha = 1 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Visualization/CustomPlaygroundQuickLookableBinaryTreeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPlaygroundQuickLookableBinaryTreeProtocol.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// A binary tree that can be visualized through Playground QuickLook. 13 | public protocol CustomPlaygroundQuickLookableBinaryTreeProtocol: BinaryTreeProtocol, CustomPlaygroundQuickLookable { 14 | /// The visual attributes of the node, to be used in rendering the image of the tree. 15 | var visualAttributes: NodeVisualAttributes? { get } 16 | } 17 | 18 | // MARK: - Default implementations 19 | 20 | extension CustomPlaygroundQuickLookableBinaryTreeProtocol { 21 | public var visualAttributes: NodeVisualAttributes? { 22 | let size = NodeVisualAttributes.Default.size 23 | let color: UIColor 24 | let text: String 25 | let textAttributes = NodeVisualAttributes.Default.textAttributes 26 | 27 | switch nodeKind { 28 | case .empty: 29 | return nil 30 | case let .leaf(value): 31 | color = .flatBlue 32 | text = String(describing: value) 33 | case let .node(value): 34 | color = .flatRed 35 | text = String(describing: value) 36 | } 37 | 38 | let connectingLineColor = color 39 | 40 | return NodeVisualAttributes(size: size, color: color, text: text, textAttributes: textAttributes, connectingLineColor: connectingLineColor) 41 | } 42 | } 43 | 44 | extension CustomPlaygroundQuickLookableBinaryTreeProtocol { 45 | public var customPlaygroundQuickLook: PlaygroundQuickLook { 46 | return .image(render()) 47 | } 48 | 49 | /// Renders an image of the tree with minimized width. 50 | public func render() -> UIImage { 51 | guard !isEmpty else { return UIImage() } 52 | let positionedTree = positioned() 53 | let imageBounds = positionedTree.bounds() 54 | return UIGraphicsImageRenderer(bounds: imageBounds).image() { context in 55 | render(positionedTree, into: context.cgContext) 56 | } 57 | } 58 | 59 | /// Returns the tree with positions applied to each of its nodes. 60 | func positioned() -> PositionedBinaryTree { 61 | return PositionedBinaryTree.reingoldTilford(tree: self) 62 | } 63 | 64 | /// Recursively renders the tree into the context. 65 | private func render(_ positionedTree: PositionedBinaryTree, into context: CGContext, connectingTo parentPosition: CGPoint? = nil) { 66 | guard let attributes = positionedTree.visualAttributes else { return } 67 | let nodePosition = positionedTree.cgPointPosition 68 | 69 | positionedTree.children.forEach { child in 70 | render(child, into: context, connectingTo: nodePosition) 71 | } 72 | 73 | if let parentPosition = parentPosition { 74 | context.drawLine(from: nodePosition, to: parentPosition, color: attributes.connectingLineColor, width: attributes.childLineWidth) 75 | } 76 | 77 | attributes.color.setFill() 78 | context.fillEllipse(in: CGRect(center: nodePosition, size: attributes.size)) 79 | attributes.text.draw(centeredAt: nodePosition, withAttributes: attributes.textAttributes) 80 | } 81 | } 82 | 83 | // MARK: - Wide render 84 | // c.f. https://talk.objc.io/episodes/S01E65-playground-quicklook-for-binary-trees 85 | extension CustomPlaygroundQuickLookableBinaryTreeProtocol { 86 | /// Renders the image whose width is computed as a function of the tree's height. 87 | /// This can easily result in trees that appear to be stretched wide, 88 | /// but has the advantage of being able to distinguish between a left and a right child 89 | /// in cases where a node has only a single child. 90 | public func renderWide() -> UIImage { 91 | guard let attributes = visualAttributes else { return UIImage() } 92 | let treeHeight = height 93 | let bounds = wideBounds(forHeight: treeHeight) 94 | let center = CGPoint(x: bounds.midX, y: attributes.size.height / 2 * NodeVisualAttributes.spacingScaleFactor.vertical) 95 | return UIGraphicsImageRenderer(bounds: bounds).image() { context in 96 | renderWide(into: context.cgContext, at: center, currentHeight: treeHeight) 97 | } 98 | } 99 | 100 | /// Recursively renders the tree into the context. 101 | private func renderWide(into context: CGContext, at center: CGPoint, currentHeight: Int) { 102 | guard let attributes = visualAttributes else { return } 103 | 104 | func recurse(child: Self?, offset: CGFloat) { 105 | guard let child = child, let childAttributes = child.visualAttributes else { return } 106 | let childCenter = CGPoint(x: center.x + offset, y: center.y + childAttributes.size.height * NodeVisualAttributes.spacingScaleFactor.vertical) 107 | context.drawLine(from: center, to: childCenter, color: childAttributes.connectingLineColor, width: childAttributes.childLineWidth) 108 | child.renderWide(into: context, at: childCenter, currentHeight: currentHeight - 1) 109 | } 110 | 111 | let offset = pow(2, CGFloat(currentHeight - 1)) * attributes.size.width * NodeVisualAttributes.spacingScaleFactor.horizontal / 2 112 | recurse(child: left, offset: -offset) 113 | recurse(child: right, offset: offset) 114 | 115 | attributes.color.setFill() 116 | context.fillEllipse(in: CGRect(center: center, size: attributes.size)) 117 | attributes.text.draw(centeredAt: center, withAttributes: attributes.textAttributes) 118 | } 119 | 120 | /// Computes the bounds of the image based on the tree's height. 121 | /// The tree's height is passed as a parameter to avoid unnecessary recalculation. 122 | private func wideBounds(forHeight height: Int) -> CGRect { 123 | guard let attributes = visualAttributes else { return .zero } 124 | let boundsWidth = pow(2, CGFloat(height)) * attributes.size.width * NodeVisualAttributes.spacingScaleFactor.horizontal 125 | let boundsHeight = CGFloat(height + 1) * attributes.size.height * NodeVisualAttributes.spacingScaleFactor.vertical 126 | return CGRect(origin: .zero, size: CGSize(width: boundsWidth, height: boundsHeight)) 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /Sources/Visualization/CustomVisualAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomVisualAttributes.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/19/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | extension SingleTypeBinaryTreeProtocol where Self: CustomPlaygroundQuickLookableBinaryTreeProtocol { 13 | public var visualAttributes: NodeVisualAttributes? { 14 | guard let value = value else { return nil } 15 | 16 | let size = NodeVisualAttributes.Default.size 17 | let color = UIColor.flatGreen 18 | let text = String(describing: value) 19 | let textAttributes = NodeVisualAttributes.Default.textAttributes 20 | let connectingLineColor = color 21 | 22 | return NodeVisualAttributes(size: size, color: color, text: text, textAttributes: textAttributes, connectingLineColor: connectingLineColor) 23 | } 24 | } 25 | 26 | extension FloatingPointArithmeticExpression { 27 | public var visualAttributes: NodeVisualAttributes? { 28 | let size = CGSize(width: 32, height: 32) 29 | let color: UIColor 30 | let text: String 31 | let textAttributes = NodeVisualAttributes.Default.textAttributes 32 | 33 | switch neverEmptyNodeKind { 34 | case let .leaf(value): 35 | color = .flatBlue 36 | let displayValue = (value * 10).rounded() / 10 37 | text = String(describing: displayValue) 38 | case let .node(value): 39 | color = .flatRed 40 | text = String(describing: value) 41 | } 42 | 43 | let connectingLineColor = color 44 | 45 | return NodeVisualAttributes(size: size, color: color, text: text, textAttributes: textAttributes, connectingLineColor: connectingLineColor) 46 | } 47 | } 48 | 49 | extension LogicalExpression { 50 | public var visualAttributes: NodeVisualAttributes? { 51 | let size = CGSize(width: 36, height: 36) 52 | let color: UIColor 53 | let text: String 54 | let textAttributes = NodeVisualAttributes.Default.textAttributes 55 | 56 | switch neverEmptyNodeKind { 57 | case let .leaf(value): 58 | color = value ? .flatGreen2 : .flatRed2 59 | text = String(describing: value) 60 | case let .node(value): 61 | color = .flatBlue2 62 | text = String(describing: value) 63 | } 64 | 65 | let connectingLineColor = color 66 | 67 | return NodeVisualAttributes(size: size, color: color, text: text, textAttributes: textAttributes, connectingLineColor: connectingLineColor) 68 | } 69 | } 70 | 71 | extension BinarySearchTree: CustomPlaygroundQuickLookableBinaryTreeProtocol { } 72 | 73 | extension RedBlackTree: CustomPlaygroundQuickLookableBinaryTreeProtocol { 74 | public var visualAttributes: NodeVisualAttributes? { 75 | guard case let .node(color, _, value, _) = self else { return nil } 76 | let uiColor: UIColor 77 | switch color { 78 | case .red: 79 | uiColor = .red 80 | case .black: 81 | uiColor = .black 82 | } 83 | 84 | let size = NodeVisualAttributes.Default.size 85 | let text = String(describing: value) 86 | let textAttributes = NodeVisualAttributes.Default.textAttributes 87 | 88 | return NodeVisualAttributes(size: size, color: uiColor, text: text, textAttributes: textAttributes, connectingLineColor: uiColor) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Visualization/EvaluatableExpressionViewFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EvaluatableExpressionViewFactory.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/22/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// A factory for producing the UIView representation of an evaluatable expression. 13 | public struct EvaluatableExpressionViewFactory { 14 | /// Returns a tuple of 1) a UIView containing properly positioned node views making up the tree, 15 | /// and 2) the root node view for use in accessing the structure of these subviews. 16 | public static func makeView(of expression: T) -> (superview: UIView, rootNodeView: BinaryTreeNodeView) where T: EvaluatableExpressionProtocol { 17 | let positionedTree = expression.positioned() 18 | let superview = UIView(frame: positionedTree.bounds()) 19 | superview.backgroundColor = .white 20 | let rootNodeView = addNodeViews(of: positionedTree, to: superview) 21 | return (superview, rootNodeView) 22 | } 23 | 24 | private static func addNodeViews(of positionedTree: PositionedBinaryTree, to view: UIView) -> BinaryTreeNodeView where T: EvaluatableExpressionProtocol { 25 | let rootNodeView = nodeView(of: positionedTree) 26 | var nodeQueue = [positionedTree] 27 | var viewQueue = [rootNodeView] 28 | while !nodeQueue.isEmpty /*, !viewQueue.isEmpty */ { 29 | let currentNode = nodeQueue.removeLast() 30 | let currentNodeView = viewQueue.removeLast() 31 | 32 | func setup(childNodeView: BinaryTreeNodeView, lineView: LineView) { 33 | guard let attributes = childNodeView.visualAttributes else { fatalError("Child node view improperly configured") } 34 | lineView.backgroundColor = .clear 35 | lineView.color = attributes.connectingLineColor 36 | lineView.width = attributes.childLineWidth 37 | currentNodeView.childLineViews.append(lineView) 38 | view.addSubview(lineView) 39 | } 40 | 41 | switch currentNode.children.count { 42 | case 0: 43 | break 44 | case 1: 45 | let singleChild = currentNode.children[0] 46 | nodeQueue.insert(singleChild, at: 0) 47 | let singleNodeView = nodeView(of: singleChild) 48 | currentNodeView.childNodeViews = [singleNodeView] 49 | viewQueue.insert(singleNodeView, at: 0) 50 | let lineSize = CGSize(width: singleNodeView.frame.width, 51 | height: singleNodeView.frame.midY - currentNodeView.frame.midY) 52 | let lineOrigin = CGPoint(x: currentNodeView.frame.minX, y: currentNodeView.frame.midY) 53 | let lineView = LineView(frame: CGRect(origin: lineOrigin, size: lineSize)) 54 | lineView.direction = .vertical 55 | setup(childNodeView: singleNodeView, lineView: lineView) 56 | case 2: 57 | let (left, right) = (currentNode.children[0], currentNode.children[1]) 58 | nodeQueue.insert(contentsOf: [left, right], at: 0) 59 | let leftNodeView = nodeView(of: left) 60 | let rightNodeView = nodeView(of: right) 61 | let childNodeViews = [leftNodeView, rightNodeView] 62 | currentNodeView.childNodeViews = childNodeViews 63 | viewQueue.insert(contentsOf: childNodeViews, at: 0) 64 | 65 | let leftLineSize = CGSize(width: currentNodeView.frame.midX - leftNodeView.frame.midX, 66 | height: leftNodeView.frame.midY - currentNodeView.frame.midY) 67 | let leftLineOrigin = CGPoint(x: leftNodeView.frame.midX, y: currentNodeView.frame.midY) 68 | let leftLineView = LineView(frame: CGRect(origin: leftLineOrigin, size: leftLineSize)) 69 | leftLineView.direction = .slantedRight 70 | 71 | let rightLineSize = CGSize(width: rightNodeView.frame.midX - currentNodeView.frame.midX, 72 | height: rightNodeView.frame.midY - currentNodeView.frame.midY) 73 | let rightLineOrigin = CGPoint(x: currentNodeView.frame.midX, y: currentNodeView.frame.midY) 74 | let rightLineView = LineView(frame: CGRect(origin: rightLineOrigin, size: rightLineSize)) 75 | rightLineView.direction = .slantedLeft 76 | 77 | setup(childNodeView: leftNodeView, lineView: leftLineView) 78 | setup(childNodeView: rightNodeView, lineView: rightLineView) 79 | default: 80 | fatalError("A binary tree can have no more than two children.") 81 | } 82 | 83 | view.addSubview(currentNodeView) // add node view last to go on top of line views 84 | } 85 | 86 | return rootNodeView 87 | } 88 | 89 | private static func nodeView(of positionedTree: PositionedBinaryTree) -> BinaryTreeNodeView where T: EvaluatableExpressionProtocol { 90 | guard let attributes = positionedTree.visualAttributes else { return BinaryTreeNodeView(frame: .zero) } 91 | let cgPointPosition = positionedTree.cgPointPosition 92 | let visualX = cgPointPosition.x + attributes.size.width / 2 * NodeVisualAttributes.spacingScaleFactor.horizontal 93 | let visualY = cgPointPosition.y + attributes.size.height / 2 * NodeVisualAttributes.spacingScaleFactor.vertical 94 | let center = CGPoint(x: visualX, y: visualY) 95 | let treeNodeView = BinaryTreeNodeView(frame: CGRect(center: center, size: attributes.size)) 96 | treeNodeView.visualAttributes = attributes 97 | let (evaluatedText, evaluatedColor) = positionedTree.tree.evaluatedNodeAttributes 98 | treeNodeView.nextLabelText = evaluatedText 99 | treeNodeView.nextColor = evaluatedColor 100 | treeNodeView.label.attributedText = NSAttributedString(string: attributes.text, attributes: attributes.textAttributes) 101 | treeNodeView.backgroundColor = attributes.color 102 | return treeNodeView 103 | } 104 | 105 | private init() { } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/Visualization/Extensions/CGContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGContext.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | extension CGContext { 13 | func drawLine(from start: CGPoint, to end: CGPoint, color: UIColor, width: CGFloat) { 14 | saveGState() 15 | move(to: start) 16 | addLine(to: end) 17 | color.setStroke() 18 | setLineWidth(width) 19 | strokePath() 20 | restoreGState() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Visualization/Extensions/CGRect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | 12 | extension CGRect { 13 | init(center: CGPoint, size: CGSize) { 14 | let origin = CGPoint(x: center.x - size.width / 2, y: center.y - size.height / 2) 15 | self.init(origin: origin, size: size) 16 | } 17 | 18 | init(topLeft: CGPoint, bottomRight: CGPoint) { 19 | assert(bottomRight.x > topLeft.x && bottomRight.y > topLeft.y) 20 | let width = abs(bottomRight.x - topLeft.x) 21 | let height = abs(bottomRight.y - topLeft.y) 22 | let size = CGSize(width: width, height: height) 23 | self.init(origin: topLeft, size: size) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Visualization/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | 12 | extension String { 13 | func draw(centeredAt center: CGPoint, withAttributes attributes: [NSAttributedStringKey: Any]) { 14 | let size = (self as NSString).size(withAttributes: attributes) 15 | let bounds = CGRect(center: center, size: size) 16 | (self as NSString).draw(in: bounds, withAttributes: attributes) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Visualization/Extensions/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/15/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | extension UIColor { 13 | static let flatRed = UIColor(hexString: "#FC575E") 14 | static let flatGreen = UIColor(hexString: "#66CC99") 15 | static let flatBlue = UIColor(hexString: "#44BBFF") 16 | 17 | static let flatRed2 = UIColor(hexString: "#FF6B6E") 18 | static let flatGreen2 = UIColor(hexString: "#2DCC70") 19 | static let flatBlue2 = UIColor(hexString: "#4ECDC4") 20 | 21 | // source: http://iosapptemplates.com/blog/swift-programming/convert-hex-colors-to-uicolor-swift-4 22 | convenience init(hexString: String, alpha: CGFloat = 1.0) { 23 | let hexString = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 24 | let scanner = Scanner(string: hexString) 25 | if (hexString.hasPrefix("#")) { 26 | scanner.scanLocation = 1 27 | } 28 | var color: UInt32 = 0 29 | scanner.scanHexInt32(&color) 30 | let mask = 0x000000FF 31 | let r = Int(color >> 16) & mask 32 | let g = Int(color >> 8) & mask 33 | let b = Int(color) & mask 34 | let red = CGFloat(r) / 255.0 35 | let green = CGFloat(g) / 255.0 36 | let blue = CGFloat(b) / 255.0 37 | self.init(red: red, green: green, blue: blue, alpha: alpha) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Visualization/LineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineView.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/21/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// A view containing a line drawn across its bounds. 13 | public class LineView: UIView { 14 | public enum Direction { 15 | case vertical 16 | case horizontal 17 | case slantedLeft 18 | case slantedRight 19 | } 20 | 21 | public var direction = Direction.vertical { didSet { setNeedsDisplay() } } 22 | public var color = UIColor.black { didSet { setNeedsDisplay() } } 23 | public var width: CGFloat = 1 { didSet { setNeedsDisplay() } } 24 | 25 | override public func draw(_ bounds: CGRect) { 26 | let path = UIBezierPath() 27 | let (start, end) = lineEndPoints(basedOn: direction) 28 | path.move(to: start) 29 | path.addLine(to: end) 30 | path.lineWidth = width 31 | color.setStroke() 32 | path.stroke() 33 | } 34 | 35 | private func lineEndPoints(basedOn direction: Direction) -> (start: CGPoint, end: CGPoint) { 36 | let start: CGPoint 37 | let end: CGPoint 38 | switch direction { 39 | case .vertical: 40 | start = CGPoint(x: bounds.midX, y: bounds.maxY) 41 | end = CGPoint(x: bounds.midX, y: bounds.minY) 42 | case .horizontal: 43 | start = CGPoint(x: bounds.minX, y: bounds.midY) 44 | end = CGPoint(x: bounds.maxX, y: bounds.midY) 45 | case .slantedLeft: 46 | start = CGPoint(x: bounds.minX, y: bounds.minY) 47 | end = CGPoint(x: bounds.maxX, y: bounds.maxY) 48 | case .slantedRight: 49 | start = CGPoint(x: bounds.minX, y: bounds.maxY) 50 | end = CGPoint(x: bounds.maxX, y: bounds.minY) 51 | } 52 | return (start, end) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Visualization/NodeVisualAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeVisualAttributes.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/17/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /// The visual attributes of a node to be used in rendering the image of a tree. 13 | public struct NodeVisualAttributes { 14 | public let size: CGSize 15 | public let color: UIColor 16 | public let text: String 17 | public let textAttributes: [NSAttributedStringKey: Any] 18 | public let connectingLineColor: UIColor 19 | 20 | public var childLineWidth: CGFloat { 21 | return (1 / Default.size.width) * size.width 22 | } 23 | 24 | public enum Default { 25 | public static let size = CGSize(width: 24, height: 24) 26 | public static let textAttributes: [NSAttributedStringKey: Any] = [ 27 | .font: UIFont.boldSystemFont(ofSize: 12), 28 | .foregroundColor: UIColor.white 29 | ] 30 | } 31 | 32 | public static let spacingScaleFactor: (horizontal: CGFloat, vertical: CGFloat) = (1.5, 1.5) 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Visualization/PositionedBinaryTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PositionedBinaryTree.swift 3 | // Expression 4 | // 5 | // Created by Michael Pangburn on 12/16/17. 6 | // Copyright © 2017 Michael Pangburn. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | 12 | /// A wrapper around a binary tree to store its logical x- and y-coordinates for positioning in space. 13 | class PositionedBinaryTree { 14 | /// The tree to position. 15 | let tree: Tree 16 | 17 | /// The logical x-coordinate of the tree in space once positioned. 18 | private(set) var x: Int 19 | 20 | /// The logical y-coordinate of the tree in space once positioned. 21 | private(set) var y: Int 22 | 23 | /// The positioned children of the wrapped tree. 24 | private(set) var children: [PositionedBinaryTree] 25 | 26 | private init(tree: Tree, depth: Int) { 27 | self.tree = tree 28 | self.x = -1 29 | self.y = depth 30 | self.children = [] 31 | } 32 | } 33 | 34 | extension PositionedBinaryTree { 35 | /// Positions the tree using a naive O(_n^2_) implementation of Reingold and Tilford's algorithm. 36 | /// c.f. https://llimllib.github.io/pymag-trees/ 37 | static func reingoldTilford(tree: Tree, depth: Int = 0) -> PositionedBinaryTree { 38 | let positionedTree = PositionedBinaryTree(tree: tree, depth: depth) 39 | switch (tree.left, tree.right) { 40 | case (nil, nil): 41 | positionedTree.x = 0 42 | case let (.some(singleChild), nil): 43 | positionedTree.children = [reingoldTilford(tree: singleChild, depth: depth + 1)] 44 | positionedTree.x = positionedTree.children.first!.x 45 | case let (nil, .some(singleChild)): // Swift does not yet support combining this with the above case. 46 | positionedTree.children = [reingoldTilford(tree: singleChild, depth: depth + 1)] 47 | positionedTree.x = positionedTree.children.first!.x 48 | case let (.some(leftChild), .some(rightChild)): 49 | let left = reingoldTilford(tree: leftChild, depth: depth + 1) 50 | let right = reingoldTilford(tree: rightChild, depth: depth + 1) 51 | positionedTree.children = [left, right] 52 | positionedTree.x = computeNodePosition(fromLeft: left, right: right) 53 | } 54 | 55 | return positionedTree 56 | } 57 | 58 | /// Computes the position for a node based on its left and right subtrees. 59 | static func computeNodePosition(fromLeft left: PositionedBinaryTree, right: PositionedBinaryTree) -> Int { 60 | let leftContour = left.computeContour(.left) // An array containing the leftmost coordinate at each level. 61 | let rightContour = right.computeContour(.right) // An array containing the rightmost coordinate at each level. 62 | 63 | var rightOffset = zip(leftContour, rightContour) 64 | .map { $0 - $1 } // Map to the distances between the left and right subtrees at each level. 65 | .max()! // Find the maximum distance between the left and right subtrees over all levels. 66 | + 1 // Add 1 to the offset so the right subtree does not overlap the left subtree when shifted. 67 | 68 | // Add an additional 1 if the midpoint between the left and right is odd. 69 | // This ensures that all nodes have integral x-coordinates with no loss of precision. 70 | rightOffset += (right.x + rightOffset + left.x) % 2 71 | 72 | // Shift the whole right subtree by the computed offset. 73 | right.shiftXCoordinates(by: rightOffset) 74 | 75 | // Return the midpoint of the left and right trees' positions. 76 | return (left.x + right.x) / 2 77 | } 78 | 79 | /// The contour of a tree is a list of the maximum or minimum coordinates of a side of the tree. 80 | enum ContourDirection { 81 | /// The left contour is found by tracing down the left side of the tree. It contains the minimum x-coordinate at each level. 82 | case left 83 | 84 | /// The right contour is found by tracing down the right side of the tree. It contains the maximum x-coordinate at each level. 85 | case right 86 | } 87 | 88 | /// Computes the tree's contour in a given direction. 89 | func computeContour(_ contourDirection: ContourDirection, level: Int = 0, contour: [Int] = []) -> [Int] { 90 | let compare: (Int, Int) -> Bool = (contourDirection == .left) ? (<) : (>) 91 | var contour = contour 92 | if contour.count < level + 1 { 93 | // This is the first node encountered at this depth, so append its x-coordinate to the list. 94 | contour.append(x) 95 | } else if compare(contour[level], x) { 96 | // This node's x-coordinate trumps the previously-recorded coordinate for this depth, so replace it. 97 | contour[level] = x 98 | } 99 | 100 | children.forEach { child in 101 | contour = child.computeContour(contourDirection, level: level + 1, contour: contour) 102 | } 103 | 104 | return contour 105 | } 106 | 107 | /// Shifts the x-coordinate of each node in the tree by the value. 108 | func shiftXCoordinates(by value: Int) { 109 | x += value 110 | children.forEach { $0.shiftXCoordinates(by: value) } 111 | } 112 | } 113 | 114 | extension PositionedBinaryTree { 115 | /// The visual attributes of the positioned tree, taken from the tree model it wraps. 116 | var visualAttributes: NodeVisualAttributes? { 117 | return tree.visualAttributes 118 | } 119 | 120 | /// Compute the bounds for the image of the tree based on the positions of its nodes. 121 | func bounds() -> CGRect { 122 | guard let attributes = visualAttributes else { return .zero } 123 | let points = cgPointPositions() 124 | let xCoordinates = points.map { $0.x } 125 | let yCoordinates = points.map { $0.y } 126 | let horizontalOffset = attributes.size.width / 2 * NodeVisualAttributes.spacingScaleFactor.horizontal 127 | let verticalOffset = attributes.size.height / 2 * NodeVisualAttributes.spacingScaleFactor.vertical 128 | let topLeft = CGPoint(x: xCoordinates.min()! - horizontalOffset, y: yCoordinates.min()! - verticalOffset) 129 | let bottomRight = CGPoint(x: xCoordinates.max()! + horizontalOffset, y: yCoordinates.max()! + verticalOffset) 130 | return CGRect(topLeft: topLeft, bottomRight: bottomRight) 131 | } 132 | 133 | /// Returns a list of the CGPoints representing the centers of all the nodes in the tree. 134 | func cgPointPositions() -> [CGPoint] { 135 | return [cgPointPosition] + children.flatMap { $0.cgPointPositions() } 136 | } 137 | 138 | /// Translates the logical position of the tree into a CGPoint representing the center of the node. 139 | var cgPointPosition: CGPoint { 140 | guard let attributes = visualAttributes else { return .zero } 141 | let scaledX = CGFloat(x) * attributes.size.width * NodeVisualAttributes.spacingScaleFactor.horizontal 142 | let scaledY = CGFloat(y) * attributes.size.height * NodeVisualAttributes.spacingScaleFactor.vertical 143 | return CGPoint(x: scaledX, y: scaledY) 144 | } 145 | } 146 | 147 | extension PositionedBinaryTree: CustomStringConvertible { 148 | var description: String { 149 | return "\(tree) at (\(x), \(y))" 150 | } 151 | } 152 | --------------------------------------------------------------------------------