├── .gitignore ├── .gitmodules ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── Demo ├── ReactKitCalculatorDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── ReactKitCalculatorDemo │ ├── AppDelegate.swift │ ├── Base.lproj │ └── LaunchScreen.xib │ ├── CalculatorViewController.swift │ ├── CalculatorViewController.xib │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ └── Info.plist ├── LICENSE ├── README.md ├── ReactKitCalculator.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── ReactKitCalculator-OSX.xcscheme │ └── ReactKitCalculator-iOS.xcscheme ├── ReactKitCalculator.xcworkspace └── contents.xcworkspacedata ├── ReactKitCalculator ├── Calculator.swift ├── Helper.swift ├── Info.plist └── ReactKitCalculator.h └── ReactKitCalculatorTests ├── ExpressionSpec.swift ├── Info.plist ├── OutputSpec.swift └── _Peripheral.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/Nimble"] 2 | path = Carthage/Checkouts/Nimble 3 | url = https://github.com/Quick/Nimble.git 4 | [submodule "Carthage/Checkouts/Quick"] 5 | path = Carthage/Checkouts/Quick 6 | url = https://github.com/Quick/Quick.git 7 | [submodule "Carthage/Checkouts/SwiftTask"] 8 | path = Carthage/Checkouts/SwiftTask 9 | url = https://github.com/ReactKit/SwiftTask.git 10 | [submodule "Carthage/Checkouts/ReactKit"] 11 | path = Carthage/Checkouts/ReactKit 12 | url = https://github.com/ReactKit/ReactKit.git 13 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactKit/ReactKit" ~> 0.12.0 -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Quick" "swift-2.0" 2 | github "Quick/Nimble" "swift-2.0" -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "a87c41424b2b8bc7821cf9e0352f87f53daf5263" 2 | github "Quick/Quick" "bfba21156d7b50d250f32424d0280511435206e6" 3 | github "ReactKit/SwiftTask" "4.0.0" 4 | github "ReactKit/ReactKit" "0.12.0" 5 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1F8560441A949F6700103F01 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8560431A949F6700103F01 /* AppDelegate.swift */; }; 11 | 1F85604B1A949F6700103F01 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F85604A1A949F6700103F01 /* Images.xcassets */; }; 12 | 1F85604E1A949F6700103F01 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1F85604C1A949F6700103F01 /* LaunchScreen.xib */; }; 13 | 1F85638C1A94BF7700103F01 /* CalculatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F85638A1A94BF7700103F01 /* CalculatorViewController.swift */; }; 14 | 1F85638D1A94BF7700103F01 /* CalculatorViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1F85638B1A94BF7700103F01 /* CalculatorViewController.xib */; }; 15 | 1F8563B01A94C05C00103F01 /* ReactKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F85631E1A94BB2800103F01 /* ReactKit.framework */; }; 16 | 1F8563B11A94C05C00103F01 /* ReactKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1F85631E1A94BB2800103F01 /* ReactKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 1F8563B41A94C08700103F01 /* ReactKitCalculator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8560861A94BB1700103F01 /* ReactKitCalculator.framework */; }; 18 | 1F8563B51A94C08700103F01 /* ReactKitCalculator.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1F8560861A94BB1700103F01 /* ReactKitCalculator.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | 1F8563DA1A94C61600103F01 /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F85632A1A94BB2800103F01 /* SwiftTask.framework */; }; 20 | 1F8563DB1A94C61600103F01 /* SwiftTask.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1F85632A1A94BB2800103F01 /* SwiftTask.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 1F8560831A94BB1700103F01 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */; 27 | proxyType = 2; 28 | remoteGlobalIDString = 1F9781931A94984600A7D656; 29 | remoteInfo = "ReactKitCalculator-OSX"; 30 | }; 31 | 1F8560851A94BB1700103F01 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */; 34 | proxyType = 2; 35 | remoteGlobalIDString = 1F977EC11A94978B00A7D656; 36 | remoteInfo = "ReactKitCalculator-iOS"; 37 | }; 38 | 1F8560871A94BB1700103F01 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */; 41 | proxyType = 2; 42 | remoteGlobalIDString = 1F9781B11A9498AD00A7D656; 43 | remoteInfo = ReactKitCalculatorTests; 44 | }; 45 | 1F8563191A94BB2800103F01 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 48 | proxyType = 2; 49 | remoteGlobalIDString = 1FED88F819C1EB120065658B; 50 | remoteInfo = "ReactKit-OSX"; 51 | }; 52 | 1F85631B1A94BB2800103F01 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 55 | proxyType = 2; 56 | remoteGlobalIDString = 1FED890319C1EB120065658B; 57 | remoteInfo = "ReactKit-OSXTests"; 58 | }; 59 | 1F85631D1A94BB2800103F01 /* PBXContainerItemProxy */ = { 60 | isa = PBXContainerItemProxy; 61 | containerPortal = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 62 | proxyType = 2; 63 | remoteGlobalIDString = 1FED894119C1EB7D0065658B; 64 | remoteInfo = "ReactKit-iOS"; 65 | }; 66 | 1F85631F1A94BB2800103F01 /* PBXContainerItemProxy */ = { 67 | isa = PBXContainerItemProxy; 68 | containerPortal = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 69 | proxyType = 2; 70 | remoteGlobalIDString = 1FED894B19C1EB7D0065658B; 71 | remoteInfo = "ReactKit-iOSTests"; 72 | }; 73 | 1F8563251A94BB2800103F01 /* PBXContainerItemProxy */ = { 74 | isa = PBXContainerItemProxy; 75 | containerPortal = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 76 | proxyType = 2; 77 | remoteGlobalIDString = 1F46DED4199EDF1000F97868; 78 | remoteInfo = "SwiftTask-OSX"; 79 | }; 80 | 1F8563271A94BB2800103F01 /* PBXContainerItemProxy */ = { 81 | isa = PBXContainerItemProxy; 82 | containerPortal = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 83 | proxyType = 2; 84 | remoteGlobalIDString = 1F46DEDF199EDF1000F97868; 85 | remoteInfo = "SwiftTask-OSXTests"; 86 | }; 87 | 1F8563291A94BB2800103F01 /* PBXContainerItemProxy */ = { 88 | isa = PBXContainerItemProxy; 89 | containerPortal = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 90 | proxyType = 2; 91 | remoteGlobalIDString = 48CD5A0C19AEE3570042B9F1; 92 | remoteInfo = "SwiftTask-iOS"; 93 | }; 94 | 1F85632B1A94BB2800103F01 /* PBXContainerItemProxy */ = { 95 | isa = PBXContainerItemProxy; 96 | containerPortal = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 97 | proxyType = 2; 98 | remoteGlobalIDString = 4822F0D019D00ABF00F5F572; 99 | remoteInfo = "SwiftTask-iOSTests"; 100 | }; 101 | 1F8563B21A94C05C00103F01 /* PBXContainerItemProxy */ = { 102 | isa = PBXContainerItemProxy; 103 | containerPortal = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 104 | proxyType = 1; 105 | remoteGlobalIDString = 1FED894019C1EB7D0065658B; 106 | remoteInfo = "ReactKit-iOS"; 107 | }; 108 | 1F8563B61A94C08700103F01 /* PBXContainerItemProxy */ = { 109 | isa = PBXContainerItemProxy; 110 | containerPortal = 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */; 111 | proxyType = 1; 112 | remoteGlobalIDString = 1F977EC01A94978B00A7D656; 113 | remoteInfo = "ReactKitCalculator-iOS"; 114 | }; 115 | 1F8563DC1A94C61600103F01 /* PBXContainerItemProxy */ = { 116 | isa = PBXContainerItemProxy; 117 | containerPortal = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 118 | proxyType = 1; 119 | remoteGlobalIDString = 48CD5A0B19AEE3570042B9F1; 120 | remoteInfo = "SwiftTask-iOS"; 121 | }; 122 | /* End PBXContainerItemProxy section */ 123 | 124 | /* Begin PBXCopyFilesBuildPhase section */ 125 | 1F8563AF1A94C03700103F01 /* Embed Frameworks */ = { 126 | isa = PBXCopyFilesBuildPhase; 127 | buildActionMask = 2147483647; 128 | dstPath = ""; 129 | dstSubfolderSpec = 10; 130 | files = ( 131 | 1F8563DB1A94C61600103F01 /* SwiftTask.framework in Embed Frameworks */, 132 | 1F8563B51A94C08700103F01 /* ReactKitCalculator.framework in Embed Frameworks */, 133 | 1F8563B11A94C05C00103F01 /* ReactKit.framework in Embed Frameworks */, 134 | ); 135 | name = "Embed Frameworks"; 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXCopyFilesBuildPhase section */ 139 | 140 | /* Begin PBXFileReference section */ 141 | 1F85603E1A949F6700103F01 /* ReactKitCalculatorDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactKitCalculatorDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 142 | 1F8560421A949F6700103F01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143 | 1F8560431A949F6700103F01 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 144 | 1F85604A1A949F6700103F01 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 145 | 1F85604D1A949F6700103F01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 146 | 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKitCalculator.xcodeproj; path = ../ReactKitCalculator.xcodeproj; sourceTree = ""; }; 147 | 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = ReactKit.xcodeproj; sourceTree = ""; }; 148 | 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SwiftTask.xcodeproj; sourceTree = ""; }; 149 | 1F85638A1A94BF7700103F01 /* CalculatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalculatorViewController.swift; sourceTree = ""; }; 150 | 1F85638B1A94BF7700103F01 /* CalculatorViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CalculatorViewController.xib; sourceTree = ""; }; 151 | /* End PBXFileReference section */ 152 | 153 | /* Begin PBXFrameworksBuildPhase section */ 154 | 1F85603B1A949F6700103F01 /* Frameworks */ = { 155 | isa = PBXFrameworksBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | 1F8563DA1A94C61600103F01 /* SwiftTask.framework in Frameworks */, 159 | 1F8563B41A94C08700103F01 /* ReactKitCalculator.framework in Frameworks */, 160 | 1F8563B01A94C05C00103F01 /* ReactKit.framework in Frameworks */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXFrameworksBuildPhase section */ 165 | 166 | /* Begin PBXGroup section */ 167 | 1F8560351A949F6700103F01 = { 168 | isa = PBXGroup; 169 | children = ( 170 | 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */, 171 | 1F8560891A94BB2700103F01 /* Checkouts */, 172 | 1F8560401A949F6700103F01 /* ReactKitCalculatorDemo */, 173 | 1F85603F1A949F6700103F01 /* Products */, 174 | ); 175 | sourceTree = ""; 176 | }; 177 | 1F85603F1A949F6700103F01 /* Products */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 1F85603E1A949F6700103F01 /* ReactKitCalculatorDemo.app */, 181 | ); 182 | name = Products; 183 | sourceTree = ""; 184 | }; 185 | 1F8560401A949F6700103F01 /* ReactKitCalculatorDemo */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 1F8560431A949F6700103F01 /* AppDelegate.swift */, 189 | 1F85638A1A94BF7700103F01 /* CalculatorViewController.swift */, 190 | 1F85638B1A94BF7700103F01 /* CalculatorViewController.xib */, 191 | 1F85604A1A949F6700103F01 /* Images.xcassets */, 192 | 1F85604C1A949F6700103F01 /* LaunchScreen.xib */, 193 | 1F8560411A949F6700103F01 /* Supporting Files */, 194 | ); 195 | path = ReactKitCalculatorDemo; 196 | sourceTree = ""; 197 | }; 198 | 1F8560411A949F6700103F01 /* Supporting Files */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 1F8560421A949F6700103F01 /* Info.plist */, 202 | ); 203 | name = "Supporting Files"; 204 | sourceTree = ""; 205 | }; 206 | 1F85607A1A94BB1700103F01 /* Products */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | 1F8560841A94BB1700103F01 /* ReactKitCalculator.framework */, 210 | 1F8560861A94BB1700103F01 /* ReactKitCalculator.framework */, 211 | 1F8560881A94BB1700103F01 /* ReactKitCalculatorTests.xctest */, 212 | ); 213 | name = Products; 214 | sourceTree = ""; 215 | }; 216 | 1F8560891A94BB2700103F01 /* Checkouts */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 1F8561AB1A94BB2700103F01 /* ReactKit */, 220 | 1F8562541A94BB2800103F01 /* SwiftTask */, 221 | ); 222 | name = Checkouts; 223 | path = ../Carthage/Checkouts; 224 | sourceTree = ""; 225 | }; 226 | 1F8561AB1A94BB2700103F01 /* ReactKit */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */, 230 | ); 231 | path = ReactKit; 232 | sourceTree = ""; 233 | }; 234 | 1F8562481A94BB2800103F01 /* Products */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 1F85631A1A94BB2800103F01 /* ReactKit.framework */, 238 | 1F85631C1A94BB2800103F01 /* ReactKit-OSXTests.xctest */, 239 | 1F85631E1A94BB2800103F01 /* ReactKit.framework */, 240 | 1F8563201A94BB2800103F01 /* ReactKit-iOSTests.xctest */, 241 | ); 242 | name = Products; 243 | sourceTree = ""; 244 | }; 245 | 1F8562541A94BB2800103F01 /* SwiftTask */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */, 249 | ); 250 | path = SwiftTask; 251 | sourceTree = ""; 252 | }; 253 | 1F8562A91A94BB2800103F01 /* Products */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | 1F8563261A94BB2800103F01 /* SwiftTask.framework */, 257 | 1F8563281A94BB2800103F01 /* SwiftTask-OSXTests.xctest */, 258 | 1F85632A1A94BB2800103F01 /* SwiftTask.framework */, 259 | 1F85632C1A94BB2800103F01 /* SwiftTask-iOSTests.xctest */, 260 | ); 261 | name = Products; 262 | sourceTree = ""; 263 | }; 264 | /* End PBXGroup section */ 265 | 266 | /* Begin PBXNativeTarget section */ 267 | 1F85603D1A949F6700103F01 /* ReactKitCalculatorDemo */ = { 268 | isa = PBXNativeTarget; 269 | buildConfigurationList = 1F85605D1A949F6700103F01 /* Build configuration list for PBXNativeTarget "ReactKitCalculatorDemo" */; 270 | buildPhases = ( 271 | 1F85603A1A949F6700103F01 /* Sources */, 272 | 1F85603B1A949F6700103F01 /* Frameworks */, 273 | 1F85603C1A949F6700103F01 /* Resources */, 274 | 1F8563AF1A94C03700103F01 /* Embed Frameworks */, 275 | ); 276 | buildRules = ( 277 | ); 278 | dependencies = ( 279 | 1F8563B31A94C05C00103F01 /* PBXTargetDependency */, 280 | 1F8563B71A94C08700103F01 /* PBXTargetDependency */, 281 | 1F8563DD1A94C61600103F01 /* PBXTargetDependency */, 282 | ); 283 | name = ReactKitCalculatorDemo; 284 | productName = ReactKitCalculatorDemo; 285 | productReference = 1F85603E1A949F6700103F01 /* ReactKitCalculatorDemo.app */; 286 | productType = "com.apple.product-type.application"; 287 | }; 288 | /* End PBXNativeTarget section */ 289 | 290 | /* Begin PBXProject section */ 291 | 1F8560361A949F6700103F01 /* Project object */ = { 292 | isa = PBXProject; 293 | attributes = { 294 | LastSwiftUpdateCheck = 0700; 295 | LastUpgradeCheck = 0700; 296 | ORGANIZATIONNAME = "Yasuhiro Inami"; 297 | TargetAttributes = { 298 | 1F85603D1A949F6700103F01 = { 299 | CreatedOnToolsVersion = 6.1.1; 300 | }; 301 | }; 302 | }; 303 | buildConfigurationList = 1F8560391A949F6700103F01 /* Build configuration list for PBXProject "ReactKitCalculatorDemo" */; 304 | compatibilityVersion = "Xcode 3.2"; 305 | developmentRegion = English; 306 | hasScannedForEncodings = 0; 307 | knownRegions = ( 308 | en, 309 | Base, 310 | ); 311 | mainGroup = 1F8560351A949F6700103F01; 312 | productRefGroup = 1F85603F1A949F6700103F01 /* Products */; 313 | projectDirPath = ""; 314 | projectReferences = ( 315 | { 316 | ProductGroup = 1F8562481A94BB2800103F01 /* Products */; 317 | ProjectRef = 1F8562471A94BB2800103F01 /* ReactKit.xcodeproj */; 318 | }, 319 | { 320 | ProductGroup = 1F85607A1A94BB1700103F01 /* Products */; 321 | ProjectRef = 1F8560791A94BB1700103F01 /* ReactKitCalculator.xcodeproj */; 322 | }, 323 | { 324 | ProductGroup = 1F8562A91A94BB2800103F01 /* Products */; 325 | ProjectRef = 1F8562A81A94BB2800103F01 /* SwiftTask.xcodeproj */; 326 | }, 327 | ); 328 | projectRoot = ""; 329 | targets = ( 330 | 1F85603D1A949F6700103F01 /* ReactKitCalculatorDemo */, 331 | ); 332 | }; 333 | /* End PBXProject section */ 334 | 335 | /* Begin PBXReferenceProxy section */ 336 | 1F8560841A94BB1700103F01 /* ReactKitCalculator.framework */ = { 337 | isa = PBXReferenceProxy; 338 | fileType = wrapper.framework; 339 | path = ReactKitCalculator.framework; 340 | remoteRef = 1F8560831A94BB1700103F01 /* PBXContainerItemProxy */; 341 | sourceTree = BUILT_PRODUCTS_DIR; 342 | }; 343 | 1F8560861A94BB1700103F01 /* ReactKitCalculator.framework */ = { 344 | isa = PBXReferenceProxy; 345 | fileType = wrapper.framework; 346 | path = ReactKitCalculator.framework; 347 | remoteRef = 1F8560851A94BB1700103F01 /* PBXContainerItemProxy */; 348 | sourceTree = BUILT_PRODUCTS_DIR; 349 | }; 350 | 1F8560881A94BB1700103F01 /* ReactKitCalculatorTests.xctest */ = { 351 | isa = PBXReferenceProxy; 352 | fileType = wrapper.cfbundle; 353 | path = ReactKitCalculatorTests.xctest; 354 | remoteRef = 1F8560871A94BB1700103F01 /* PBXContainerItemProxy */; 355 | sourceTree = BUILT_PRODUCTS_DIR; 356 | }; 357 | 1F85631A1A94BB2800103F01 /* ReactKit.framework */ = { 358 | isa = PBXReferenceProxy; 359 | fileType = wrapper.framework; 360 | path = ReactKit.framework; 361 | remoteRef = 1F8563191A94BB2800103F01 /* PBXContainerItemProxy */; 362 | sourceTree = BUILT_PRODUCTS_DIR; 363 | }; 364 | 1F85631C1A94BB2800103F01 /* ReactKit-OSXTests.xctest */ = { 365 | isa = PBXReferenceProxy; 366 | fileType = wrapper.cfbundle; 367 | path = "ReactKit-OSXTests.xctest"; 368 | remoteRef = 1F85631B1A94BB2800103F01 /* PBXContainerItemProxy */; 369 | sourceTree = BUILT_PRODUCTS_DIR; 370 | }; 371 | 1F85631E1A94BB2800103F01 /* ReactKit.framework */ = { 372 | isa = PBXReferenceProxy; 373 | fileType = wrapper.framework; 374 | path = ReactKit.framework; 375 | remoteRef = 1F85631D1A94BB2800103F01 /* PBXContainerItemProxy */; 376 | sourceTree = BUILT_PRODUCTS_DIR; 377 | }; 378 | 1F8563201A94BB2800103F01 /* ReactKit-iOSTests.xctest */ = { 379 | isa = PBXReferenceProxy; 380 | fileType = wrapper.cfbundle; 381 | path = "ReactKit-iOSTests.xctest"; 382 | remoteRef = 1F85631F1A94BB2800103F01 /* PBXContainerItemProxy */; 383 | sourceTree = BUILT_PRODUCTS_DIR; 384 | }; 385 | 1F8563261A94BB2800103F01 /* SwiftTask.framework */ = { 386 | isa = PBXReferenceProxy; 387 | fileType = wrapper.framework; 388 | path = SwiftTask.framework; 389 | remoteRef = 1F8563251A94BB2800103F01 /* PBXContainerItemProxy */; 390 | sourceTree = BUILT_PRODUCTS_DIR; 391 | }; 392 | 1F8563281A94BB2800103F01 /* SwiftTask-OSXTests.xctest */ = { 393 | isa = PBXReferenceProxy; 394 | fileType = wrapper.cfbundle; 395 | path = "SwiftTask-OSXTests.xctest"; 396 | remoteRef = 1F8563271A94BB2800103F01 /* PBXContainerItemProxy */; 397 | sourceTree = BUILT_PRODUCTS_DIR; 398 | }; 399 | 1F85632A1A94BB2800103F01 /* SwiftTask.framework */ = { 400 | isa = PBXReferenceProxy; 401 | fileType = wrapper.framework; 402 | path = SwiftTask.framework; 403 | remoteRef = 1F8563291A94BB2800103F01 /* PBXContainerItemProxy */; 404 | sourceTree = BUILT_PRODUCTS_DIR; 405 | }; 406 | 1F85632C1A94BB2800103F01 /* SwiftTask-iOSTests.xctest */ = { 407 | isa = PBXReferenceProxy; 408 | fileType = wrapper.cfbundle; 409 | path = "SwiftTask-iOSTests.xctest"; 410 | remoteRef = 1F85632B1A94BB2800103F01 /* PBXContainerItemProxy */; 411 | sourceTree = BUILT_PRODUCTS_DIR; 412 | }; 413 | /* End PBXReferenceProxy section */ 414 | 415 | /* Begin PBXResourcesBuildPhase section */ 416 | 1F85603C1A949F6700103F01 /* Resources */ = { 417 | isa = PBXResourcesBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | 1F85604E1A949F6700103F01 /* LaunchScreen.xib in Resources */, 421 | 1F85638D1A94BF7700103F01 /* CalculatorViewController.xib in Resources */, 422 | 1F85604B1A949F6700103F01 /* Images.xcassets in Resources */, 423 | ); 424 | runOnlyForDeploymentPostprocessing = 0; 425 | }; 426 | /* End PBXResourcesBuildPhase section */ 427 | 428 | /* Begin PBXSourcesBuildPhase section */ 429 | 1F85603A1A949F6700103F01 /* Sources */ = { 430 | isa = PBXSourcesBuildPhase; 431 | buildActionMask = 2147483647; 432 | files = ( 433 | 1F85638C1A94BF7700103F01 /* CalculatorViewController.swift in Sources */, 434 | 1F8560441A949F6700103F01 /* AppDelegate.swift in Sources */, 435 | ); 436 | runOnlyForDeploymentPostprocessing = 0; 437 | }; 438 | /* End PBXSourcesBuildPhase section */ 439 | 440 | /* Begin PBXTargetDependency section */ 441 | 1F8563B31A94C05C00103F01 /* PBXTargetDependency */ = { 442 | isa = PBXTargetDependency; 443 | name = "ReactKit-iOS"; 444 | targetProxy = 1F8563B21A94C05C00103F01 /* PBXContainerItemProxy */; 445 | }; 446 | 1F8563B71A94C08700103F01 /* PBXTargetDependency */ = { 447 | isa = PBXTargetDependency; 448 | name = "ReactKitCalculator-iOS"; 449 | targetProxy = 1F8563B61A94C08700103F01 /* PBXContainerItemProxy */; 450 | }; 451 | 1F8563DD1A94C61600103F01 /* PBXTargetDependency */ = { 452 | isa = PBXTargetDependency; 453 | name = "SwiftTask-iOS"; 454 | targetProxy = 1F8563DC1A94C61600103F01 /* PBXContainerItemProxy */; 455 | }; 456 | /* End PBXTargetDependency section */ 457 | 458 | /* Begin PBXVariantGroup section */ 459 | 1F85604C1A949F6700103F01 /* LaunchScreen.xib */ = { 460 | isa = PBXVariantGroup; 461 | children = ( 462 | 1F85604D1A949F6700103F01 /* Base */, 463 | ); 464 | name = LaunchScreen.xib; 465 | sourceTree = ""; 466 | }; 467 | /* End PBXVariantGroup section */ 468 | 469 | /* Begin XCBuildConfiguration section */ 470 | 1F85605B1A949F6700103F01 /* Debug */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | ALWAYS_SEARCH_USER_PATHS = NO; 474 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 475 | CLANG_CXX_LIBRARY = "libc++"; 476 | CLANG_ENABLE_MODULES = YES; 477 | CLANG_ENABLE_OBJC_ARC = YES; 478 | CLANG_WARN_BOOL_CONVERSION = YES; 479 | CLANG_WARN_CONSTANT_CONVERSION = YES; 480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 481 | CLANG_WARN_EMPTY_BODY = YES; 482 | CLANG_WARN_ENUM_CONVERSION = YES; 483 | CLANG_WARN_INT_CONVERSION = YES; 484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 485 | CLANG_WARN_UNREACHABLE_CODE = YES; 486 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 487 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 488 | COPY_PHASE_STRIP = NO; 489 | ENABLE_STRICT_OBJC_MSGSEND = YES; 490 | ENABLE_TESTABILITY = YES; 491 | GCC_C_LANGUAGE_STANDARD = gnu99; 492 | GCC_DYNAMIC_NO_PIC = NO; 493 | GCC_OPTIMIZATION_LEVEL = 0; 494 | GCC_PREPROCESSOR_DEFINITIONS = ( 495 | "DEBUG=1", 496 | "$(inherited)", 497 | ); 498 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 499 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 500 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 501 | GCC_WARN_UNDECLARED_SELECTOR = YES; 502 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 503 | GCC_WARN_UNUSED_FUNCTION = YES; 504 | GCC_WARN_UNUSED_VARIABLE = YES; 505 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 506 | MTL_ENABLE_DEBUG_INFO = YES; 507 | ONLY_ACTIVE_ARCH = YES; 508 | SDKROOT = iphoneos; 509 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 510 | TARGETED_DEVICE_FAMILY = "1,2"; 511 | }; 512 | name = Debug; 513 | }; 514 | 1F85605C1A949F6700103F01 /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | ALWAYS_SEARCH_USER_PATHS = NO; 518 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 519 | CLANG_CXX_LIBRARY = "libc++"; 520 | CLANG_ENABLE_MODULES = YES; 521 | CLANG_ENABLE_OBJC_ARC = YES; 522 | CLANG_WARN_BOOL_CONVERSION = YES; 523 | CLANG_WARN_CONSTANT_CONVERSION = YES; 524 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 525 | CLANG_WARN_EMPTY_BODY = YES; 526 | CLANG_WARN_ENUM_CONVERSION = YES; 527 | CLANG_WARN_INT_CONVERSION = YES; 528 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 529 | CLANG_WARN_UNREACHABLE_CODE = YES; 530 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 531 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 532 | COPY_PHASE_STRIP = YES; 533 | ENABLE_NS_ASSERTIONS = NO; 534 | ENABLE_STRICT_OBJC_MSGSEND = YES; 535 | GCC_C_LANGUAGE_STANDARD = gnu99; 536 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 537 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 538 | GCC_WARN_UNDECLARED_SELECTOR = YES; 539 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 540 | GCC_WARN_UNUSED_FUNCTION = YES; 541 | GCC_WARN_UNUSED_VARIABLE = YES; 542 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 543 | MTL_ENABLE_DEBUG_INFO = NO; 544 | SDKROOT = iphoneos; 545 | TARGETED_DEVICE_FAMILY = "1,2"; 546 | VALIDATE_PRODUCT = YES; 547 | }; 548 | name = Release; 549 | }; 550 | 1F85605E1A949F6700103F01 /* Debug */ = { 551 | isa = XCBuildConfiguration; 552 | buildSettings = { 553 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 554 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 555 | INFOPLIST_FILE = ReactKitCalculatorDemo/Info.plist; 556 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 557 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 558 | PRODUCT_NAME = ReactKitCalculatorDemo; 559 | }; 560 | name = Debug; 561 | }; 562 | 1F85605F1A949F6700103F01 /* Release */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 567 | INFOPLIST_FILE = ReactKitCalculatorDemo/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 569 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 570 | PRODUCT_NAME = ReactKitCalculatorDemo; 571 | }; 572 | name = Release; 573 | }; 574 | /* End XCBuildConfiguration section */ 575 | 576 | /* Begin XCConfigurationList section */ 577 | 1F8560391A949F6700103F01 /* Build configuration list for PBXProject "ReactKitCalculatorDemo" */ = { 578 | isa = XCConfigurationList; 579 | buildConfigurations = ( 580 | 1F85605B1A949F6700103F01 /* Debug */, 581 | 1F85605C1A949F6700103F01 /* Release */, 582 | ); 583 | defaultConfigurationIsVisible = 0; 584 | defaultConfigurationName = Release; 585 | }; 586 | 1F85605D1A949F6700103F01 /* Build configuration list for PBXNativeTarget "ReactKitCalculatorDemo" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 1F85605E1A949F6700103F01 /* Debug */, 590 | 1F85605F1A949F6700103F01 /* Release */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | /* End XCConfigurationList section */ 596 | }; 597 | rootObject = 1F8560361A949F6700103F01 /* Project object */; 598 | } 599 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ReactKitCalculatorDemo 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 17 | 18 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 19 | self.window?.rootViewController = CalculatorViewController(nibName: "CalculatorViewController", bundle: nil) 20 | self.window?.makeKeyAndVisible() 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(application: UIApplication) { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(application: UIApplication) { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(application: UIApplication) { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(application: UIApplication) { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | func applicationWillTerminate(application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/CalculatorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalculatorViewController.swift 3 | // ReactKitCalculatorDemo 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactKit 11 | import ReactKitCalculator 12 | 13 | class CalculatorViewController: UIViewController { 14 | 15 | @IBOutlet var nonClearButtons: [UIButton]! 16 | @IBOutlet var clearButton: UIButton! 17 | 18 | @IBOutlet var textField: UITextField! 19 | @IBOutlet var historyLabel: UILabel! 20 | 21 | var calculator: Calculator? 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | self.setupSubviews() 27 | self.setupStreams() 28 | } 29 | 30 | func setupSubviews() 31 | { 32 | for subview in (self.nonClearButtons + [self.clearButton]) { 33 | subview.layer.borderWidth = 0.5 34 | subview.layer.borderColor = UIColor.blackColor().colorWithAlphaComponent(0.5).CGColor 35 | } 36 | 37 | self.historyLabel.text = "" 38 | } 39 | 40 | func setupStreams() 41 | { 42 | self.calculator = Calculator { mapper in 43 | 44 | let alternativeMappingTuples = [ 45 | ("×", Calculator.Key.Multiply), // convert "×" to "*" 46 | ("÷", Calculator.Key.Divide) // convert "÷" to "/" 47 | ] 48 | 49 | // map buttonStream to calculator via `button.title` 50 | for button in self.nonClearButtons { 51 | let buttonTitle = button.titleForState(.Normal) 52 | let defaultMappedKey = Calculator.Key(rawValue: buttonTitle!) 53 | 54 | let key = defaultMappedKey ?? (alternativeMappingTuples.filter { $0.0 == buttonTitle }.first?.1) ?? nil 55 | 56 | if let key = key { 57 | mapper[key] = button.buttonStream() 58 | } 59 | } 60 | 61 | // manually map `.Clear` & `.AllClear` 62 | for clearKey in Calculator.Key.clearKeys() { 63 | mapper[clearKey] = self.clearButton.buttonStream { button -> Bool in button?.titleForState(.Normal) == clearKey.rawValue } 64 | |> filter { $0 == true } 65 | |> map { _ in () as Void } // asStream(Void) 66 | } 67 | } 68 | 69 | // REACT 70 | (self.textField, "text") <~ self.calculator!.outputStream 71 | (self.historyLabel, "text") <~ self.calculator!.expressionStream 72 | 73 | // REACT: toggle C <-> AC 74 | self.calculator!.inputStream ~> { [weak self] key in 75 | if Calculator.Key.numKeys().contains(key) { 76 | self?.clearButton?.setTitle("C", forState: .Normal) 77 | 78 | } 79 | else if key == .Clear { 80 | self?.clearButton?.setTitle("AC", forState: .Normal) 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/CalculatorViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 65 | 84 | 92 | 100 | 108 | 116 | 124 | 132 | 140 | 148 | 156 | 164 | 172 | 180 | 188 | 196 | 204 | 212 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Demo/ReactKitCalculatorDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ReactKit 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactKitCalculator 2 | 3 | Mimics iOS Calculator.app using ReactKit. 4 | 5 | 6 | 7 | 8 | ## Licence 9 | 10 | [MIT](https://github.com/ReactKit/ReactKitCalculator/blob/master/LICENSE) 11 | -------------------------------------------------------------------------------- /ReactKitCalculator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1F5C2EB51A98B7E200D2DBD6 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5C2EB41A98B7E200D2DBD6 /* Helper.swift */; }; 11 | 1F5C2EB61A98B7E200D2DBD6 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5C2EB41A98B7E200D2DBD6 /* Helper.swift */; }; 12 | 1F977EC71A94978B00A7D656 /* ReactKitCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F977EC61A94978B00A7D656 /* ReactKitCalculator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 1F9781B71A9498AD00A7D656 /* ReactKitCalculator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F9781931A94984600A7D656 /* ReactKitCalculator.framework */; }; 14 | 1F9781C21A949A1B00A7D656 /* Calculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9781C11A949A1B00A7D656 /* Calculator.swift */; }; 15 | 1F9781C31A949A1B00A7D656 /* Calculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9781C11A949A1B00A7D656 /* Calculator.swift */; }; 16 | 1F9781C71A949A3200A7D656 /* _Peripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9781C41A949A3200A7D656 /* _Peripheral.swift */; }; 17 | 1F9781C81A949A3200A7D656 /* ExpressionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9781C51A949A3200A7D656 /* ExpressionSpec.swift */; }; 18 | 1F9781C91A949A3200A7D656 /* OutputSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9781C61A949A3200A7D656 /* OutputSpec.swift */; }; 19 | 484F9D641AAD380900E1D0DA /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 484F9D631AAD380900E1D0DA /* Nimble.framework */; }; 20 | 48B3B3841AAD3888005312D0 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48B3B3831AAD3888005312D0 /* Quick.framework */; }; 21 | 48B3B3861AAD39C8005312D0 /* ReactKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48B3B3851AAD39C8005312D0 /* ReactKit.framework */; }; 22 | 48B3B3891AAD39E2005312D0 /* ReactKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48B3B3851AAD39C8005312D0 /* ReactKit.framework */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 1F9781B81A9498AD00A7D656 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 1F977EB81A94978B00A7D656 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 1F9781921A94984600A7D656; 31 | remoteInfo = "ReactKitCalculator-OSX"; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 1F5C2EB41A98B7E200D2DBD6 /* Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 37 | 1F977EC11A94978B00A7D656 /* ReactKitCalculator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactKitCalculator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 1F977EC51A94978B00A7D656 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 1F977EC61A94978B00A7D656 /* ReactKitCalculator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactKitCalculator.h; sourceTree = ""; }; 40 | 1F9781931A94984600A7D656 /* ReactKitCalculator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactKitCalculator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 1F9781B11A9498AD00A7D656 /* ReactKitCalculatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactKitCalculatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 1F9781B41A9498AD00A7D656 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 1F9781C11A949A1B00A7D656 /* Calculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Calculator.swift; sourceTree = ""; }; 44 | 1F9781C41A949A3200A7D656 /* _Peripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Peripheral.swift; sourceTree = ""; }; 45 | 1F9781C51A949A3200A7D656 /* ExpressionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionSpec.swift; sourceTree = ""; }; 46 | 1F9781C61A949A3200A7D656 /* OutputSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputSpec.swift; sourceTree = ""; }; 47 | 484F9D631AAD380900E1D0DA /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = ../Carthage/Checkouts/Nimble/build/Debug/Nimble.framework; sourceTree = ""; }; 48 | 48B3B3831AAD3888005312D0 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = ../Carthage/Checkouts/Quick/build/Debug/Quick.framework; sourceTree = ""; }; 49 | 48B3B3851AAD39C8005312D0 /* ReactKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactKit.framework; path = ../Carthage/Checkouts/ReactKit/build/Debug/ReactKit.framework; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 1F977EBD1A94978B00A7D656 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 48B3B3891AAD39E2005312D0 /* ReactKit.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | 1F97818F1A94984600A7D656 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 48B3B3861AAD39C8005312D0 /* ReactKit.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 1F9781AE1A9498AD00A7D656 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 48B3B3841AAD3888005312D0 /* Quick.framework in Frameworks */, 74 | 484F9D641AAD380900E1D0DA /* Nimble.framework in Frameworks */, 75 | 1F9781B71A9498AD00A7D656 /* ReactKitCalculator.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | 1F977EB71A94978B00A7D656 = { 83 | isa = PBXGroup; 84 | children = ( 85 | 1F977EC31A94978B00A7D656 /* ReactKitCalculator */, 86 | 1F9781B21A9498AD00A7D656 /* ReactKitCalculatorTests */, 87 | 1F977EC21A94978B00A7D656 /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 1F977EC21A94978B00A7D656 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 1F977EC11A94978B00A7D656 /* ReactKitCalculator.framework */, 95 | 1F9781931A94984600A7D656 /* ReactKitCalculator.framework */, 96 | 1F9781B11A9498AD00A7D656 /* ReactKitCalculatorTests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 1F977EC31A94978B00A7D656 /* ReactKitCalculator */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 1F977EC61A94978B00A7D656 /* ReactKitCalculator.h */, 105 | 1F9781C11A949A1B00A7D656 /* Calculator.swift */, 106 | 1F5C2EB41A98B7E200D2DBD6 /* Helper.swift */, 107 | 1F977EC41A94978B00A7D656 /* Supporting Files */, 108 | ); 109 | path = ReactKitCalculator; 110 | sourceTree = ""; 111 | }; 112 | 1F977EC41A94978B00A7D656 /* Supporting Files */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 48B3B3851AAD39C8005312D0 /* ReactKit.framework */, 116 | 1F977EC51A94978B00A7D656 /* Info.plist */, 117 | ); 118 | name = "Supporting Files"; 119 | sourceTree = ""; 120 | }; 121 | 1F9781B21A9498AD00A7D656 /* ReactKitCalculatorTests */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 1F9781C41A949A3200A7D656 /* _Peripheral.swift */, 125 | 1F9781C61A949A3200A7D656 /* OutputSpec.swift */, 126 | 1F9781C51A949A3200A7D656 /* ExpressionSpec.swift */, 127 | 1F9781B31A9498AD00A7D656 /* Supporting Files */, 128 | ); 129 | path = ReactKitCalculatorTests; 130 | sourceTree = ""; 131 | }; 132 | 1F9781B31A9498AD00A7D656 /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 48B3B3831AAD3888005312D0 /* Quick.framework */, 136 | 484F9D631AAD380900E1D0DA /* Nimble.framework */, 137 | 1F9781B41A9498AD00A7D656 /* Info.plist */, 138 | ); 139 | name = "Supporting Files"; 140 | sourceTree = ""; 141 | }; 142 | /* End PBXGroup section */ 143 | 144 | /* Begin PBXHeadersBuildPhase section */ 145 | 1F977EBE1A94978B00A7D656 /* Headers */ = { 146 | isa = PBXHeadersBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 1F977EC71A94978B00A7D656 /* ReactKitCalculator.h in Headers */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | 1F9781901A94984600A7D656 /* Headers */ = { 154 | isa = PBXHeadersBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXHeadersBuildPhase section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 1F977EC01A94978B00A7D656 /* ReactKitCalculator-iOS */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 1F977ED71A94978C00A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculator-iOS" */; 166 | buildPhases = ( 167 | 1F977EBC1A94978B00A7D656 /* Sources */, 168 | 1F977EBD1A94978B00A7D656 /* Frameworks */, 169 | 1F977EBE1A94978B00A7D656 /* Headers */, 170 | 1F977EBF1A94978B00A7D656 /* Resources */, 171 | ); 172 | buildRules = ( 173 | ); 174 | dependencies = ( 175 | ); 176 | name = "ReactKitCalculator-iOS"; 177 | productName = ReactKitCalculator; 178 | productReference = 1F977EC11A94978B00A7D656 /* ReactKitCalculator.framework */; 179 | productType = "com.apple.product-type.framework"; 180 | }; 181 | 1F9781921A94984600A7D656 /* ReactKitCalculator-OSX */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 1F9781A61A94984600A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculator-OSX" */; 184 | buildPhases = ( 185 | 1F97818E1A94984600A7D656 /* Sources */, 186 | 1F97818F1A94984600A7D656 /* Frameworks */, 187 | 1F9781901A94984600A7D656 /* Headers */, 188 | 1F9781911A94984600A7D656 /* Resources */, 189 | ); 190 | buildRules = ( 191 | ); 192 | dependencies = ( 193 | ); 194 | name = "ReactKitCalculator-OSX"; 195 | productName = "ReactKitCalculator-OSX"; 196 | productReference = 1F9781931A94984600A7D656 /* ReactKitCalculator.framework */; 197 | productType = "com.apple.product-type.framework"; 198 | }; 199 | 1F9781B01A9498AD00A7D656 /* ReactKitCalculatorTests */ = { 200 | isa = PBXNativeTarget; 201 | buildConfigurationList = 1F9781BA1A9498AD00A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculatorTests" */; 202 | buildPhases = ( 203 | 1F9781AD1A9498AD00A7D656 /* Sources */, 204 | 1F9781AE1A9498AD00A7D656 /* Frameworks */, 205 | 1F9781AF1A9498AD00A7D656 /* Resources */, 206 | ); 207 | buildRules = ( 208 | ); 209 | dependencies = ( 210 | 1F9781B91A9498AD00A7D656 /* PBXTargetDependency */, 211 | ); 212 | name = ReactKitCalculatorTests; 213 | productName = ReactKitCalculatorTests; 214 | productReference = 1F9781B11A9498AD00A7D656 /* ReactKitCalculatorTests.xctest */; 215 | productType = "com.apple.product-type.bundle.unit-test"; 216 | }; 217 | /* End PBXNativeTarget section */ 218 | 219 | /* Begin PBXProject section */ 220 | 1F977EB81A94978B00A7D656 /* Project object */ = { 221 | isa = PBXProject; 222 | attributes = { 223 | LastSwiftUpdateCheck = 0700; 224 | LastUpgradeCheck = 0700; 225 | ORGANIZATIONNAME = "Yasuhiro Inami"; 226 | TargetAttributes = { 227 | 1F977EC01A94978B00A7D656 = { 228 | CreatedOnToolsVersion = 6.1.1; 229 | }; 230 | 1F9781921A94984600A7D656 = { 231 | CreatedOnToolsVersion = 6.1.1; 232 | }; 233 | 1F9781B01A9498AD00A7D656 = { 234 | CreatedOnToolsVersion = 6.1.1; 235 | }; 236 | }; 237 | }; 238 | buildConfigurationList = 1F977EBB1A94978B00A7D656 /* Build configuration list for PBXProject "ReactKitCalculator" */; 239 | compatibilityVersion = "Xcode 3.2"; 240 | developmentRegion = English; 241 | hasScannedForEncodings = 0; 242 | knownRegions = ( 243 | en, 244 | Base, 245 | ); 246 | mainGroup = 1F977EB71A94978B00A7D656; 247 | productRefGroup = 1F977EC21A94978B00A7D656 /* Products */; 248 | projectDirPath = ""; 249 | projectRoot = ""; 250 | targets = ( 251 | 1F9781921A94984600A7D656 /* ReactKitCalculator-OSX */, 252 | 1F977EC01A94978B00A7D656 /* ReactKitCalculator-iOS */, 253 | 1F9781B01A9498AD00A7D656 /* ReactKitCalculatorTests */, 254 | ); 255 | }; 256 | /* End PBXProject section */ 257 | 258 | /* Begin PBXResourcesBuildPhase section */ 259 | 1F977EBF1A94978B00A7D656 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | 1F9781911A94984600A7D656 /* Resources */ = { 267 | isa = PBXResourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | 1F9781AF1A9498AD00A7D656 /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXResourcesBuildPhase section */ 281 | 282 | /* Begin PBXSourcesBuildPhase section */ 283 | 1F977EBC1A94978B00A7D656 /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 1F9781C31A949A1B00A7D656 /* Calculator.swift in Sources */, 288 | 1F5C2EB61A98B7E200D2DBD6 /* Helper.swift in Sources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | 1F97818E1A94984600A7D656 /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | 1F9781C21A949A1B00A7D656 /* Calculator.swift in Sources */, 297 | 1F5C2EB51A98B7E200D2DBD6 /* Helper.swift in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | 1F9781AD1A9498AD00A7D656 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | 1F9781C81A949A3200A7D656 /* ExpressionSpec.swift in Sources */, 306 | 1F9781C71A949A3200A7D656 /* _Peripheral.swift in Sources */, 307 | 1F9781C91A949A3200A7D656 /* OutputSpec.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXSourcesBuildPhase section */ 312 | 313 | /* Begin PBXTargetDependency section */ 314 | 1F9781B91A9498AD00A7D656 /* PBXTargetDependency */ = { 315 | isa = PBXTargetDependency; 316 | target = 1F9781921A94984600A7D656 /* ReactKitCalculator-OSX */; 317 | targetProxy = 1F9781B81A9498AD00A7D656 /* PBXContainerItemProxy */; 318 | }; 319 | /* End PBXTargetDependency section */ 320 | 321 | /* Begin XCBuildConfiguration section */ 322 | 1F977ED51A94978C00A7D656 /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ALWAYS_SEARCH_USER_PATHS = NO; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_UNREACHABLE_CODE = YES; 338 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 339 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 340 | COPY_PHASE_STRIP = NO; 341 | CURRENT_PROJECT_VERSION = 1; 342 | ENABLE_STRICT_OBJC_MSGSEND = YES; 343 | ENABLE_TESTABILITY = YES; 344 | GCC_C_LANGUAGE_STANDARD = gnu99; 345 | GCC_DYNAMIC_NO_PIC = NO; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PREPROCESSOR_DEFINITIONS = ( 348 | "DEBUG=1", 349 | "$(inherited)", 350 | ); 351 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 354 | GCC_WARN_UNDECLARED_SELECTOR = YES; 355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 356 | GCC_WARN_UNUSED_FUNCTION = YES; 357 | GCC_WARN_UNUSED_VARIABLE = YES; 358 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 359 | MTL_ENABLE_DEBUG_INFO = YES; 360 | ONLY_ACTIVE_ARCH = YES; 361 | SDKROOT = iphoneos; 362 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 363 | TARGETED_DEVICE_FAMILY = "1,2"; 364 | VERSIONING_SYSTEM = "apple-generic"; 365 | VERSION_INFO_PREFIX = ""; 366 | }; 367 | name = Debug; 368 | }; 369 | 1F977ED61A94978C00A7D656 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_SEARCH_USER_PATHS = NO; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_WARN_BOOL_CONVERSION = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_EMPTY_BODY = YES; 381 | CLANG_WARN_ENUM_CONVERSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 384 | CLANG_WARN_UNREACHABLE_CODE = YES; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 387 | COPY_PHASE_STRIP = YES; 388 | CURRENT_PROJECT_VERSION = 1; 389 | ENABLE_NS_ASSERTIONS = NO; 390 | ENABLE_STRICT_OBJC_MSGSEND = YES; 391 | GCC_C_LANGUAGE_STANDARD = gnu99; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | VALIDATE_PRODUCT = YES; 403 | VERSIONING_SYSTEM = "apple-generic"; 404 | VERSION_INFO_PREFIX = ""; 405 | }; 406 | name = Release; 407 | }; 408 | 1F977ED81A94978C00A7D656 /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | APPLICATION_EXTENSION_API_ONLY = YES; 412 | CLANG_ENABLE_MODULES = YES; 413 | DEFINES_MODULE = YES; 414 | DYLIB_COMPATIBILITY_VERSION = 1; 415 | DYLIB_CURRENT_VERSION = 1; 416 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 417 | INFOPLIST_FILE = ReactKitCalculator/Info.plist; 418 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 419 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 420 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 421 | PRODUCT_NAME = "$(PROJECT_NAME)"; 422 | SKIP_INSTALL = YES; 423 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 424 | }; 425 | name = Debug; 426 | }; 427 | 1F977ED91A94978C00A7D656 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | APPLICATION_EXTENSION_API_ONLY = YES; 431 | CLANG_ENABLE_MODULES = YES; 432 | DEFINES_MODULE = YES; 433 | DYLIB_COMPATIBILITY_VERSION = 1; 434 | DYLIB_CURRENT_VERSION = 1; 435 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 436 | INFOPLIST_FILE = ReactKitCalculator/Info.plist; 437 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 440 | PRODUCT_NAME = "$(PROJECT_NAME)"; 441 | SKIP_INSTALL = YES; 442 | }; 443 | name = Release; 444 | }; 445 | 1F9781A71A94984600A7D656 /* Debug */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | APPLICATION_EXTENSION_API_ONLY = YES; 449 | CLANG_ENABLE_MODULES = YES; 450 | COMBINE_HIDPI_IMAGES = YES; 451 | DEFINES_MODULE = YES; 452 | DYLIB_COMPATIBILITY_VERSION = 1; 453 | DYLIB_CURRENT_VERSION = 1; 454 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 455 | FRAMEWORK_VERSION = A; 456 | GCC_PREPROCESSOR_DEFINITIONS = ( 457 | "DEBUG=1", 458 | "$(inherited)", 459 | ); 460 | INFOPLIST_FILE = ReactKitCalculator/Info.plist; 461 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 463 | MACOSX_DEPLOYMENT_TARGET = 10.10; 464 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 465 | PRODUCT_NAME = "$(PROJECT_NAME)"; 466 | SDKROOT = macosx; 467 | SKIP_INSTALL = YES; 468 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 469 | }; 470 | name = Debug; 471 | }; 472 | 1F9781A81A94984600A7D656 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | APPLICATION_EXTENSION_API_ONLY = YES; 476 | CLANG_ENABLE_MODULES = YES; 477 | COMBINE_HIDPI_IMAGES = YES; 478 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 479 | DEFINES_MODULE = YES; 480 | DYLIB_COMPATIBILITY_VERSION = 1; 481 | DYLIB_CURRENT_VERSION = 1; 482 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 483 | FRAMEWORK_VERSION = A; 484 | INFOPLIST_FILE = ReactKitCalculator/Info.plist; 485 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 487 | MACOSX_DEPLOYMENT_TARGET = 10.10; 488 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 489 | PRODUCT_NAME = "$(PROJECT_NAME)"; 490 | SDKROOT = macosx; 491 | SKIP_INSTALL = YES; 492 | }; 493 | name = Release; 494 | }; 495 | 1F9781BB1A9498AD00A7D656 /* Debug */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | COMBINE_HIDPI_IMAGES = YES; 499 | FRAMEWORK_SEARCH_PATHS = ( 500 | "$(DEVELOPER_FRAMEWORKS_DIR)", 501 | "$(inherited)", 502 | ); 503 | GCC_PREPROCESSOR_DEFINITIONS = ( 504 | "DEBUG=1", 505 | "$(inherited)", 506 | ); 507 | INFOPLIST_FILE = ReactKitCalculatorTests/Info.plist; 508 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 509 | MACOSX_DEPLOYMENT_TARGET = 10.10; 510 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | SDKROOT = macosx; 513 | }; 514 | name = Debug; 515 | }; 516 | 1F9781BC1A9498AD00A7D656 /* Release */ = { 517 | isa = XCBuildConfiguration; 518 | buildSettings = { 519 | COMBINE_HIDPI_IMAGES = YES; 520 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 521 | FRAMEWORK_SEARCH_PATHS = ( 522 | "$(DEVELOPER_FRAMEWORKS_DIR)", 523 | "$(inherited)", 524 | ); 525 | INFOPLIST_FILE = ReactKitCalculatorTests/Info.plist; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 527 | MACOSX_DEPLOYMENT_TARGET = 10.10; 528 | PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SDKROOT = macosx; 531 | }; 532 | name = Release; 533 | }; 534 | /* End XCBuildConfiguration section */ 535 | 536 | /* Begin XCConfigurationList section */ 537 | 1F977EBB1A94978B00A7D656 /* Build configuration list for PBXProject "ReactKitCalculator" */ = { 538 | isa = XCConfigurationList; 539 | buildConfigurations = ( 540 | 1F977ED51A94978C00A7D656 /* Debug */, 541 | 1F977ED61A94978C00A7D656 /* Release */, 542 | ); 543 | defaultConfigurationIsVisible = 0; 544 | defaultConfigurationName = Release; 545 | }; 546 | 1F977ED71A94978C00A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculator-iOS" */ = { 547 | isa = XCConfigurationList; 548 | buildConfigurations = ( 549 | 1F977ED81A94978C00A7D656 /* Debug */, 550 | 1F977ED91A94978C00A7D656 /* Release */, 551 | ); 552 | defaultConfigurationIsVisible = 0; 553 | defaultConfigurationName = Release; 554 | }; 555 | 1F9781A61A94984600A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculator-OSX" */ = { 556 | isa = XCConfigurationList; 557 | buildConfigurations = ( 558 | 1F9781A71A94984600A7D656 /* Debug */, 559 | 1F9781A81A94984600A7D656 /* Release */, 560 | ); 561 | defaultConfigurationIsVisible = 0; 562 | defaultConfigurationName = Release; 563 | }; 564 | 1F9781BA1A9498AD00A7D656 /* Build configuration list for PBXNativeTarget "ReactKitCalculatorTests" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 1F9781BB1A9498AD00A7D656 /* Debug */, 568 | 1F9781BC1A9498AD00A7D656 /* Release */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | /* End XCConfigurationList section */ 574 | }; 575 | rootObject = 1F977EB81A94978B00A7D656 /* Project object */; 576 | } 577 | -------------------------------------------------------------------------------- /ReactKitCalculator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ReactKitCalculator.xcodeproj/xcshareddata/xcschemes/ReactKitCalculator-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ReactKitCalculator.xcodeproj/xcshareddata/xcschemes/ReactKitCalculator-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ReactKitCalculator.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 12 | 13 | 14 | 17 | 19 | 20 | 21 | 24 | 26 | 27 | 28 | 31 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ReactKitCalculator/Calculator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calculator.swift 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactKit 11 | 12 | /// mimics iOS's Calculator.app 13 | public class Calculator 14 | { 15 | public enum Key: String 16 | { 17 | case Num0 = "0", Num1 = "1", Num2 = "2", Num3 = "3", Num4 = "4", Num5 = "5", Num6 = "6", Num7 = "7", Num8 = "8", Num9 = "9" 18 | case Point = "." 19 | case PlusMinus = "±", Percent = "%" 20 | 21 | case Equal = "=" 22 | case Plus = "+", Minus = "-", Multiply = "*", Divide = "/" 23 | case Clear = "C", AllClear = "AC" 24 | 25 | public static func allKeys() -> [Key] 26 | { 27 | return self.numBuildKeys() + self.arithOperatorKeys() + self.clearKeys() + [.Equal] 28 | } 29 | 30 | public static func numKeys() -> [Key] 31 | { 32 | return [ .Num0, .Num1, .Num2, .Num3, .Num4, .Num5, .Num6, .Num7, .Num8, .Num9, .Point ] 33 | } 34 | 35 | public static func numBuildKeys() -> [Key] 36 | { 37 | return self.numKeys() + self.unaryOperators() 38 | } 39 | 40 | public static func unaryOperators() -> [Key] 41 | { 42 | return [ .PlusMinus, .Percent ] 43 | } 44 | 45 | /// arithmetic operator keys 46 | public static func arithOperatorKeys() -> [Key] 47 | { 48 | return [ .Plus, .Minus, .Multiply, .Divide ] 49 | } 50 | 51 | public static func clearKeys() -> [Key] 52 | { 53 | return [ .Clear, .AllClear ] 54 | } 55 | 56 | public var precedence: Int8 57 | { 58 | switch self { 59 | case .Multiply, .Divide: 60 | return 2 61 | case .Plus, .Minus: 62 | return 1 63 | default: 64 | return 0 65 | } 66 | } 67 | 68 | /// for arithmetic operation 69 | public func evaluate(a: Double)(_ b: Double) -> Double 70 | { 71 | switch self { 72 | case .Plus: 73 | return b + a 74 | case .Minus: 75 | return b - a 76 | case .Multiply: 77 | return b * a 78 | case .Divide: 79 | return b / a 80 | default: 81 | return a 82 | } 83 | } 84 | 85 | /// for unary operation 86 | public func evaluate(a: Double) -> Double 87 | { 88 | switch self { 89 | case .PlusMinus: 90 | return -a 91 | case .Percent: 92 | return a * 0.01 93 | default: 94 | return a 95 | } 96 | } 97 | } 98 | 99 | /// maps `Key` to `Stream`, then converts to `keyStreams: [Stream]` 100 | public class Mapper 101 | { 102 | private var _keyMap: [Key : Stream] = [:] 103 | 104 | public subscript(key: Key) -> Stream? 105 | { 106 | get { 107 | return self._keyMap[key] 108 | } 109 | set(newStream) { 110 | self._keyMap[key] = newStream 111 | } 112 | } 113 | 114 | internal func keyStreams() -> [Stream] 115 | { 116 | var keyStreams: [Stream] = [] 117 | for (key, stream) in self._keyMap { 118 | keyStreams.append(stream |> map { key }) 119 | } 120 | return keyStreams 121 | } 122 | } 123 | 124 | /// TODO: implement `bracketLevel` feature 125 | internal enum _Token: CustomStringConvertible, CustomDebugStringConvertible 126 | { 127 | typealias OperatorTuple = (key: Key, calculatedValue: Double, bracketLevel: Int) 128 | 129 | case Number(Double) 130 | case Operator(Key, calculatedValue: Double, bracketLevel: Int) 131 | 132 | var number: Double? 133 | { 134 | switch self { 135 | case .Number(let value): return value 136 | default: return nil 137 | } 138 | } 139 | 140 | var operatorTuple: OperatorTuple? 141 | { 142 | switch self { 143 | case .Operator(let tuple): return tuple 144 | default: return nil 145 | } 146 | } 147 | 148 | var operatorKey: Key? 149 | { 150 | return self.operatorTuple?.key ?? nil 151 | } 152 | 153 | var operatorCalculatedValue: Double? 154 | { 155 | return self.operatorTuple?.calculatedValue ?? nil 156 | } 157 | 158 | var operatorBracketLevel: Int? 159 | { 160 | return self.operatorTuple?.bracketLevel ?? nil 161 | } 162 | 163 | var description: String 164 | { 165 | switch self { 166 | case .Number(let value): 167 | return "`\(value)`" 168 | case .Operator(let tuple): 169 | return "`\(tuple.0.rawValue)`" 170 | } 171 | } 172 | 173 | var debugDescription: String 174 | { 175 | switch self { 176 | case .Number(let value): 177 | return "" 178 | case .Operator(let tuple): 179 | return "" 180 | } 181 | } 182 | } 183 | 184 | internal class _Buffer: CustomStringConvertible 185 | { 186 | var tokens: [_Token] = [] 187 | var lastAnswer: Double = 0 188 | var lastArithKey: Key = .Equal 189 | var lastArithValue: Double = 0 190 | 191 | var description: String 192 | { 193 | return "<_Buffer; tokens=\(tokens); lastAnswer=\(lastAnswer); lastArithKey=`\(lastArithKey.rawValue)`; lastArithValue=\(lastArithValue)>" 194 | } 195 | 196 | func addNumber(value: Double) 197 | { 198 | // remove last number-token if needed 199 | if self.tokens.last?.number != nil { 200 | self.tokens.removeLast() 201 | } 202 | 203 | self.tokens.append(_Token.Number(value)) 204 | 205 | // update lastArithValue if needed 206 | if self.tokens.count > 1 && Key.arithOperatorKeys().contains(self.lastArithKey) { 207 | self.lastArithValue = value 208 | } 209 | } 210 | 211 | func clear() 212 | { 213 | self.lastAnswer = 0 214 | } 215 | 216 | func allClear() 217 | { 218 | self.tokens.removeAll(keepCapacity: false) 219 | self.lastAnswer = 0 220 | self.lastArithKey = .Equal 221 | self.lastArithValue = 0 222 | } 223 | } 224 | 225 | /// a.k.a mergedKeyStream 226 | public internal(set) var inputStream: Stream! 227 | 228 | /// retro-calculator (single-lined & narrow display) output 229 | public internal(set) var outputStream: Stream! 230 | 231 | /// realtime buffering stream 232 | public internal(set) var expressionStream: Stream! 233 | 234 | private let mapper = Mapper() 235 | 236 | 237 | public init(initClosure: Mapper -> Void) 238 | { 239 | // pass `self.mapper` to collect keyStreams via `initClosure` 240 | initClosure(self.mapper) 241 | 242 | let mergedKeyStream = self.mapper.keyStreams() |> mergeAll 243 | 244 | /// 245 | /// sends digit-accumulated numString 246 | /// 247 | /// e.g. `1 2 + 3 + * 4 =` will send: 248 | /// 249 | /// - [t = 1] "1" 250 | /// - [t = 2] "12" 251 | /// - [t = 3] (none) 252 | /// - [t = 4] "3" 253 | /// - [t = 5] (none) 254 | /// - [t = 6] (none) 255 | /// - [t = 7] "4" 256 | /// - [t = 8] (none) 257 | /// 258 | let numBuildStream: Stream = 259 | mergedKeyStream 260 | |> mapAccumulate(nil) { (accumulatedString, newKey) -> String? in 261 | 262 | let acc = (accumulatedString ?? Key.Num0.rawValue) 263 | 264 | switch newKey { 265 | case .Point: 266 | if acc.rangeOfString(Key.Point.rawValue) != nil { 267 | return acc // don't add another `.Point` if already exists 268 | } 269 | else { 270 | return acc + newKey.rawValue 271 | } 272 | 273 | // numKey except `.Point` (NOTE: `case .Point` declared above) 274 | case let numKey where Key.numKeys().contains(numKey): 275 | if acc == Key.Minus.rawValue + Key.Num0.rawValue { 276 | return Key.Minus.rawValue + newKey.rawValue // e.g. "-0" then "1" will be "-1" 277 | } 278 | else if acc == Key.Num0.rawValue { 279 | return newKey.rawValue // e.g. "0" then "1" will be "1" 280 | } 281 | else { 282 | return (accumulatedString ?? "") + newKey.rawValue // e.g. "12" then "3" will be "123" 283 | } 284 | 285 | case .PlusMinus: 286 | // comment-out: iOS Calculator.app evaluates `.PlusMinus` with string-based, not double-based (especially important when `.Point` is suffixed) 287 | //return newKey.evaluate(acc.doubleValue).calculatorString 288 | 289 | // string-based toggling of prefixed "-" 290 | if acc.hasPrefix(Key.Minus.rawValue) { 291 | return acc.substringFromIndex(acc.startIndex.advancedBy(1)) 292 | } 293 | else { 294 | return Key.Minus.rawValue + acc 295 | } 296 | 297 | // NOTE: this unaryKey will not contain `.PlusMinus` as `case .PlusMinus` is declared above 298 | case let unaryKey where Key.unaryOperators().contains(unaryKey): 299 | return newKey.evaluate((acc as NSString).doubleValue).calculatorString 300 | 301 | // comment-out: don't send "0" because it will confuse with `Key.Num0` input 302 | // case .Clear: 303 | // return Key.Num0.rawValue // clear to 0 304 | 305 | default: 306 | // clear previous accumulatedString 307 | // (NOTE: don't send "" which will cause forthcoming stream-operations to convert to 0.0 via `str.doubleValue`) 308 | return nil 309 | } 310 | } 311 | |> filter { $0 != nil } 312 | |> peek { print("numBuildStream ---> \($0)") } 313 | 314 | let numTokenStream: Stream<_Token> = 315 | numBuildStream 316 | |> map { _Token.Number(($0! as NSString).doubleValue) } 317 | 318 | let operatorKeyTokenStream: Stream<_Token> = 319 | mergedKeyStream 320 | |> filter { !Key.numBuildKeys().contains($0) } 321 | |> map { _Token.Operator($0, calculatedValue: 0, bracketLevel: 0) } 322 | 323 | /// numTokenStream + operatorKeyTokenStream 324 | let tokenStream: Stream<_Token> = 325 | numTokenStream 326 | |> merge(operatorKeyTokenStream) 327 | |> peek { print(""); print("tokenStream ---> \($0)") } 328 | 329 | /// 330 | /// Quite complex stream-operation using `customize()` to encapsulate `buffer` 331 | /// and send its `buffer.tokens`. 332 | /// 333 | /// (TODO: break down this mess into smaller fundamental operations) 334 | /// 335 | let bufferingTokensStream: Stream<[_Token]> = 336 | tokenStream 337 | |> customize { upstream, progress, fulfill, reject in 338 | 339 | let _b = _Buffer() // buffer 340 | 341 | upstream.react { (newToken: _Token) in 342 | 343 | print("[progress] newToken = \(newToken)") 344 | print("[progress] buffer = \(_b)") 345 | 346 | assert(_b.tokens.find { $0.operatorKey != nil && $0.operatorKey! == Key.Equal } == nil, "`buffer.tokens` should not contain `.Equal`.") 347 | 348 | switch newToken { 349 | 350 | case .Number(let newValue): 351 | _b.addNumber(newValue) 352 | 353 | // send stream value 354 | progress(_b.tokens) 355 | 356 | case .Operator(let newOperatorKey, _, _): 357 | 358 | switch newOperatorKey { 359 | 360 | case .Clear: 361 | _b.clear() 362 | _b.addNumber((Key.Num0.rawValue as NSString).doubleValue) 363 | 364 | case .AllClear: 365 | _b.allClear() 366 | 367 | default: 368 | // use lastAnswer if `_b.tokens` are empty 369 | if _b.tokens.count == 0 { 370 | _b.tokens.append(_Token.Number(_b.lastAnswer)) 371 | } 372 | 373 | // use `_b.lastArithKey` & `_b.lastArithValue` e.g. `2 + 3 = 4 =` will print `7` 374 | if _b.tokens.count == 1 && newOperatorKey == .Equal && Key.arithOperatorKeys().contains(_b.lastArithKey) { 375 | let lastNumber = _b.tokens.last!.number! 376 | _b.tokens.append(_Token.Operator(_b.lastArithKey, calculatedValue: lastNumber, bracketLevel: 0)) 377 | _b.tokens.append(_Token.Number(_b.lastArithValue)) 378 | } 379 | 380 | // append/remove token if consecutive operatorKeys 381 | if let lastOperatorCalculatedValue = _b.tokens.last?.operatorCalculatedValue { 382 | if newOperatorKey == .Equal { // force-equal: e.g. `+` then `=` 383 | // fill lastOperatorCalculatedValue (displaying value) before Equal-calculation 384 | _b.tokens.append(_Token.Number(lastOperatorCalculatedValue)) 385 | 386 | _b.lastArithValue = lastOperatorCalculatedValue // update lastArithValue 387 | } 388 | else { // operator change: e.g. `+` then `*` 389 | _b.tokens.removeLast() 390 | } 391 | } 392 | 393 | print("prepared tokens = \(_b.tokens)") 394 | 395 | assert(_b.tokens.last?.number != nil, "`buffer.tokens.last` should have number.") 396 | let lastNumber = _b.tokens.last!.number! 397 | 398 | // 399 | // append new operatorKey-token 400 | // (TODO: consider operatorBracketLevel) 401 | // 402 | let prevOperatorToken = _b.tokens.reverse().find { 403 | $0.operatorKey != nil && $0.operatorBracketLevel == newToken.operatorBracketLevel 404 | } 405 | print("prevOperatorToken = \(prevOperatorToken)") 406 | 407 | if let prevOperatorKey = prevOperatorToken?.operatorKey { 408 | 409 | // if moving to higher precedence, e.g. `3 + 4 *` 410 | if newOperatorKey.precedence > prevOperatorKey.precedence { 411 | let token = _Token.Operator(newOperatorKey, calculatedValue: lastNumber, bracketLevel: 0) 412 | _b.tokens.append(token) 413 | } 414 | else { 415 | // calculate `calculatedValue` 416 | 417 | var calculatedValue: Double = lastNumber 418 | var maxPrecedence = Int8.max 419 | 420 | // look for past operators and precalculate if possible 421 | for pastToken in _b.tokens.reverse() { 422 | if let pastOperatorTuple = pastToken.operatorTuple { 423 | 424 | let pastOperatorPrecedence = pastOperatorTuple.key.precedence 425 | if pastOperatorPrecedence < newOperatorKey.precedence || pastOperatorPrecedence <= Key.Equal.precedence { 426 | break // stop: no need to precalculate for lower precedence 427 | } 428 | 429 | if pastOperatorPrecedence < maxPrecedence { 430 | let beforeCalculatedValue = calculatedValue 431 | calculatedValue = pastOperatorTuple.key.evaluate(calculatedValue)(pastOperatorTuple.calculatedValue) 432 | print("[precalculate] \(beforeCalculatedValue) -> (\(pastOperatorTuple.key.rawValue), \(pastOperatorTuple.calculatedValue)) -> \(calculatedValue)") 433 | maxPrecedence = pastOperatorPrecedence 434 | } 435 | } 436 | } 437 | 438 | // append new operatorKey 439 | _b.tokens.append(_Token.Operator(newOperatorKey, calculatedValue: calculatedValue, bracketLevel: 0)) 440 | } 441 | 442 | } 443 | // if no prevOperatorKey 444 | else { 445 | // append new operatorKey 446 | _b.tokens.append(_Token.Operator(newOperatorKey, calculatedValue: lastNumber, bracketLevel: 0)) 447 | } 448 | 449 | // update lastArithKey 450 | if Key.arithOperatorKeys().contains(newOperatorKey) { 451 | _b.lastArithKey = newOperatorKey 452 | } 453 | 454 | break // default 455 | 456 | } // switch newOperatorKey 457 | 458 | // send stream value 459 | progress(_b.tokens) 460 | 461 | if newOperatorKey == .Equal { 462 | if let answer = _b.tokens.last?.operatorCalculatedValue { 463 | _b.lastAnswer = answer 464 | } 465 | 466 | _b.tokens.removeAll(keepCapacity: false) 467 | } 468 | 469 | break // case .Operator: 470 | 471 | } // switch newToken 472 | } 473 | } 474 | |> peek { print("bufferingTokensStream ---> \($0)") } 475 | 476 | let precalculatingStream: Stream = 477 | bufferingTokensStream 478 | |> map { ($0.last?.operatorCalculatedValue ?? 0).calculatorString } 479 | |> peek { print("precalculatingStream ---> \($0)") } 480 | 481 | self.inputStream = mergedKeyStream 482 | 483 | self.outputStream = 484 | numBuildStream 485 | |> map { _calculatorString(raw: $0!, rtrims: false) } // output `calculatorString` to show commas & exponent, and also suffixed `.Point`+`.Num0`s if needed 486 | |> merge(precalculatingStream) 487 | |> peek { print("outputStream ---> \($0)") } 488 | 489 | self.expressionStream = 490 | bufferingTokensStream 491 | |> map { (tokens: [_Token]) in 492 | // NOTE: explicitly declare `acc` & `token` type, or Swift compiler will take too much time for compiling 493 | return tokens.reduce("") { (acc: String, token: _Token) -> String in 494 | return acc + (token.number?.calculatorString ?? token.operatorKey?.rawValue ?? "") + " " 495 | } 496 | } 497 | |> peek { print("expressionStream ---> \($0)") } 498 | } 499 | 500 | deinit 501 | { 502 | print("[deinit] \(self)") 503 | } 504 | } -------------------------------------------------------------------------------- /ReactKitCalculator/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/21. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactKit 11 | 12 | let MAX_DIGIT_FOR_NONEXPONENT = 9 13 | let MIN_EXPONENT = 9 14 | let SIGNIFICAND_DIGIT = 7 15 | let COMMA_SEPARATOR = "," 16 | 17 | typealias ScientificNotation = (significand: Double, exponent: Int) 18 | 19 | func _scientificNotation(num: Double) -> ScientificNotation 20 | { 21 | var exponent: Int = 0 22 | 23 | var orderValue: Double = 1.0 24 | let absSelf = abs(num) 25 | if absSelf > 1 { 26 | while absSelf >= orderValue * 10 { 27 | exponent++ 28 | orderValue *= 10 29 | } 30 | } 31 | else if absSelf > 0 { 32 | while absSelf < orderValue { 33 | exponent-- 34 | orderValue *= 0.1 35 | } 36 | } 37 | 38 | var significand = num * pow(10.0, Double(-exponent)) 39 | 40 | // 41 | // NOTE: 42 | // Due to rounding of floating-point, 43 | // accumulating `exponent` might fail at this point (`significand` becomes `10.0`), 44 | // so adjust them if needed. 45 | // 46 | if significand == 10.0 { 47 | significand = 1.0 48 | exponent++ 49 | } 50 | 51 | // print("_scientificNotation(\(num)) = \((significand, exponent))") 52 | return (significand, exponent) 53 | } 54 | 55 | /// 56 | /// add either expontent or readable-commas to numString as follows: 57 | /// 58 | /// - "123" -> "123" (no change) 59 | /// - "12345.6700" -> "12,345.67" (rtrims=true) or "12,345.6700" (rtrims=false) 60 | /// - "123456789" -> "1.234567e+8" 61 | /// - inf -> "inf" 62 | /// - NaN -> "nan" 63 | /// 64 | func _calculatorString(num: Double? = nil, raw numString: String? = nil, rtrims rtrimsSuffixedPointAndZeros: Bool = true) -> String 65 | { 66 | precondition(num != nil || numString != nil, "Either `num` or `numString` must be non-nil.") 67 | 68 | let num = num ?? (numString! as NSString).doubleValue 69 | 70 | // return "inf" or "nan" if needed 71 | if !num.isFinite { return "\(num)" } 72 | 73 | let (significand, exponent) = _scientificNotation(num) 74 | 75 | let shouldShowNegativeExponent = (num > -1 && num < 1 && exponent <= -MIN_EXPONENT) 76 | let shouldShowPositiveExponent = ((num > 1 || num < -1) && exponent >= MIN_EXPONENT) 77 | 78 | var string: String 79 | 80 | // add exponent, e.g. 1.2345678e+9 81 | if shouldShowPositiveExponent || shouldShowNegativeExponent { 82 | 83 | print("") 84 | print("*** calculatorString ***") 85 | print("num = \(num)") 86 | print("significand = \(significand)") 87 | print("exponent = \(exponent)") 88 | 89 | if significand == Double.infinity { 90 | string = "\(significand)" 91 | } 92 | else { 93 | string = _rtrimFloatString(significand.calculatorString) 94 | 95 | if string.characters.count > SIGNIFICAND_DIGIT + 1 { // +1 for `.Point` 96 | string = string.substringToIndex(string.startIndex.advancedBy(SIGNIFICAND_DIGIT + 1)) 97 | } 98 | 99 | // append exponent 100 | if shouldShowPositiveExponent { 101 | // e.g. `1e+9` 102 | string = string + "e\(Calculator.Key.Plus.rawValue)\(exponent)" 103 | } 104 | else { 105 | // e.g. `1e-9` 106 | string = string + "e\(Calculator.Key.Minus.rawValue)\(-exponent)" 107 | } 108 | } 109 | 110 | } 111 | // add commas for integer-part 112 | else { 113 | let numString = numString ?? (NSString(format: "%0.\(MIN_EXPONENT)f", num) as String) 114 | string = _commaString(numString) 115 | 116 | if rtrimsSuffixedPointAndZeros { 117 | string = _rtrimFloatString(string) 118 | } 119 | } 120 | 121 | return string 122 | } 123 | 124 | /// e.g. "12345.67000" -> "12,345.67000" (used for number with non-exponent only) 125 | func _commaString(var string: String) -> String 126 | { 127 | // split by `.Point` 128 | let splittedStrings = string.componentsSeparatedByString(Calculator.Key.Point.rawValue) 129 | if let integerString = splittedStrings.first { 130 | 131 | var integerCharacters = Array(integerString.characters) 132 | let isNegative = integerString.hasPrefix(Calculator.Key.Minus.rawValue) 133 | 134 | // insert commas 135 | for var i = integerCharacters.count - 3; i > (isNegative ? 1 : 0); i -= 3 { 136 | integerCharacters.insert(Character(COMMA_SEPARATOR), atIndex: i) 137 | } 138 | 139 | string = String(integerCharacters) 140 | 141 | if splittedStrings.count == 2 { 142 | // append `.Point` & decimal-part 143 | string = string + Calculator.Key.Point.rawValue + splittedStrings.last! 144 | } 145 | } 146 | 147 | var nonNumberCount = 0 148 | for char in string.characters { 149 | if char == Character(COMMA_SEPARATOR) || char == Character(Calculator.Key.Point.rawValue) { 150 | nonNumberCount++ 151 | } 152 | } 153 | 154 | // limit to MAX_DIGIT_FOR_NONEXPONENT considering non-number characters 155 | if string.characters.count > MAX_DIGIT_FOR_NONEXPONENT + nonNumberCount { 156 | string = string.substringToIndex(string.startIndex.advancedBy(MAX_DIGIT_FOR_NONEXPONENT + nonNumberCount)) 157 | } 158 | 159 | return string 160 | } 161 | 162 | /// e.g. "123.000" -> "123" 163 | func _rtrimFloatString(var string: String) -> String 164 | { 165 | // trim floating zeros, e.g. `123.000` -> `123.` 166 | if string.rangeOfString(Calculator.Key.Point.rawValue) != nil { 167 | while string.hasSuffix(Calculator.Key.Num0.rawValue) { 168 | string = string.substringToIndex(string.startIndex.advancedBy(string.characters.count-1)) 169 | } 170 | } 171 | 172 | // trim suffix `.Point` if needed, e.g. `123.` -> `123` 173 | if string.hasSuffix(Calculator.Key.Point.rawValue) { 174 | string = string.substringToIndex(string.startIndex.advancedBy(string.characters.count-1)) 175 | } 176 | 177 | return string 178 | } 179 | 180 | extension Double 181 | { 182 | var calculatorString: String 183 | { 184 | return _calculatorString(self, rtrims: true) 185 | } 186 | } 187 | 188 | extension Array 189 | { 190 | func find(findClosure: Element -> Bool) -> Element? 191 | { 192 | for element in self { 193 | if findClosure(element) { 194 | return element 195 | } 196 | } 197 | return nil 198 | } 199 | } -------------------------------------------------------------------------------- /ReactKitCalculator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ReactKitCalculator/ReactKitCalculator.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReactKitCalculator.h 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ReactKitCalculator. 12 | FOUNDATION_EXPORT double ReactKitCalculatorVersionNumber; 13 | 14 | //! Project version string for ReactKitCalculator. 15 | FOUNDATION_EXPORT const unsigned char ReactKitCalculatorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ReactKitCalculatorTests/ExpressionSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpressionSpec.swift 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import ReactKitCalculator 10 | import ReactKit 11 | import Quick 12 | import Nimble 13 | 14 | class ExpressionSpec: QuickSpec 15 | { 16 | override func spec() 17 | { 18 | typealias Key = Calculator.Key 19 | 20 | var p: _Peripheral! 21 | var calculator: Calculator! 22 | 23 | beforeEach { 24 | p = _Peripheral() 25 | 26 | calculator = Calculator { mapper in 27 | let stream = KVO.stream(p, "input") 28 | |> map { $0 as? String } // asStream(String?) 29 | 30 | for key in Calculator.Key.allKeys() { 31 | mapper[key] = stream 32 | |> filter { $0 == key.rawValue } 33 | |> map { _ -> Void in } // asStream(Void) 34 | } 35 | } 36 | 37 | #if true 38 | // REACT (debug print) 39 | calculator.inputStream ~> { key in 40 | print("") 41 | print("***************************") 42 | print("pressed key = `\(key.rawValue)`") 43 | } 44 | 45 | // REACT (debug print) 46 | calculator.outputStream ~> { output in 47 | let output = output ?? "(nil)"; 48 | print("output = `\(output)`") 49 | print("") 50 | } 51 | 52 | // REACT (debug print) 53 | calculator.expressionStream ~> { expression in 54 | let expression = expression ?? "(nil)"; 55 | print("expression = `\(expression)`") 56 | print("") 57 | } 58 | #endif 59 | 60 | // REACT (set after debug print) 61 | (p, "expression") <~ calculator.expressionStream 62 | } 63 | 64 | describe("accumulate digits") { 65 | 66 | context("digits") { 67 | 68 | it("`1 2 3` should print `123`") { 69 | 70 | p.input = "1" 71 | expect(p.expression!).to(equal("1 ")) 72 | 73 | p.input = "2" 74 | expect(p.expression!).to(equal("12 ")) 75 | 76 | p.input = "3" 77 | expect(p.expression!).to(equal("123 ")) 78 | 79 | } 80 | 81 | } 82 | 83 | context("decimal point") { 84 | 85 | it("`1 . 2 3` should print `1.23`") { 86 | 87 | p.input = "1" 88 | expect(p.expression!).to(equal("1 ")) 89 | 90 | p.input = "." 91 | expect(p.expression!).to(equal("1 ")) // NOTE: outputStream sends "1." 92 | 93 | p.input = "2" 94 | expect(p.expression!).to(equal("1.2 ")) 95 | 96 | p.input = "3" 97 | expect(p.expression!).to(equal("1.23 ")) 98 | 99 | } 100 | 101 | it("`. 2 3` should print `0.23`") { 102 | 103 | p.input = "." 104 | expect(p.expression).to(equal("0 ")) // NOTE: outputStream sends "0." 105 | 106 | p.input = "2" 107 | expect(p.expression!).to(equal("0.2 ")) 108 | 109 | p.input = "3" 110 | expect(p.expression!).to(equal("0.23 ")) 111 | 112 | } 113 | 114 | } 115 | } 116 | 117 | describe(".Equal") { 118 | 119 | it("`= =` should print `0 = ` then `0 = `") { 120 | 121 | p.input = "=" 122 | expect(p.expression!).to(equal("0 = ")) 123 | 124 | p.input = "=" 125 | expect(p.expression!).to(equal("0 = ")) 126 | 127 | } 128 | 129 | it("`1 = =` should print `1 = ` then `1 = `") { 130 | 131 | p.input = "1" 132 | expect(p.expression!).to(equal("1 ")) 133 | 134 | p.input = "=" 135 | expect(p.expression!).to(equal("1 = ")) 136 | 137 | p.input = "=" 138 | expect(p.expression!).to(equal("1 = ")) 139 | 140 | } 141 | } 142 | 143 | describe(".Clear") { 144 | 145 | it("`2 + C = =`") { 146 | 147 | p.input = "2" 148 | expect(p.expression!).to(equal("2 ")) 149 | 150 | p.input = "+" 151 | expect(p.expression!).to(equal("2 + ")) 152 | 153 | p.input = "C" 154 | expect(p.expression!).to(equal("2 + 0 ")) // 0 is set 155 | 156 | p.input = "=" 157 | expect(p.expression!).to(equal("2 + 0 = ")) 158 | 159 | p.input = "=" 160 | expect(p.expression!).to(equal("2 + 0 = ")) 161 | 162 | } 163 | 164 | it("`2 + 3 C = =`") { 165 | 166 | p.input = "2" 167 | expect(p.expression!).to(equal("2 ")) 168 | 169 | p.input = "+" 170 | expect(p.expression!).to(equal("2 + ")) 171 | 172 | p.input = "3" 173 | expect(p.expression!).to(equal("2 + 3 ")) 174 | 175 | p.input = "C" 176 | expect(p.expression!).to(equal("2 + 0 ")) // clear current only 177 | 178 | p.input = "=" 179 | expect(p.expression!).to(equal("2 + 0 = ")) 180 | 181 | p.input = "=" 182 | expect(p.expression!).to(equal("2 + 0 = ")) 183 | 184 | } 185 | 186 | it("`2 + 3 C 4 = =`") { 187 | 188 | p.input = "2" 189 | expect(p.expression!).to(equal("2 ")) 190 | 191 | p.input = "+" 192 | expect(p.expression!).to(equal("2 + ")) 193 | 194 | p.input = "3" 195 | expect(p.expression!).to(equal("2 + 3 ")) 196 | 197 | p.input = "C" 198 | expect(p.expression!).to(equal("2 + 0 ")) // clear current only 199 | 200 | p.input = "4" 201 | expect(p.expression!).to(equal("2 + 4 ")) 202 | 203 | p.input = "=" 204 | expect(p.expression!).to(equal("2 + 4 = ")) 205 | 206 | p.input = "=" 207 | expect(p.expression!).to(equal("6 + 4 = ")) 208 | 209 | } 210 | 211 | it("`2 + 3 = C = =`") { 212 | 213 | p.input = "2" 214 | expect(p.expression!).to(equal("2 ")) 215 | 216 | p.input = "+" 217 | expect(p.expression!).to(equal("2 + ")) 218 | 219 | p.input = "3" 220 | expect(p.expression!).to(equal("2 + 3 ")) 221 | 222 | p.input = "=" 223 | expect(p.expression!).to(equal("2 + 3 = ")) // last operation: +3 224 | 225 | p.input = "C" 226 | expect(p.expression!).to(equal("0 ")) 227 | 228 | p.input = "=" 229 | expect(p.expression!).to(equal("0 + 3 = ")) 230 | 231 | p.input = "=" 232 | expect(p.expression!).to(equal("3 + 3 = ")) 233 | 234 | } 235 | 236 | } 237 | 238 | } 239 | } -------------------------------------------------------------------------------- /ReactKitCalculatorTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ReactKitCalculatorTests/OutputSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutputSpec.swift 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import ReactKitCalculator 10 | import ReactKit 11 | import Quick 12 | import Nimble 13 | 14 | class OutputSpec: QuickSpec 15 | { 16 | override func spec() 17 | { 18 | typealias Key = Calculator.Key 19 | 20 | var p: _Peripheral! 21 | var calculator: Calculator! 22 | 23 | beforeEach { 24 | p = _Peripheral() 25 | 26 | calculator = Calculator { mapper in 27 | let stream = KVO.stream(p, "input") 28 | |> map { $0 as? String } // asStream(String?) 29 | 30 | for key in Calculator.Key.allKeys() { 31 | mapper[key] = stream 32 | |> filter { $0 == key.rawValue } 33 | |> map { _ -> Void in } // asStream(Void) 34 | } 35 | } 36 | 37 | #if true 38 | // REACT (debug print) 39 | calculator.inputStream ~> { key in 40 | print("") 41 | print("***************************") 42 | print("pressed key = `\(key.rawValue)`") 43 | } 44 | 45 | // REACT (debug print) 46 | calculator.outputStream ~> { output in 47 | let output = output ?? "(nil)"; 48 | print("output = `\(output)`") 49 | print("") 50 | } 51 | #endif 52 | 53 | // REACT (set after debug print) 54 | (p, "output") <~ calculator.outputStream 55 | } 56 | 57 | describe("accumulate number") { 58 | 59 | context("digits") { 60 | 61 | it("`1 2 3` should accumulate 3-digits") { 62 | 63 | p.input = "1" 64 | expect(p.output!).to(equal("1")) 65 | 66 | p.input = "2" 67 | expect(p.output!).to(equal("12")) 68 | 69 | p.input = "3" 70 | expect(p.output!).to(equal("123")) 71 | 72 | } 73 | 74 | } 75 | 76 | context("decimal point") { 77 | 78 | it("`1 . 2 3` should print `1.23`") { 79 | 80 | p.input = "1" 81 | expect(p.output!).to(equal("1")) 82 | 83 | p.input = "." 84 | expect(p.output!).to(equal("1.")) // not "1" 85 | 86 | p.input = "2" 87 | expect(p.output!).to(equal("1.2")) 88 | 89 | p.input = "3" 90 | expect(p.output!).to(equal("1.23")) 91 | 92 | } 93 | 94 | it("`. 2 3` should print `0.23`") { 95 | 96 | p.input = "." 97 | expect(p.output!).to(equal("0.")) // "0" should be prepended 98 | 99 | p.input = "2" 100 | expect(p.output!).to(equal("0.2")) 101 | 102 | p.input = "3" 103 | expect(p.output!).to(equal("0.23")) 104 | 105 | } 106 | 107 | } 108 | 109 | context("decimal point + zero") { 110 | 111 | it("`0 . 0` should print `0.0`") { 112 | 113 | p.input = "0" 114 | expect(p.output!).to(equal("0")) 115 | 116 | p.input = "." 117 | expect(p.output!).to(equal("0.")) 118 | 119 | p.input = "0" 120 | expect(p.output!).to(equal("0.0")) 121 | 122 | } 123 | 124 | it("`. 0` should print `0.0`") { 125 | 126 | p.input = "." 127 | expect(p.output!).to(equal("0.")) // "0" should be prepended 128 | 129 | p.input = "0" 130 | expect(p.output!).to(equal("0.0")) 131 | 132 | } 133 | 134 | } 135 | 136 | context(".PlusMinus") { 137 | 138 | it("`±` should print `-0`") { 139 | 140 | p.input = "±" 141 | expect(p.output!).to(equal("-0")) 142 | 143 | } 144 | 145 | it("`± 0` should print `-0`") { 146 | 147 | p.input = "±" 148 | expect(p.output!).to(equal("-0")) 149 | 150 | p.input = "0" 151 | expect(p.output!).to(equal("-0")) 152 | 153 | } 154 | 155 | it("`± 0 1` should print `-1`") { 156 | 157 | p.input = "±" 158 | expect(p.output!).to(equal("-0")) 159 | 160 | p.input = "0" 161 | expect(p.output!).to(equal("-0")) 162 | 163 | p.input = "1" 164 | expect(p.output!).to(equal("-1")) 165 | 166 | } 167 | 168 | it("`1 ±` should print `-1`") { 169 | 170 | p.input = "1" 171 | expect(p.output!).to(equal("1")) 172 | 173 | p.input = "±" 174 | expect(p.output!).to(equal("-1")) 175 | 176 | } 177 | 178 | it("`1 . ±` should print `-1.`") { 179 | 180 | p.input = "1" 181 | expect(p.output!).to(equal("1")) 182 | 183 | p.input = "." 184 | expect(p.output!).to(equal("1.")) 185 | 186 | p.input = "±" 187 | expect(p.output!).to(equal("-1.")) 188 | 189 | } 190 | 191 | it("`1 . ±` should print `-1.`") { 192 | 193 | p.input = "1" 194 | expect(p.output!).to(equal("1")) 195 | 196 | p.input = "." 197 | expect(p.output!).to(equal("1.")) 198 | 199 | p.input = "±" 200 | expect(p.output!).to(equal("-1.")) 201 | 202 | } 203 | 204 | it("`1 1 1 ±` should print `-111`") { 205 | 206 | p.input = "1" 207 | expect(p.output!).to(equal("1")) 208 | 209 | p.input = "1" 210 | expect(p.output!).to(equal("11")) 211 | 212 | p.input = "1" 213 | expect(p.output!).to(equal("111")) 214 | 215 | p.input = "±" 216 | expect(p.output!).to(equal("-111")) 217 | 218 | } 219 | 220 | } 221 | } 222 | 223 | describe("simple operation") { 224 | 225 | it("`2 + 3 = =` should print `5` then `8`") { 226 | 227 | p.input = "2" 228 | expect(p.output!).to(equal("2")) 229 | 230 | p.input = "+" 231 | expect(p.output!).to(equal("2")) 232 | 233 | p.input = "3" 234 | expect(p.output!).to(equal("3")) 235 | 236 | p.input = "=" 237 | expect(p.output!).to(equal("5")) 238 | 239 | p.input = "=" 240 | expect(p.output!).to(equal("8")) 241 | 242 | } 243 | 244 | it("`2 - 3 = =` should print `-1` then `-4`") { 245 | 246 | p.input = "2" 247 | expect(p.output!).to(equal("2")) 248 | 249 | p.input = "-" 250 | expect(p.output!).to(equal("2")) 251 | 252 | p.input = "3" 253 | expect(p.output!).to(equal("3")) 254 | 255 | p.input = "=" 256 | expect(p.output!).to(equal("-1")) 257 | 258 | p.input = "=" 259 | expect(p.output!).to(equal("-4")) 260 | 261 | } 262 | 263 | it("`2 * 3 = =` should print `6` then `18`") { 264 | 265 | p.input = "2" 266 | expect(p.output!).to(equal("2")) 267 | 268 | p.input = "*" 269 | expect(p.output!).to(equal("2")) 270 | 271 | p.input = "3" 272 | expect(p.output!).to(equal("3")) 273 | 274 | p.input = "=" 275 | expect(p.output!).to(equal("6")) 276 | 277 | p.input = "=" 278 | expect(p.output!).to(equal("18")) 279 | 280 | } 281 | 282 | it("`2 / 3 = =` should print `0.666...` then `0.222...`") { 283 | 284 | p.input = "2" 285 | expect(p.output!).to(equal("2")) 286 | 287 | p.input = "/" 288 | expect(p.output!).to(equal("2")) 289 | 290 | p.input = "3" 291 | expect(p.output!).to(equal("3")) 292 | 293 | p.input = "=" 294 | expect(p.output!).to(contain("0.666")) //.to(beginWith("0.666")) 295 | 296 | p.input = "=" 297 | expect(p.output!).to(contain("0.222")) //.to(beginWith("0.222")) 298 | 299 | } 300 | } 301 | 302 | describe("multiple operations") { 303 | 304 | context("same operators") { 305 | 306 | it("`2 + 3 + 4 = =` should print `9` then `13`, with printing result of `2 + 3` in midway") { 307 | 308 | p.input = "2" 309 | expect(p.output!).to(equal("2")) 310 | 311 | p.input = "+" 312 | expect(p.output!).to(equal("2")) 313 | 314 | p.input = "3" 315 | expect(p.output!).to(equal("3")) 316 | 317 | p.input = "+" 318 | expect(p.output!).to(equal("5")) 319 | 320 | p.input = "4" 321 | expect(p.output!).to(equal("4")) 322 | 323 | p.input = "=" 324 | expect(p.output!).to(equal("9")) 325 | 326 | p.input = "=" 327 | expect(p.output!).to(equal("13")) 328 | 329 | } 330 | } 331 | 332 | context("different operators (precedence)") { 333 | 334 | it("`2 + 3 * 4 = =` should print `14` then `56`, WITHOUT printing result of `2 + 3` in midway") { 335 | 336 | p.input = "2" 337 | expect(p.output!).to(equal("2")) 338 | 339 | p.input = "+" 340 | expect(p.output!).to(equal("2")) 341 | 342 | p.input = "3" 343 | expect(p.output!).to(equal("3")) 344 | 345 | p.input = "*" 346 | expect(p.output!).to(equal("3")) // NOTE: not printing result of `2 + 3` 347 | 348 | p.input = "4" 349 | expect(p.output!).to(equal("4")) 350 | 351 | p.input = "=" 352 | expect(p.output!).to(equal("14")) 353 | 354 | p.input = "=" 355 | expect(p.output!).to(equal("56")) 356 | 357 | } 358 | 359 | it("`2 * 3 + 4 = =` should print `10` then `14`, with printing result of `2 * 3` in midway") { 360 | 361 | p.input = "2" 362 | expect(p.output!).to(equal("2")) 363 | 364 | p.input = "*" 365 | expect(p.output!).to(equal("2")) 366 | 367 | p.input = "3" 368 | expect(p.output!).to(equal("3")) 369 | 370 | p.input = "+" 371 | expect(p.output!).to(equal("6")) 372 | 373 | p.input = "4" 374 | expect(p.output!).to(equal("4")) 375 | 376 | p.input = "=" 377 | expect(p.output!).to(equal("10")) 378 | 379 | p.input = "=" 380 | expect(p.output!).to(equal("14")) 381 | 382 | } 383 | } 384 | } 385 | 386 | describe("forced-`.Equal` e.g. `+` then `=`") { 387 | 388 | context("same operators") { 389 | 390 | it("`2 + 3 + = =` should print `10` then `15`") { 391 | 392 | p.input = "2" 393 | expect(p.output!).to(equal("2")) 394 | 395 | p.input = "+" 396 | expect(p.output!).to(equal("2")) 397 | 398 | p.input = "3" 399 | expect(p.output!).to(equal("3")) 400 | 401 | p.input = "*" 402 | expect(p.output!).to(equal("3")) 403 | 404 | p.input = "=" 405 | expect(p.output!).to(equal("11")) 406 | 407 | p.input = "=" 408 | expect(p.output!).to(equal("33")) 409 | 410 | } 411 | 412 | } 413 | 414 | context("different operators (precedence)") { 415 | 416 | it("`2 + 3 * = =` should print `11` then `33`") { 417 | 418 | p.input = "2" 419 | expect(p.output!).to(equal("2")) 420 | 421 | p.input = "+" 422 | expect(p.output!).to(equal("2")) 423 | 424 | p.input = "3" 425 | expect(p.output!).to(equal("3")) 426 | 427 | p.input = "*" 428 | expect(p.output!).to(equal("3")) 429 | 430 | p.input = "=" 431 | expect(p.output!).to(equal("11")) 432 | 433 | p.input = "=" 434 | expect(p.output!).to(equal("33")) 435 | 436 | } 437 | 438 | it("`2 * 3 + = =` should print `12` then `18`") { 439 | 440 | p.input = "2" 441 | expect(p.output!).to(equal("2")) 442 | 443 | p.input = "*" 444 | expect(p.output!).to(equal("2")) 445 | 446 | p.input = "3" 447 | expect(p.output!).to(equal("3")) 448 | 449 | p.input = "+" 450 | expect(p.output!).to(equal("6")) 451 | 452 | p.input = "=" 453 | expect(p.output!).to(equal("12")) 454 | 455 | p.input = "=" 456 | expect(p.output!).to(equal("18")) 457 | 458 | } 459 | } 460 | } 461 | 462 | describe("operator change") { 463 | 464 | it("`1 2 + 3 + * 4 =` should cancel previous addition and print `24`") { 465 | 466 | p.input = "1" 467 | expect(p.output!).to(equal("1")) 468 | 469 | p.input = "2" 470 | expect(p.output!).to(equal("12")) 471 | 472 | p.input = "+" 473 | expect(p.output!).to(equal("12")) 474 | 475 | p.input = "3" 476 | expect(p.output!).to(equal("3")) 477 | 478 | p.input = "+" 479 | expect(p.output!).to(equal("15")) // should immediately calculate addition 480 | 481 | p.input = "*" 482 | expect(p.output!).to(equal("3")) // should revert output for multiply (higher precedence) 483 | 484 | p.input = "4" 485 | expect(p.output!).to(equal("4")) 486 | 487 | p.input = "=" 488 | expect(p.output!).to(equal("24")) 489 | 490 | } 491 | 492 | } 493 | 494 | describe("`.Clear`") { 495 | 496 | it("`2 + 3 = C = =` should print `5` then `3` then `6`") { 497 | 498 | p.input = "2" 499 | expect(p.output).to(equal("2")) 500 | 501 | p.input = "+" 502 | expect(p.output!).to(equal("2")) 503 | 504 | p.input = "3" 505 | expect(p.output!).to(equal("3")) 506 | 507 | p.input = "=" 508 | expect(p.output!).to(equal("5")) 509 | 510 | p.input = "C" 511 | expect(p.output!).to(equal("0")) 512 | 513 | p.input = "=" 514 | expect(p.output!).to(equal("3")) 515 | 516 | p.input = "=" 517 | expect(p.output!).to(equal("6")) 518 | 519 | } 520 | 521 | it("`2 + 3 = C 4 = =` should print `5` then `7` then `10`") { 522 | 523 | p.input = "2" 524 | expect(p.output).to(equal("2")) 525 | 526 | p.input = "+" 527 | expect(p.output!).to(equal("2")) 528 | 529 | p.input = "3" 530 | expect(p.output!).to(equal("3")) 531 | 532 | p.input = "=" 533 | expect(p.output!).to(equal("5")) 534 | 535 | p.input = "C" 536 | expect(p.output!).to(equal("0")) 537 | 538 | p.input = "4" 539 | expect(p.output!).to(equal("4")) 540 | 541 | p.input = "=" 542 | expect(p.output!).to(equal("7")) 543 | 544 | p.input = "=" 545 | expect(p.output!).to(equal("10")) 546 | 547 | } 548 | 549 | it("`2 + C = =` should print `2` then `2`") { 550 | 551 | p.input = "2" 552 | expect(p.output).to(equal("2")) 553 | 554 | p.input = "+" 555 | expect(p.output!).to(equal("2")) 556 | 557 | p.input = "C" 558 | expect(p.output!).to(equal("0")) 559 | 560 | p.input = "=" 561 | expect(p.output!).to(equal("2")) 562 | 563 | p.input = "=" 564 | expect(p.output!).to(equal("2")) 565 | 566 | } 567 | 568 | it("`2 + 3 C = =` should print `2` then `2`") { 569 | 570 | p.input = "2" 571 | expect(p.output).to(equal("2")) 572 | 573 | p.input = "+" 574 | expect(p.output!).to(equal("2")) 575 | 576 | p.input = "3" 577 | expect(p.output!).to(equal("3")) 578 | 579 | p.input = "C" 580 | expect(p.output!).to(equal("0")) 581 | 582 | p.input = "=" 583 | expect(p.output!).to(equal("2")) 584 | 585 | p.input = "=" 586 | expect(p.output!).to(equal("2")) 587 | 588 | } 589 | 590 | it("`2 + 3 C 4 = =` should print `6` then `10`") { 591 | 592 | p.input = "2" 593 | expect(p.output!).to(equal("2")) 594 | 595 | p.input = "+" 596 | expect(p.output!).to(equal("2")) 597 | 598 | p.input = "3" 599 | expect(p.output!).to(equal("3")) 600 | 601 | p.input = "C" 602 | expect(p.output!).to(equal("0")) 603 | 604 | p.input = "4" 605 | expect(p.output!).to(equal("4")) 606 | 607 | p.input = "=" 608 | expect(p.output!).to(equal("6")) 609 | 610 | p.input = "=" 611 | expect(p.output!).to(equal("10")) 612 | 613 | } 614 | 615 | it("`2 + 3 + 4 C = =` should print `5` then `5` after 1st `.Equal`") { 616 | 617 | p.input = "2" 618 | expect(p.output!).to(equal("2")) 619 | 620 | p.input = "+" 621 | expect(p.output!).to(equal("2")) 622 | 623 | p.input = "3" 624 | expect(p.output!).to(equal("3")) 625 | 626 | p.input = "+" 627 | expect(p.output!).to(equal("5")) 628 | 629 | p.input = "4" 630 | expect(p.output!).to(equal("4")) 631 | 632 | p.input = "C" 633 | expect(p.output!).to(equal("0")) 634 | 635 | p.input = "=" 636 | expect(p.output!).to(equal("5")) 637 | 638 | p.input = "=" 639 | expect(p.output!).to(equal("5")) 640 | } 641 | 642 | } 643 | 644 | describe("underflow") { 645 | 646 | it("`. 1 * = = = = = = = = * = `") { 647 | 648 | p.input = "." 649 | p.input = "1" 650 | p.input = "*" 651 | for _ in 0..<7 { 652 | p.input = "=" 653 | } 654 | expect(p.output!).to(equal("0.00000001")) 655 | 656 | p.input = "=" 657 | expect(p.output!).to(equal("1e-9")) 658 | 659 | p.input = "*" 660 | p.input = "=" 661 | expect(p.output!).to(equal("1e-18")) 662 | 663 | } 664 | 665 | it("`. 0 0 0 0 0 0 0 1 * 0 . 1 = `") { 666 | 667 | p.input = "." 668 | for _ in 0..<7 { 669 | p.input = "0" 670 | } 671 | p.input = "1" 672 | expect(p.output!).to(equal("0.00000001")) 673 | 674 | p.input = "*" 675 | expect(p.output!).to(equal("0.00000001")) 676 | 677 | p.input = "0" 678 | p.input = "." 679 | p.input = "1" 680 | p.input = "=" 681 | expect(p.output!).to(equal("1e-9")) 682 | 683 | } 684 | 685 | } 686 | 687 | // TODO: add bracket feature 688 | // describe("bracket") { 689 | // 690 | // it("`2 * ( 3 + 4 ) = =` should print `14` then `21` (+7 for consecutive-.Equal)") { 691 | // } 692 | // } 693 | 694 | } 695 | } -------------------------------------------------------------------------------- /ReactKitCalculatorTests/_Peripheral.swift: -------------------------------------------------------------------------------- 1 | // 2 | // _Peripheral.swift 3 | // ReactKitCalculator 4 | // 5 | // Created by Yasuhiro Inami on 2015/02/18. 6 | // Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class _Peripheral: NSObject 12 | { 13 | // NOTE: can't use `Calculator.Key?` for `dynamic var`, so use String + KVO + filter to workaround 14 | dynamic var input: String? 15 | 16 | dynamic var output: String? 17 | dynamic var expression: String? 18 | } --------------------------------------------------------------------------------