├── .gitignore ├── .gitmodules ├── .swiftlint.yml ├── Cartfile.private ├── Cartfile.resolved ├── Configurations ├── Base.xcconfig ├── Debug.xcconfig └── Release.xcconfig ├── FunRouter.playground ├── Pages │ ├── Router1.xcplaygroundpage │ │ └── Contents.swift │ └── Router2.xcplaygroundpage │ │ └── Contents.swift └── contents.xcplayground ├── FunRouter.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── FunRouter.xcscheme ├── LICENSE ├── README.md ├── Scripts └── generate-flip-curry.swift ├── Sources ├── Base │ ├── FlipCurry.swift │ ├── FuncEquality.swift │ ├── Operators.swift │ └── Prelude.swift ├── FunRouter.h ├── Info.plist ├── Route1.swift └── Route2.swift └── Specs ├── Fixtures └── Sitemap.swift ├── Info.plist ├── Route1Spec.swift └── Route2Spec.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/Curry"] 2 | path = Carthage/Checkouts/Curry 3 | url = https://github.com/thoughtbot/Curry.git 4 | [submodule "Carthage/Checkouts/Nimble"] 5 | path = Carthage/Checkouts/Nimble 6 | url = https://github.com/Quick/Nimble.git 7 | [submodule "Carthage/Checkouts/Quick"] 8 | path = Carthage/Checkouts/Quick 9 | url = https://github.com/Quick/Quick.git 10 | [submodule "Carthage/Checkouts/xcconfigs"] 11 | path = Carthage/Checkouts/xcconfigs 12 | url = https://github.com/mrackwitz/xcconfigs.git 13 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: 3 | # To fix `trailing_whitespace` error, 4 | # go to Xcode Preferences -> Text Editing -> turn on both "Automatically trim trailing whitespace" and "Including whitespace-only lines". 5 | # 6 | 7 | disabled_rules: 8 | - line_length 9 | - function_body_length 10 | - type_body_length 11 | - file_length 12 | - cyclomatic_complexity 13 | 14 | - opening_brace # prefer Allman-Style 15 | - statement_position # allow `if {}\nelse {}` 16 | - type_name # allow "_" prefix name 17 | - variable_name # allow "_" prefix name 18 | - todo 19 | - valid_docs 20 | 21 | - force_cast # for existential type workaround 22 | 23 | opt_in_rules: 24 | # - empty_count # local variable name `count` is frequently used 25 | 26 | included: 27 | - Sources 28 | - Tests 29 | 30 | excluded: 31 | - Carthage 32 | - Packages 33 | 34 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle) 35 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "thoughtbot/Curry" "master" 2 | github "Quick/Quick" ~> 0.10.0 3 | github "Quick/Nimble" ~> 5.0.0 4 | github "mrackwitz/xcconfigs" 5 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "thoughtbot/Curry" "408134ffa8dd1c09b0050f65eae300381b934bf6" 2 | github "Quick/Nimble" "v5.0.0" 3 | github "Quick/Quick" "v0.10.0" 4 | github "mrackwitz/xcconfigs" "3.0" 5 | -------------------------------------------------------------------------------- /Configurations/Base.xcconfig: -------------------------------------------------------------------------------- 1 | MACOSX_DEPLOYMENT_TARGET = 10.9 2 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 3 | WATCHOS_DEPLOYMENT_TARGET = 2.0 4 | TVOS_DEPLOYMENT_TARGET = 9.0 -------------------------------------------------------------------------------- /Configurations/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | SWIFT_OPTIMIZATION_LEVEL = -Onone; -------------------------------------------------------------------------------- /Configurations/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | SWIFT_OPTIMIZATION_LEVEL = -Owholemodule; -------------------------------------------------------------------------------- /FunRouter.playground/Pages/Router1.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import FunRouter 3 | import Curry 4 | 5 | //: ## Function-based URL route 6 | typealias Route = Route1 7 | 8 | //: ### Basic routing 9 | 10 | do { 11 | /// Parses "/Hello/{city}/{year}". 12 | let route: Route<(String, Int)> = 13 | curry { ($1, $2) } <^> match("Hello") <*> string() <*> int() 14 | 15 | // or, more fancy flipped way: 16 | // let route: Route<(String, Int)> = 17 | // /"Hello" string() int() <&!> flipCurry { ($1, $2) } 18 | 19 | let value1 = route.run(["Hello", "Budapest", "2016"]) 20 | let value2 = route.run(["Ya", "tu", "sabes"]) 21 | } 22 | 23 | //: ### More complex routing 24 | 25 | enum Sitemap { 26 | case foo(Int) // "/R/foo/{Int}" 27 | case bar(Double) // "/R/bar/{Double}" 28 | case baz(String) // "/R/baz/{String}" 29 | case fooBar // "/R/foo/bar" 30 | case notFound // 404 31 | } 32 | 33 | do { 34 | let route: Route = 35 | match("R") 36 | *> choice([ 37 | match("foo") *> int() <&> Sitemap.foo, 38 | match("bar") *> double() <&> Sitemap.bar, 39 | match("baz") *> string() <&> Sitemap.baz, 40 | match("foo") *> match("bar") *> pure(Sitemap.fooBar), 41 | ]) 42 | <|> pure(Sitemap.notFound) 43 | 44 | route.run(["R", "foo", "123"]) 45 | route.run(["R", "bar", "456"]) 46 | route.run(["R", "baz", "xyz"]) 47 | route.run(["R", "foo", "bar"]) 48 | route.run(["R", "foo", "xxx"]) // notFound 49 | } 50 | -------------------------------------------------------------------------------- /FunRouter.playground/Pages/Router2.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import FunRouter 3 | import Curry 4 | 5 | //: ## Enum-based URL route (optimizable) 6 | typealias Route = Route2 7 | 8 | //: ### Basic routing 9 | 10 | do { 11 | /// Parses "/Hello/{city}/{year}". 12 | let route: Route<(String, Int)> = 13 | curry { ($1, $2) } <^> match("Hello") <*> string() <*> int() 14 | 15 | // or, more fancy flipped way: 16 | // let route: Route<(String, Int)> = 17 | // /"Hello" string() int() <&!> flipCurry { ($1, $2) } 18 | 19 | let value1 = route.run(["Hello", "Budapest", "2016"]) 20 | let value2 = route.run(["Ya", "tu", "sabes"]) 21 | } 22 | 23 | //: ### More complex routing 24 | 25 | enum Sitemap { 26 | case foo(Int) // "/R/foo/{Int}" 27 | case bar(Double) // "/R/bar/{Double}" 28 | case baz(String) // "/R/baz/{String}" 29 | case fooBar // "/R/foo/bar" 30 | case notFound // 404 31 | } 32 | 33 | do { 34 | let route: Route = 35 | match("R") 36 | *> choice([ 37 | match("foo") *> int() <&> Sitemap.foo, 38 | match("bar") *> double() <&> Sitemap.bar, 39 | match("baz") *> string() <&> Sitemap.baz, 40 | match("foo") *> match("bar") *> pure(Sitemap.fooBar), 41 | ]) 42 | <|> pure(Sitemap.notFound) 43 | 44 | route.run(["R", "foo", "123"]) 45 | route.run(["R", "bar", "456"]) 46 | route.run(["R", "baz", "xyz"]) 47 | route.run(["R", "foo", "bar"]) 48 | route.run(["R", "foo", "xxx"]) // notFound 49 | } 50 | 51 | //: ### Optimization 52 | 53 | do { 54 | let naiveRoute: Route = 55 | match("R") 56 | *> choice([ 57 | match("foo") *> int() <&> Sitemap.foo, 58 | match("bar") *> double() <&> Sitemap.bar, 59 | match("foo") *> match("bar") *> pure(Sitemap.fooBar) 60 | ]) 61 | 62 | let optimizedRoute = optimize(naiveRoute) 63 | 64 | print("before optimize =", naiveRoute) 65 | print(" after optimize =", optimizedRoute) 66 | } 67 | -------------------------------------------------------------------------------- /FunRouter.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FunRouter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1F191CB21D9BF08400E96414 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 486D11781D7ED9010043FF29 /* Quick.framework */; }; 11 | 1F1BFFC61D9737A3000105EB /* FlipCurry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1BFFC51D9737A3000105EB /* FlipCurry.swift */; }; 12 | 484D01371D7EFCC20045601C /* Curry.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 486D11611D7ED9010043FF29 /* Curry.framework */; }; 13 | 484D01481D7EFD910045601C /* FuncEquality.swift in Sources */ = {isa = PBXBuildFile; fileRef = 484D01471D7EFD910045601C /* FuncEquality.swift */; }; 14 | 48BC71F91D7ED0AC0071A7F3 /* FunRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 48BC71F71D7ED0AC0071A7F3 /* FunRouter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 48BC72031D7ED1A50071A7F3 /* Route1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC72011D7ED1A50071A7F3 /* Route1.swift */; }; 16 | 48BC72041D7ED1A50071A7F3 /* Route2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC72021D7ED1A50071A7F3 /* Route2.swift */; }; 17 | 48BC720A1D7ED2B30071A7F3 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC72071D7ED2B30071A7F3 /* Operators.swift */; }; 18 | 48BC720B1D7ED2B30071A7F3 /* Prelude.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC72081D7ED2B30071A7F3 /* Prelude.swift */; }; 19 | 48BC72151D7ED4830071A7F3 /* FunRouter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48BC71F41D7ED0AC0071A7F3 /* FunRouter.framework */; }; 20 | 48BC721F1D7ED6A20071A7F3 /* Route1Spec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC721B1D7ED6540071A7F3 /* Route1Spec.swift */; }; 21 | 48BC72201D7ED6A50071A7F3 /* Route2Spec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC721C1D7ED6540071A7F3 /* Route2Spec.swift */; }; 22 | 48BC74861D7ED7D00071A7F3 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48BC74701D7ED6CC0071A7F3 /* Nimble.framework */; }; 23 | 48BC748F1D7ED8550071A7F3 /* Sitemap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48BC748E1D7ED8550071A7F3 /* Sitemap.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 486D115E1D7ED9010043FF29 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */; 30 | proxyType = 2; 31 | remoteGlobalIDString = F88630561B4EF96200F53969; 32 | remoteInfo = "Curry-iOS"; 33 | }; 34 | 486D11601D7ED9010043FF29 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */; 37 | proxyType = 2; 38 | remoteGlobalIDString = F886307D1B4EF9F800F53969; 39 | remoteInfo = "Curry-Mac"; 40 | }; 41 | 486D11621D7ED9010043FF29 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */; 44 | proxyType = 2; 45 | remoteGlobalIDString = 804D01F31BA3684C0005BBC4; 46 | remoteInfo = "Curry-watchOS"; 47 | }; 48 | 486D11641D7ED9010043FF29 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */; 51 | proxyType = 2; 52 | remoteGlobalIDString = 80E059071BA9FA7F0077CBA7; 53 | remoteInfo = "Curry-tvOS"; 54 | }; 55 | 486D11771D7ED9010043FF29 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 58 | proxyType = 2; 59 | remoteGlobalIDString = DAEB6B8E1943873100289F44; 60 | remoteInfo = "Quick-OSX"; 61 | }; 62 | 486D11791D7ED9010043FF29 /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 65 | proxyType = 2; 66 | remoteGlobalIDString = DAEB6B991943873100289F44; 67 | remoteInfo = "Quick-OSXTests"; 68 | }; 69 | 486D117B1D7ED9010043FF29 /* PBXContainerItemProxy */ = { 70 | isa = PBXContainerItemProxy; 71 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 72 | proxyType = 2; 73 | remoteGlobalIDString = DA5663E81A4C8D8500193C88; 74 | remoteInfo = "QuickFocused-OSXTests"; 75 | }; 76 | 486D117D1D7ED9010043FF29 /* PBXContainerItemProxy */ = { 77 | isa = PBXContainerItemProxy; 78 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 79 | proxyType = 2; 80 | remoteGlobalIDString = 5A5D117C19473F2100F6D13D; 81 | remoteInfo = "Quick-iOS"; 82 | }; 83 | 486D117F1D7ED9010043FF29 /* PBXContainerItemProxy */ = { 84 | isa = PBXContainerItemProxy; 85 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 86 | proxyType = 2; 87 | remoteGlobalIDString = 5A5D118619473F2100F6D13D; 88 | remoteInfo = "Quick-iOSTests"; 89 | }; 90 | 486D11811D7ED9010043FF29 /* PBXContainerItemProxy */ = { 91 | isa = PBXContainerItemProxy; 92 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 93 | proxyType = 2; 94 | remoteGlobalIDString = DA9876B21A4C70EB0004AA17; 95 | remoteInfo = "QuickFocused-iOSTests"; 96 | }; 97 | 486D11831D7ED9010043FF29 /* PBXContainerItemProxy */ = { 98 | isa = PBXContainerItemProxy; 99 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 100 | proxyType = 2; 101 | remoteGlobalIDString = 1F118CD51BDCA4AB005013A2; 102 | remoteInfo = "Quick-tvOS"; 103 | }; 104 | 486D11851D7ED9010043FF29 /* PBXContainerItemProxy */ = { 105 | isa = PBXContainerItemProxy; 106 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 107 | proxyType = 2; 108 | remoteGlobalIDString = 1F118CDE1BDCA4AB005013A2; 109 | remoteInfo = "Quick-tvOSTests"; 110 | }; 111 | 486D11871D7ED9010043FF29 /* PBXContainerItemProxy */ = { 112 | isa = PBXContainerItemProxy; 113 | containerPortal = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 114 | proxyType = 2; 115 | remoteGlobalIDString = 1F118CF01BDCA4BB005013A2; 116 | remoteInfo = "QuickFocused-tvOSTests"; 117 | }; 118 | 48BC72161D7ED4830071A7F3 /* PBXContainerItemProxy */ = { 119 | isa = PBXContainerItemProxy; 120 | containerPortal = 48BC71EB1D7ED0AC0071A7F3 /* Project object */; 121 | proxyType = 1; 122 | remoteGlobalIDString = 48BC71F31D7ED0AC0071A7F3; 123 | remoteInfo = FunRouter; 124 | }; 125 | 48BC746F1D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 126 | isa = PBXContainerItemProxy; 127 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 128 | proxyType = 2; 129 | remoteGlobalIDString = 1F925EAD195C0D6300ED456B; 130 | remoteInfo = "Nimble-macOS"; 131 | }; 132 | 48BC74711D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 133 | isa = PBXContainerItemProxy; 134 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 135 | proxyType = 2; 136 | remoteGlobalIDString = 1F925EB7195C0D6300ED456B; 137 | remoteInfo = "Nimble-macOSTests"; 138 | }; 139 | 48BC74731D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 140 | isa = PBXContainerItemProxy; 141 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 142 | proxyType = 2; 143 | remoteGlobalIDString = 1F1A74291940169200FFFC47; 144 | remoteInfo = "Nimble-iOS"; 145 | }; 146 | 48BC74751D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 147 | isa = PBXContainerItemProxy; 148 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 149 | proxyType = 2; 150 | remoteGlobalIDString = 1F1A74341940169200FFFC47; 151 | remoteInfo = "Nimble-iOSTests"; 152 | }; 153 | 48BC74771D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 154 | isa = PBXContainerItemProxy; 155 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 156 | proxyType = 2; 157 | remoteGlobalIDString = 1F5DF1551BDCA0CE00C3A531; 158 | remoteInfo = "Nimble-tvOS"; 159 | }; 160 | 48BC74791D7ED6CC0071A7F3 /* PBXContainerItemProxy */ = { 161 | isa = PBXContainerItemProxy; 162 | containerPortal = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 163 | proxyType = 2; 164 | remoteGlobalIDString = 1F5DF15E1BDCA0CE00C3A531; 165 | remoteInfo = "Nimble-tvOSTests"; 166 | }; 167 | /* End PBXContainerItemProxy section */ 168 | 169 | /* Begin PBXFileReference section */ 170 | 1F191CC91D9BF65B00E96414 /* FunRouter.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = FunRouter.playground; sourceTree = ""; }; 171 | 1F1BFFC51D9737A3000105EB /* FlipCurry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlipCurry.swift; sourceTree = ""; }; 172 | 1F1BFFDC1D97698B000105EB /* generate-flip-curry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "generate-flip-curry.swift"; sourceTree = ""; }; 173 | 484D01471D7EFD910045601C /* FuncEquality.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FuncEquality.swift; sourceTree = ""; }; 174 | 48BC71F41D7ED0AC0071A7F3 /* FunRouter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FunRouter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 175 | 48BC71F71D7ED0AC0071A7F3 /* FunRouter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FunRouter.h; sourceTree = ""; }; 176 | 48BC71F81D7ED0AC0071A7F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 177 | 48BC72011D7ED1A50071A7F3 /* Route1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route1.swift; sourceTree = ""; }; 178 | 48BC72021D7ED1A50071A7F3 /* Route2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route2.swift; sourceTree = ""; }; 179 | 48BC72071D7ED2B30071A7F3 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; 180 | 48BC72081D7ED2B30071A7F3 /* Prelude.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Prelude.swift; sourceTree = ""; }; 181 | 48BC72101D7ED4830071A7F3 /* FunRouterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FunRouterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 182 | 48BC72141D7ED4830071A7F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 183 | 48BC721B1D7ED6540071A7F3 /* Route1Spec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route1Spec.swift; sourceTree = ""; }; 184 | 48BC721C1D7ED6540071A7F3 /* Route2Spec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route2Spec.swift; sourceTree = ""; }; 185 | 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Curry.xcodeproj; path = Curry/Curry.xcodeproj; sourceTree = ""; }; 186 | 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Nimble.xcodeproj; path = Nimble/Nimble.xcodeproj; sourceTree = ""; }; 187 | 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Quick.xcodeproj; path = Quick/Quick.xcodeproj; sourceTree = ""; }; 188 | 48BC74191D7ED6BE0071A7F3 /* UniversalFramework_Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Base.xcconfig; sourceTree = ""; }; 189 | 48BC741A1D7ED6BE0071A7F3 /* UniversalFramework_Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Framework.xcconfig; sourceTree = ""; }; 190 | 48BC741B1D7ED6BE0071A7F3 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = ""; }; 191 | 48BC747D1D7ED7190071A7F3 /* Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; 192 | 48BC747E1D7ED7190071A7F3 /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 193 | 48BC747F1D7ED7190071A7F3 /* Release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 194 | 48BC748E1D7ED8550071A7F3 /* Sitemap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sitemap.swift; sourceTree = ""; }; 195 | /* End PBXFileReference section */ 196 | 197 | /* Begin PBXFrameworksBuildPhase section */ 198 | 48BC71F01D7ED0AC0071A7F3 /* Frameworks */ = { 199 | isa = PBXFrameworksBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | 48BC720D1D7ED4830071A7F3 /* Frameworks */ = { 206 | isa = PBXFrameworksBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 484D01371D7EFCC20045601C /* Curry.framework in Frameworks */, 210 | 1F191CB21D9BF08400E96414 /* Quick.framework in Frameworks */, 211 | 48BC74861D7ED7D00071A7F3 /* Nimble.framework in Frameworks */, 212 | 48BC72151D7ED4830071A7F3 /* FunRouter.framework in Frameworks */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXFrameworksBuildPhase section */ 217 | 218 | /* Begin PBXGroup section */ 219 | 1F1BFFDA1D976978000105EB /* Scripts */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 1F1BFFDC1D97698B000105EB /* generate-flip-curry.swift */, 223 | ); 224 | path = Scripts; 225 | sourceTree = ""; 226 | }; 227 | 486D11581D7ED9000043FF29 /* Products */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | 486D115F1D7ED9010043FF29 /* Curry.framework */, 231 | 486D11611D7ED9010043FF29 /* Curry.framework */, 232 | 486D11631D7ED9010043FF29 /* Curry.framework */, 233 | 486D11651D7ED9010043FF29 /* Curry.framework */, 234 | ); 235 | name = Products; 236 | sourceTree = ""; 237 | }; 238 | 486D116C1D7ED9010043FF29 /* Products */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 486D11781D7ED9010043FF29 /* Quick.framework */, 242 | 486D117A1D7ED9010043FF29 /* Quick-macOSTests.xctest */, 243 | 486D117C1D7ED9010043FF29 /* QuickFocused-macOSTests.xctest */, 244 | 486D117E1D7ED9010043FF29 /* Quick.framework */, 245 | 486D11801D7ED9010043FF29 /* Quick-iOSTests.xctest */, 246 | 486D11821D7ED9010043FF29 /* QuickFocused-iOSTests.xctest */, 247 | 486D11841D7ED9010043FF29 /* Quick.framework */, 248 | 486D11861D7ED9010043FF29 /* Quick-tvOSTests.xctest */, 249 | 486D11881D7ED9010043FF29 /* QuickFocused-tvOSTests.xctest */, 250 | ); 251 | name = Products; 252 | sourceTree = ""; 253 | }; 254 | 48BC71EA1D7ED0AC0071A7F3 = { 255 | isa = PBXGroup; 256 | children = ( 257 | 1F191CC91D9BF65B00E96414 /* FunRouter.playground */, 258 | 48BC747B1D7ED6E10071A7F3 /* Configurations */, 259 | 48BC72211D7ED6BE0071A7F3 /* Checkouts */, 260 | 48BC71F61D7ED0AC0071A7F3 /* Sources */, 261 | 48BC72111D7ED4830071A7F3 /* Specs */, 262 | 1F1BFFDA1D976978000105EB /* Scripts */, 263 | 48BC71F51D7ED0AC0071A7F3 /* Products */, 264 | ); 265 | sourceTree = ""; 266 | }; 267 | 48BC71F51D7ED0AC0071A7F3 /* Products */ = { 268 | isa = PBXGroup; 269 | children = ( 270 | 48BC71F41D7ED0AC0071A7F3 /* FunRouter.framework */, 271 | 48BC72101D7ED4830071A7F3 /* FunRouterTests.xctest */, 272 | ); 273 | name = Products; 274 | sourceTree = ""; 275 | }; 276 | 48BC71F61D7ED0AC0071A7F3 /* Sources */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | 48BC72051D7ED2B30071A7F3 /* Base */, 280 | 48BC72011D7ED1A50071A7F3 /* Route1.swift */, 281 | 48BC72021D7ED1A50071A7F3 /* Route2.swift */, 282 | 48BC71F71D7ED0AC0071A7F3 /* FunRouter.h */, 283 | 48BC71F81D7ED0AC0071A7F3 /* Info.plist */, 284 | ); 285 | path = Sources; 286 | sourceTree = ""; 287 | }; 288 | 48BC72051D7ED2B30071A7F3 /* Base */ = { 289 | isa = PBXGroup; 290 | children = ( 291 | 48BC72071D7ED2B30071A7F3 /* Operators.swift */, 292 | 48BC72081D7ED2B30071A7F3 /* Prelude.swift */, 293 | 484D01471D7EFD910045601C /* FuncEquality.swift */, 294 | 1F1BFFC51D9737A3000105EB /* FlipCurry.swift */, 295 | ); 296 | path = Base; 297 | sourceTree = ""; 298 | }; 299 | 48BC72111D7ED4830071A7F3 /* Specs */ = { 300 | isa = PBXGroup; 301 | children = ( 302 | 48BC748D1D7ED8550071A7F3 /* Fixtures */, 303 | 48BC721B1D7ED6540071A7F3 /* Route1Spec.swift */, 304 | 48BC721C1D7ED6540071A7F3 /* Route2Spec.swift */, 305 | 48BC72141D7ED4830071A7F3 /* Info.plist */, 306 | ); 307 | path = Specs; 308 | sourceTree = ""; 309 | }; 310 | 48BC72211D7ED6BE0071A7F3 /* Checkouts */ = { 311 | isa = PBXGroup; 312 | children = ( 313 | 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */, 314 | 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */, 315 | 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */, 316 | ); 317 | name = Checkouts; 318 | path = Carthage/Checkouts; 319 | sourceTree = ""; 320 | }; 321 | 48BC74161D7ED6BE0071A7F3 /* mrackwitz/xcconfigs (for target) */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | 48BC74191D7ED6BE0071A7F3 /* UniversalFramework_Base.xcconfig */, 325 | 48BC741A1D7ED6BE0071A7F3 /* UniversalFramework_Framework.xcconfig */, 326 | 48BC741B1D7ED6BE0071A7F3 /* UniversalFramework_Test.xcconfig */, 327 | ); 328 | name = "mrackwitz/xcconfigs (for target)"; 329 | path = xcconfigs; 330 | sourceTree = ""; 331 | }; 332 | 48BC74671D7ED6CC0071A7F3 /* Products */ = { 333 | isa = PBXGroup; 334 | children = ( 335 | 48BC74701D7ED6CC0071A7F3 /* Nimble.framework */, 336 | 48BC74721D7ED6CC0071A7F3 /* NimbleTests.xctest */, 337 | 48BC74741D7ED6CC0071A7F3 /* Nimble.framework */, 338 | 48BC74761D7ED6CC0071A7F3 /* NimbleTests.xctest */, 339 | 48BC74781D7ED6CC0071A7F3 /* Nimble.framework */, 340 | 48BC747A1D7ED6CC0071A7F3 /* NimbleTests.xctest */, 341 | ); 342 | name = Products; 343 | sourceTree = ""; 344 | }; 345 | 48BC747B1D7ED6E10071A7F3 /* Configurations */ = { 346 | isa = PBXGroup; 347 | children = ( 348 | 48BC747C1D7ED7190071A7F3 /* project xcconfigs */, 349 | 48BC74161D7ED6BE0071A7F3 /* mrackwitz/xcconfigs (for target) */, 350 | ); 351 | name = Configurations; 352 | path = Carthage/Checkouts; 353 | sourceTree = ""; 354 | }; 355 | 48BC747C1D7ED7190071A7F3 /* project xcconfigs */ = { 356 | isa = PBXGroup; 357 | children = ( 358 | 48BC747D1D7ED7190071A7F3 /* Base.xcconfig */, 359 | 48BC747E1D7ED7190071A7F3 /* Debug.xcconfig */, 360 | 48BC747F1D7ED7190071A7F3 /* Release.xcconfig */, 361 | ); 362 | name = "project xcconfigs"; 363 | path = Configurations; 364 | sourceTree = SOURCE_ROOT; 365 | }; 366 | 48BC748D1D7ED8550071A7F3 /* Fixtures */ = { 367 | isa = PBXGroup; 368 | children = ( 369 | 48BC748E1D7ED8550071A7F3 /* Sitemap.swift */, 370 | ); 371 | path = Fixtures; 372 | sourceTree = ""; 373 | }; 374 | /* End PBXGroup section */ 375 | 376 | /* Begin PBXHeadersBuildPhase section */ 377 | 48BC71F11D7ED0AC0071A7F3 /* Headers */ = { 378 | isa = PBXHeadersBuildPhase; 379 | buildActionMask = 2147483647; 380 | files = ( 381 | 48BC71F91D7ED0AC0071A7F3 /* FunRouter.h in Headers */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXHeadersBuildPhase section */ 386 | 387 | /* Begin PBXNativeTarget section */ 388 | 48BC71F31D7ED0AC0071A7F3 /* FunRouter */ = { 389 | isa = PBXNativeTarget; 390 | buildConfigurationList = 48BC71FC1D7ED0AC0071A7F3 /* Build configuration list for PBXNativeTarget "FunRouter" */; 391 | buildPhases = ( 392 | 48BC71EF1D7ED0AC0071A7F3 /* Sources */, 393 | 48BC71F01D7ED0AC0071A7F3 /* Frameworks */, 394 | 48BC71F11D7ED0AC0071A7F3 /* Headers */, 395 | 48BC71F21D7ED0AC0071A7F3 /* Resources */, 396 | ); 397 | buildRules = ( 398 | ); 399 | dependencies = ( 400 | ); 401 | name = FunRouter; 402 | productName = FunRouter; 403 | productReference = 48BC71F41D7ED0AC0071A7F3 /* FunRouter.framework */; 404 | productType = "com.apple.product-type.framework"; 405 | }; 406 | 48BC720F1D7ED4830071A7F3 /* FunRouterTests */ = { 407 | isa = PBXNativeTarget; 408 | buildConfigurationList = 48BC72181D7ED4830071A7F3 /* Build configuration list for PBXNativeTarget "FunRouterTests" */; 409 | buildPhases = ( 410 | 48BC720C1D7ED4830071A7F3 /* Sources */, 411 | 48BC720D1D7ED4830071A7F3 /* Frameworks */, 412 | 48BC720E1D7ED4830071A7F3 /* Resources */, 413 | ); 414 | buildRules = ( 415 | ); 416 | dependencies = ( 417 | 48BC72171D7ED4830071A7F3 /* PBXTargetDependency */, 418 | ); 419 | name = FunRouterTests; 420 | productName = FunRouterTests; 421 | productReference = 48BC72101D7ED4830071A7F3 /* FunRouterTests.xctest */; 422 | productType = "com.apple.product-type.bundle.unit-test"; 423 | }; 424 | /* End PBXNativeTarget section */ 425 | 426 | /* Begin PBXProject section */ 427 | 48BC71EB1D7ED0AC0071A7F3 /* Project object */ = { 428 | isa = PBXProject; 429 | attributes = { 430 | LastSwiftUpdateCheck = 0800; 431 | LastUpgradeCheck = 0800; 432 | ORGANIZATIONNAME = "Yasuhiro Inami"; 433 | TargetAttributes = { 434 | 48BC71F31D7ED0AC0071A7F3 = { 435 | CreatedOnToolsVersion = 8.0; 436 | LastSwiftMigration = 0800; 437 | ProvisioningStyle = Manual; 438 | }; 439 | 48BC720F1D7ED4830071A7F3 = { 440 | CreatedOnToolsVersion = 8.0; 441 | ProvisioningStyle = Manual; 442 | }; 443 | }; 444 | }; 445 | buildConfigurationList = 48BC71EE1D7ED0AC0071A7F3 /* Build configuration list for PBXProject "FunRouter" */; 446 | compatibilityVersion = "Xcode 3.2"; 447 | developmentRegion = English; 448 | hasScannedForEncodings = 0; 449 | knownRegions = ( 450 | en, 451 | ); 452 | mainGroup = 48BC71EA1D7ED0AC0071A7F3; 453 | productRefGroup = 48BC71F51D7ED0AC0071A7F3 /* Products */; 454 | projectDirPath = ""; 455 | projectReferences = ( 456 | { 457 | ProductGroup = 486D11581D7ED9000043FF29 /* Products */; 458 | ProjectRef = 48BC72291D7ED6BE0071A7F3 /* Curry.xcodeproj */; 459 | }, 460 | { 461 | ProductGroup = 48BC74671D7ED6CC0071A7F3 /* Products */; 462 | ProjectRef = 48BC723F1D7ED6BE0071A7F3 /* Nimble.xcodeproj */; 463 | }, 464 | { 465 | ProductGroup = 486D116C1D7ED9010043FF29 /* Products */; 466 | ProjectRef = 48BC73AA1D7ED6BE0071A7F3 /* Quick.xcodeproj */; 467 | }, 468 | ); 469 | projectRoot = ""; 470 | targets = ( 471 | 48BC71F31D7ED0AC0071A7F3 /* FunRouter */, 472 | 48BC720F1D7ED4830071A7F3 /* FunRouterTests */, 473 | ); 474 | }; 475 | /* End PBXProject section */ 476 | 477 | /* Begin PBXReferenceProxy section */ 478 | 486D115F1D7ED9010043FF29 /* Curry.framework */ = { 479 | isa = PBXReferenceProxy; 480 | fileType = wrapper.framework; 481 | path = Curry.framework; 482 | remoteRef = 486D115E1D7ED9010043FF29 /* PBXContainerItemProxy */; 483 | sourceTree = BUILT_PRODUCTS_DIR; 484 | }; 485 | 486D11611D7ED9010043FF29 /* Curry.framework */ = { 486 | isa = PBXReferenceProxy; 487 | fileType = wrapper.framework; 488 | path = Curry.framework; 489 | remoteRef = 486D11601D7ED9010043FF29 /* PBXContainerItemProxy */; 490 | sourceTree = BUILT_PRODUCTS_DIR; 491 | }; 492 | 486D11631D7ED9010043FF29 /* Curry.framework */ = { 493 | isa = PBXReferenceProxy; 494 | fileType = wrapper.framework; 495 | path = Curry.framework; 496 | remoteRef = 486D11621D7ED9010043FF29 /* PBXContainerItemProxy */; 497 | sourceTree = BUILT_PRODUCTS_DIR; 498 | }; 499 | 486D11651D7ED9010043FF29 /* Curry.framework */ = { 500 | isa = PBXReferenceProxy; 501 | fileType = wrapper.framework; 502 | path = Curry.framework; 503 | remoteRef = 486D11641D7ED9010043FF29 /* PBXContainerItemProxy */; 504 | sourceTree = BUILT_PRODUCTS_DIR; 505 | }; 506 | 486D11781D7ED9010043FF29 /* Quick.framework */ = { 507 | isa = PBXReferenceProxy; 508 | fileType = wrapper.framework; 509 | path = Quick.framework; 510 | remoteRef = 486D11771D7ED9010043FF29 /* PBXContainerItemProxy */; 511 | sourceTree = BUILT_PRODUCTS_DIR; 512 | }; 513 | 486D117A1D7ED9010043FF29 /* Quick-macOSTests.xctest */ = { 514 | isa = PBXReferenceProxy; 515 | fileType = wrapper.cfbundle; 516 | path = "Quick-macOSTests.xctest"; 517 | remoteRef = 486D11791D7ED9010043FF29 /* PBXContainerItemProxy */; 518 | sourceTree = BUILT_PRODUCTS_DIR; 519 | }; 520 | 486D117C1D7ED9010043FF29 /* QuickFocused-macOSTests.xctest */ = { 521 | isa = PBXReferenceProxy; 522 | fileType = wrapper.cfbundle; 523 | path = "QuickFocused-macOSTests.xctest"; 524 | remoteRef = 486D117B1D7ED9010043FF29 /* PBXContainerItemProxy */; 525 | sourceTree = BUILT_PRODUCTS_DIR; 526 | }; 527 | 486D117E1D7ED9010043FF29 /* Quick.framework */ = { 528 | isa = PBXReferenceProxy; 529 | fileType = wrapper.framework; 530 | path = Quick.framework; 531 | remoteRef = 486D117D1D7ED9010043FF29 /* PBXContainerItemProxy */; 532 | sourceTree = BUILT_PRODUCTS_DIR; 533 | }; 534 | 486D11801D7ED9010043FF29 /* Quick-iOSTests.xctest */ = { 535 | isa = PBXReferenceProxy; 536 | fileType = wrapper.cfbundle; 537 | path = "Quick-iOSTests.xctest"; 538 | remoteRef = 486D117F1D7ED9010043FF29 /* PBXContainerItemProxy */; 539 | sourceTree = BUILT_PRODUCTS_DIR; 540 | }; 541 | 486D11821D7ED9010043FF29 /* QuickFocused-iOSTests.xctest */ = { 542 | isa = PBXReferenceProxy; 543 | fileType = wrapper.cfbundle; 544 | path = "QuickFocused-iOSTests.xctest"; 545 | remoteRef = 486D11811D7ED9010043FF29 /* PBXContainerItemProxy */; 546 | sourceTree = BUILT_PRODUCTS_DIR; 547 | }; 548 | 486D11841D7ED9010043FF29 /* Quick.framework */ = { 549 | isa = PBXReferenceProxy; 550 | fileType = wrapper.framework; 551 | path = Quick.framework; 552 | remoteRef = 486D11831D7ED9010043FF29 /* PBXContainerItemProxy */; 553 | sourceTree = BUILT_PRODUCTS_DIR; 554 | }; 555 | 486D11861D7ED9010043FF29 /* Quick-tvOSTests.xctest */ = { 556 | isa = PBXReferenceProxy; 557 | fileType = wrapper.cfbundle; 558 | path = "Quick-tvOSTests.xctest"; 559 | remoteRef = 486D11851D7ED9010043FF29 /* PBXContainerItemProxy */; 560 | sourceTree = BUILT_PRODUCTS_DIR; 561 | }; 562 | 486D11881D7ED9010043FF29 /* QuickFocused-tvOSTests.xctest */ = { 563 | isa = PBXReferenceProxy; 564 | fileType = wrapper.cfbundle; 565 | path = "QuickFocused-tvOSTests.xctest"; 566 | remoteRef = 486D11871D7ED9010043FF29 /* PBXContainerItemProxy */; 567 | sourceTree = BUILT_PRODUCTS_DIR; 568 | }; 569 | 48BC74701D7ED6CC0071A7F3 /* Nimble.framework */ = { 570 | isa = PBXReferenceProxy; 571 | fileType = wrapper.framework; 572 | path = Nimble.framework; 573 | remoteRef = 48BC746F1D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 574 | sourceTree = BUILT_PRODUCTS_DIR; 575 | }; 576 | 48BC74721D7ED6CC0071A7F3 /* NimbleTests.xctest */ = { 577 | isa = PBXReferenceProxy; 578 | fileType = wrapper.cfbundle; 579 | path = NimbleTests.xctest; 580 | remoteRef = 48BC74711D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 581 | sourceTree = BUILT_PRODUCTS_DIR; 582 | }; 583 | 48BC74741D7ED6CC0071A7F3 /* Nimble.framework */ = { 584 | isa = PBXReferenceProxy; 585 | fileType = wrapper.framework; 586 | path = Nimble.framework; 587 | remoteRef = 48BC74731D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 588 | sourceTree = BUILT_PRODUCTS_DIR; 589 | }; 590 | 48BC74761D7ED6CC0071A7F3 /* NimbleTests.xctest */ = { 591 | isa = PBXReferenceProxy; 592 | fileType = wrapper.cfbundle; 593 | path = NimbleTests.xctest; 594 | remoteRef = 48BC74751D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 595 | sourceTree = BUILT_PRODUCTS_DIR; 596 | }; 597 | 48BC74781D7ED6CC0071A7F3 /* Nimble.framework */ = { 598 | isa = PBXReferenceProxy; 599 | fileType = wrapper.framework; 600 | path = Nimble.framework; 601 | remoteRef = 48BC74771D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 602 | sourceTree = BUILT_PRODUCTS_DIR; 603 | }; 604 | 48BC747A1D7ED6CC0071A7F3 /* NimbleTests.xctest */ = { 605 | isa = PBXReferenceProxy; 606 | fileType = wrapper.cfbundle; 607 | path = NimbleTests.xctest; 608 | remoteRef = 48BC74791D7ED6CC0071A7F3 /* PBXContainerItemProxy */; 609 | sourceTree = BUILT_PRODUCTS_DIR; 610 | }; 611 | /* End PBXReferenceProxy section */ 612 | 613 | /* Begin PBXResourcesBuildPhase section */ 614 | 48BC71F21D7ED0AC0071A7F3 /* Resources */ = { 615 | isa = PBXResourcesBuildPhase; 616 | buildActionMask = 2147483647; 617 | files = ( 618 | ); 619 | runOnlyForDeploymentPostprocessing = 0; 620 | }; 621 | 48BC720E1D7ED4830071A7F3 /* Resources */ = { 622 | isa = PBXResourcesBuildPhase; 623 | buildActionMask = 2147483647; 624 | files = ( 625 | ); 626 | runOnlyForDeploymentPostprocessing = 0; 627 | }; 628 | /* End PBXResourcesBuildPhase section */ 629 | 630 | /* Begin PBXSourcesBuildPhase section */ 631 | 48BC71EF1D7ED0AC0071A7F3 /* Sources */ = { 632 | isa = PBXSourcesBuildPhase; 633 | buildActionMask = 2147483647; 634 | files = ( 635 | 48BC72041D7ED1A50071A7F3 /* Route2.swift in Sources */, 636 | 1F1BFFC61D9737A3000105EB /* FlipCurry.swift in Sources */, 637 | 48BC720A1D7ED2B30071A7F3 /* Operators.swift in Sources */, 638 | 48BC72031D7ED1A50071A7F3 /* Route1.swift in Sources */, 639 | 484D01481D7EFD910045601C /* FuncEquality.swift in Sources */, 640 | 48BC720B1D7ED2B30071A7F3 /* Prelude.swift in Sources */, 641 | ); 642 | runOnlyForDeploymentPostprocessing = 0; 643 | }; 644 | 48BC720C1D7ED4830071A7F3 /* Sources */ = { 645 | isa = PBXSourcesBuildPhase; 646 | buildActionMask = 2147483647; 647 | files = ( 648 | 48BC748F1D7ED8550071A7F3 /* Sitemap.swift in Sources */, 649 | 48BC721F1D7ED6A20071A7F3 /* Route1Spec.swift in Sources */, 650 | 48BC72201D7ED6A50071A7F3 /* Route2Spec.swift in Sources */, 651 | ); 652 | runOnlyForDeploymentPostprocessing = 0; 653 | }; 654 | /* End PBXSourcesBuildPhase section */ 655 | 656 | /* Begin PBXTargetDependency section */ 657 | 48BC72171D7ED4830071A7F3 /* PBXTargetDependency */ = { 658 | isa = PBXTargetDependency; 659 | target = 48BC71F31D7ED0AC0071A7F3 /* FunRouter */; 660 | targetProxy = 48BC72161D7ED4830071A7F3 /* PBXContainerItemProxy */; 661 | }; 662 | /* End PBXTargetDependency section */ 663 | 664 | /* Begin XCBuildConfiguration section */ 665 | 48BC71FA1D7ED0AC0071A7F3 /* Debug */ = { 666 | isa = XCBuildConfiguration; 667 | baseConfigurationReference = 48BC747E1D7ED7190071A7F3 /* Debug.xcconfig */; 668 | buildSettings = { 669 | ALWAYS_SEARCH_USER_PATHS = NO; 670 | CLANG_ANALYZER_NONNULL = YES; 671 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 672 | CLANG_CXX_LIBRARY = "libc++"; 673 | CLANG_ENABLE_MODULES = YES; 674 | CLANG_ENABLE_OBJC_ARC = YES; 675 | CLANG_WARN_BOOL_CONVERSION = YES; 676 | CLANG_WARN_CONSTANT_CONVERSION = YES; 677 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 678 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 679 | CLANG_WARN_EMPTY_BODY = YES; 680 | CLANG_WARN_ENUM_CONVERSION = YES; 681 | CLANG_WARN_INFINITE_RECURSION = YES; 682 | CLANG_WARN_INT_CONVERSION = YES; 683 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 684 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 685 | CLANG_WARN_UNREACHABLE_CODE = YES; 686 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 687 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 688 | COPY_PHASE_STRIP = NO; 689 | CURRENT_PROJECT_VERSION = 1; 690 | DEBUG_INFORMATION_FORMAT = dwarf; 691 | ENABLE_STRICT_OBJC_MSGSEND = YES; 692 | ENABLE_TESTABILITY = YES; 693 | GCC_C_LANGUAGE_STANDARD = gnu99; 694 | GCC_DYNAMIC_NO_PIC = NO; 695 | GCC_NO_COMMON_BLOCKS = YES; 696 | GCC_OPTIMIZATION_LEVEL = 0; 697 | GCC_PREPROCESSOR_DEFINITIONS = ( 698 | "DEBUG=1", 699 | "$(inherited)", 700 | ); 701 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 702 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 703 | GCC_WARN_UNDECLARED_SELECTOR = YES; 704 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 705 | GCC_WARN_UNUSED_FUNCTION = YES; 706 | GCC_WARN_UNUSED_VARIABLE = YES; 707 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 708 | MTL_ENABLE_DEBUG_INFO = YES; 709 | ONLY_ACTIVE_ARCH = YES; 710 | SDKROOT = iphoneos; 711 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 712 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 713 | TARGETED_DEVICE_FAMILY = "1,2"; 714 | VERSIONING_SYSTEM = "apple-generic"; 715 | VERSION_INFO_PREFIX = ""; 716 | }; 717 | name = Debug; 718 | }; 719 | 48BC71FB1D7ED0AC0071A7F3 /* Release */ = { 720 | isa = XCBuildConfiguration; 721 | baseConfigurationReference = 48BC747F1D7ED7190071A7F3 /* Release.xcconfig */; 722 | buildSettings = { 723 | ALWAYS_SEARCH_USER_PATHS = NO; 724 | CLANG_ANALYZER_NONNULL = YES; 725 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 726 | CLANG_CXX_LIBRARY = "libc++"; 727 | CLANG_ENABLE_MODULES = YES; 728 | CLANG_ENABLE_OBJC_ARC = YES; 729 | CLANG_WARN_BOOL_CONVERSION = YES; 730 | CLANG_WARN_CONSTANT_CONVERSION = YES; 731 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 732 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 733 | CLANG_WARN_EMPTY_BODY = YES; 734 | CLANG_WARN_ENUM_CONVERSION = YES; 735 | CLANG_WARN_INFINITE_RECURSION = YES; 736 | CLANG_WARN_INT_CONVERSION = YES; 737 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 738 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 739 | CLANG_WARN_UNREACHABLE_CODE = YES; 740 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 741 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 742 | COPY_PHASE_STRIP = NO; 743 | CURRENT_PROJECT_VERSION = 1; 744 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 745 | ENABLE_NS_ASSERTIONS = NO; 746 | ENABLE_STRICT_OBJC_MSGSEND = YES; 747 | GCC_C_LANGUAGE_STANDARD = gnu99; 748 | GCC_NO_COMMON_BLOCKS = YES; 749 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 750 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 751 | GCC_WARN_UNDECLARED_SELECTOR = YES; 752 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 753 | GCC_WARN_UNUSED_FUNCTION = YES; 754 | GCC_WARN_UNUSED_VARIABLE = YES; 755 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 756 | MTL_ENABLE_DEBUG_INFO = NO; 757 | SDKROOT = iphoneos; 758 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 759 | TARGETED_DEVICE_FAMILY = "1,2"; 760 | VALIDATE_PRODUCT = YES; 761 | VERSIONING_SYSTEM = "apple-generic"; 762 | VERSION_INFO_PREFIX = ""; 763 | }; 764 | name = Release; 765 | }; 766 | 48BC71FD1D7ED0AC0071A7F3 /* Debug */ = { 767 | isa = XCBuildConfiguration; 768 | baseConfigurationReference = 48BC741A1D7ED6BE0071A7F3 /* UniversalFramework_Framework.xcconfig */; 769 | buildSettings = { 770 | CLANG_ENABLE_MODULES = YES; 771 | CODE_SIGN_IDENTITY = ""; 772 | DEFINES_MODULE = YES; 773 | DEVELOPMENT_TEAM = ""; 774 | DYLIB_COMPATIBILITY_VERSION = 1; 775 | DYLIB_CURRENT_VERSION = 1; 776 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 777 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 778 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 779 | PRODUCT_BUNDLE_IDENTIFIER = com.inamiy.FunRouter; 780 | PRODUCT_NAME = "$(TARGET_NAME)"; 781 | SKIP_INSTALL = YES; 782 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 783 | SWIFT_VERSION = 3.0; 784 | }; 785 | name = Debug; 786 | }; 787 | 48BC71FE1D7ED0AC0071A7F3 /* Release */ = { 788 | isa = XCBuildConfiguration; 789 | baseConfigurationReference = 48BC741A1D7ED6BE0071A7F3 /* UniversalFramework_Framework.xcconfig */; 790 | buildSettings = { 791 | CLANG_ENABLE_MODULES = YES; 792 | CODE_SIGN_IDENTITY = ""; 793 | DEFINES_MODULE = YES; 794 | DEVELOPMENT_TEAM = ""; 795 | DYLIB_COMPATIBILITY_VERSION = 1; 796 | DYLIB_CURRENT_VERSION = 1; 797 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 798 | INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; 799 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 800 | PRODUCT_BUNDLE_IDENTIFIER = com.inamiy.FunRouter; 801 | PRODUCT_NAME = "$(TARGET_NAME)"; 802 | SKIP_INSTALL = YES; 803 | SWIFT_VERSION = 3.0; 804 | }; 805 | name = Release; 806 | }; 807 | 48BC72191D7ED4830071A7F3 /* Debug */ = { 808 | isa = XCBuildConfiguration; 809 | baseConfigurationReference = 48BC741B1D7ED6BE0071A7F3 /* UniversalFramework_Test.xcconfig */; 810 | buildSettings = { 811 | DEVELOPMENT_TEAM = ""; 812 | INFOPLIST_FILE = Specs/Info.plist; 813 | PRODUCT_BUNDLE_IDENTIFIER = com.inamiy.FunRouterTests; 814 | PRODUCT_NAME = "$(TARGET_NAME)"; 815 | SWIFT_VERSION = 3.0; 816 | }; 817 | name = Debug; 818 | }; 819 | 48BC721A1D7ED4830071A7F3 /* Release */ = { 820 | isa = XCBuildConfiguration; 821 | baseConfigurationReference = 48BC741B1D7ED6BE0071A7F3 /* UniversalFramework_Test.xcconfig */; 822 | buildSettings = { 823 | DEVELOPMENT_TEAM = ""; 824 | INFOPLIST_FILE = Specs/Info.plist; 825 | PRODUCT_BUNDLE_IDENTIFIER = com.inamiy.FunRouterTests; 826 | PRODUCT_NAME = "$(TARGET_NAME)"; 827 | SWIFT_VERSION = 3.0; 828 | }; 829 | name = Release; 830 | }; 831 | /* End XCBuildConfiguration section */ 832 | 833 | /* Begin XCConfigurationList section */ 834 | 48BC71EE1D7ED0AC0071A7F3 /* Build configuration list for PBXProject "FunRouter" */ = { 835 | isa = XCConfigurationList; 836 | buildConfigurations = ( 837 | 48BC71FA1D7ED0AC0071A7F3 /* Debug */, 838 | 48BC71FB1D7ED0AC0071A7F3 /* Release */, 839 | ); 840 | defaultConfigurationIsVisible = 0; 841 | defaultConfigurationName = Release; 842 | }; 843 | 48BC71FC1D7ED0AC0071A7F3 /* Build configuration list for PBXNativeTarget "FunRouter" */ = { 844 | isa = XCConfigurationList; 845 | buildConfigurations = ( 846 | 48BC71FD1D7ED0AC0071A7F3 /* Debug */, 847 | 48BC71FE1D7ED0AC0071A7F3 /* Release */, 848 | ); 849 | defaultConfigurationIsVisible = 0; 850 | defaultConfigurationName = Release; 851 | }; 852 | 48BC72181D7ED4830071A7F3 /* Build configuration list for PBXNativeTarget "FunRouterTests" */ = { 853 | isa = XCConfigurationList; 854 | buildConfigurations = ( 855 | 48BC72191D7ED4830071A7F3 /* Debug */, 856 | 48BC721A1D7ED4830071A7F3 /* Release */, 857 | ); 858 | defaultConfigurationIsVisible = 0; 859 | defaultConfigurationName = Release; 860 | }; 861 | /* End XCConfigurationList section */ 862 | }; 863 | rootObject = 48BC71EB1D7ED0AC0071A7F3 /* Project object */; 864 | } 865 | -------------------------------------------------------------------------------- /FunRouter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FunRouter.xcodeproj/xcshareddata/xcschemes/FunRouter.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yasuhiro Inami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunRouter 2 | 3 | Functional & type-safe URL routing example for [Functional Swift Conference 2016](http://2016.funswiftconf.com/). 4 | 5 | There are following route types available. 6 | 7 | - `Route1` (struct, function-based) 8 | - `Route2` (enum, optimizable) 9 | 10 | 11 | ## Example 12 | 13 | Assume `typealias Route = RouteN` (`N = 1, 2`). 14 | 15 | ```swift 16 | /// Parses "/Hello/{city}/{year}". 17 | let route: Route<(String, Int)> = 18 | curry { ($1, $2) } <^> match("Hello") <*> string() <*> int() 19 | 20 | // or, more fancy flipped way: 21 | // let route = 22 | // /"Hello" string() int() <&!> flipCurry { ($1, $2) } 23 | 24 | let value1 = route.run(["Hello", "Budapest", "2016"]) 25 | expect(value1?.0) == "Budapest" 26 | expect(value1?.1) == 2016 27 | 28 | let value2 = route.run(["Ya", "tu", "sabes"]) 29 | expect(value2).to(beNil()) 30 | ``` 31 | 32 | ### More complex routing 33 | 34 | ```swift 35 | enum Sitemap { 36 | case foo(Int) // "/R/foo/{Int}" 37 | case bar(Double) // "/R/bar/{Double}" 38 | case baz(String) // "/R/baz/{String}" 39 | case fooBar // "/R/foo/bar" 40 | case notFound 41 | } 42 | 43 | let route: Route = 44 | match("R") 45 | *> choice([ 46 | match("foo") *> int() <&> Sitemap.foo, 47 | match("bar") *> double() <&> Sitemap.bar, 48 | match("baz") *> string() <&> Sitemap.baz, 49 | match("foo") *> match("bar") *> pure(Sitemap.fooBar), 50 | ]) 51 | <|> pure(Sitemap.notFound) 52 | 53 | expect(route.run(["R", "foo", "123"])) == .foo(123) 54 | expect(route.run(["R", "bar", "4.5"])) == .bar(4.5) 55 | expect(route.run(["R", "baz", "xyz"])) == .baz("xyz") 56 | expect(route.run(["R", "foo", "bar"])) == .fooBar 57 | expect(route.run(["R", "foo", "xxx"])) == .notFound 58 | ``` 59 | 60 | ### Optimization 61 | 62 | For `Route2`, you can call `optimize()` so that: 63 | 64 | ```swift 65 | // Before optimization (duplicated `match("foo")`) 66 | let naiveRoute: Route = 67 | match("R") 68 | *> choice([ 69 | match("foo") *> int() <&> Sitemap.foo, 70 | match("bar") *> double() <&> Sitemap.bar, 71 | match("baz") *> string() <&> Sitemap.baz, 72 | match("foo") *> match("bar") *> pure(Sitemap.fooBar) 73 | ]) 74 | 75 | // After optimization 76 | let optimizedRoute = optimize(naiveRoute) 77 | ``` 78 | 79 | will have an optimized structure as: 80 | 81 | ```swift 82 | naiveRoute = .match("R", .choice([ 83 | .match("foo", .capture(int)), 84 | .match("bar", .capture(double)), 85 | .match("baz", .capture(string)), 86 | .match("foo", .match("bar", .term(fooBar))) 87 | ])) 88 | 89 | optimizedRoute = .match("R", .choice([ 90 | .match("bar", .capture(double)), 91 | .match("baz", .capture(string)), 92 | .match("foo", .choice([ 93 | .capture(int), 94 | .match("bar", .term(fooBar)) 95 | ])) 96 | ])) 97 | ``` 98 | 99 | 100 | ## References 101 | 102 | - Talk 103 | - [Yasuhiro Inami - Type-safe URL routing in Swift - YouTube](https://www.youtube.com/watch?v=RJy_2Si6Bpw) (video) 104 | - [Type-safe URL Routing in Swift // Speaker Deck](https://speakerdeck.com/inamiy/type-safe-url-routing-in-swift) (slide) 105 | - (Related) Parser Combinator 106 | - [Parser Combinators in Swift](https://realm.io/news/tryswift-yasuhiro-inami-parser-combinator/) (video) 107 | - [Parser Combinator in Swift // Speaker Deck](https://speakerdeck.com/inamiy/parser-combinator-in-swift) (slide) 108 | 109 | 110 | ## License 111 | 112 | [MIT](LICENSE) 113 | -------------------------------------------------------------------------------- /Scripts/generate-flip-curry.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun swift 2 | 3 | // From https://github.com/thoughtbot/Curry 4 | 5 | // Generates a Swift file with implementation of function currying for a ridicolously high number of arguments 6 | 7 | import Foundation 8 | 9 | let generics = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] 10 | 11 | extension Array { 12 | subscript(safe index: Int) -> Element? { 13 | return indices ~= index ? self[index] : .none 14 | } 15 | } 16 | 17 | func genericType(for position: Int) -> String { 18 | let max = generics.count 19 | switch position { 20 | case _ where position < max: return generics[position % max] 21 | default: return generics[position / max - 1] + generics[position % max] 22 | } 23 | } 24 | 25 | func commaConcat(_ xs: [String]) -> String { 26 | return xs.joined(separator: ", ") 27 | } 28 | 29 | func singleParameterFunctions(_ xs: [String]) -> String { 30 | guard let first = xs.first else { fatalError("Attempted to nest functions with no arguments") } 31 | guard xs.last != first else { return first } 32 | let remainder = Array(xs.dropFirst()) 33 | return "(\(first)) -> \(singleParameterFunctions(remainder))" 34 | } 35 | 36 | func curryDefinitionGenerator(arguments: Int) -> String { 37 | let genericParameters = (0.." 39 | 40 | let inputParameters = Array(genericParameters[0.. (B) -> (A) -> D" 50 | let innerFunctionArguments = commaConcat(lowerFunctionArguments) // "a, b, c" 51 | 52 | let functionDefinition = "function(\(innerFunctionArguments))" // function(a, b, c) 53 | 54 | let implementation = reversedLowerFunctionArguments.enumerated().reversed().reduce(functionDefinition) { accum, tuple in 55 | let (index, argument) = tuple 56 | let functionParameters = Array(reversedGenericParameters.suffix(from: index + 1)) 57 | return "{ (\(argument): \(reversedInputParameters[index])) -> \(singleParameterFunctions(functionParameters)) in \(accum) }" 58 | } // "{ (a: A) -> (B) -> (C) -> D in { (b: B) -> (C) -> D in { (c: C) -> D in function(a, b, c) } } }" 59 | 60 | let curry = [ 61 | "public func flipCurry\(genericTypeDefinition)(_ function: @escaping \(functionArguments) -> \(returnType)) -> \(returnFunction) {", 62 | " return \(implementation)", 63 | "}" 64 | ] 65 | 66 | return curry.joined(separator: "\n") 67 | } 68 | 69 | print("Generating 💬") 70 | 71 | let input = CommandLine.arguments[safe: 1] ?? "20" 72 | let limit = Int(input)! 73 | 74 | let start = 2 75 | let curries = (start...limit+1).map { curryDefinitionGenerator(arguments: $0) } 76 | 77 | let output = curries.joined(separator: "\n\n") + "\n" 78 | 79 | let outputPath = "FlipCurry.swift" 80 | let currentPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) 81 | let currySwiftPath = currentPath.appendingPathComponent(outputPath) 82 | do { 83 | try output.write(to: currySwiftPath, atomically: true, encoding: String.Encoding.utf8) 84 | print("Done, curry functions files written at \(outputPath) 👍") 85 | } catch let e as NSError { 86 | print("An error occurred while saving the generated functions. Error: \(e)") 87 | } 88 | -------------------------------------------------------------------------------- /Sources/Base/FlipCurry.swift: -------------------------------------------------------------------------------- 1 | public func flipCurry(_ function: @escaping (A) -> B) -> (A) -> B { 2 | return { (a: A) -> B in function(a) } 3 | } 4 | 5 | public func flipCurry(_ function: @escaping (A, B) -> C) -> (B) -> (A) -> C { 6 | return { (b: B) -> (A) -> C in { (a: A) -> C in function(a, b) } } 7 | } 8 | 9 | public func flipCurry(_ function: @escaping (A, B, C) -> D) -> (C) -> (B) -> (A) -> D { 10 | return { (c: C) -> (B) -> (A) -> D in { (b: B) -> (A) -> D in { (a: A) -> D in function(a, b, c) } } } 11 | } 12 | 13 | public func flipCurry(_ function: @escaping (A, B, C, D) -> E) -> (D) -> (C) -> (B) -> (A) -> E { 14 | return { (d: D) -> (C) -> (B) -> (A) -> E in { (c: C) -> (B) -> (A) -> E in { (b: B) -> (A) -> E in { (a: A) -> E in function(a, b, c, d) } } } } 15 | } 16 | 17 | public func flipCurry(_ function: @escaping (A, B, C, D, E) -> F) -> (E) -> (D) -> (C) -> (B) -> (A) -> F { 18 | return { (e: E) -> (D) -> (C) -> (B) -> (A) -> F in { (d: D) -> (C) -> (B) -> (A) -> F in { (c: C) -> (B) -> (A) -> F in { (b: B) -> (A) -> F in { (a: A) -> F in function(a, b, c, d, e) } } } } } 19 | } 20 | 21 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F) -> G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> G { 22 | return { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> G in { (e: E) -> (D) -> (C) -> (B) -> (A) -> G in { (d: D) -> (C) -> (B) -> (A) -> G in { (c: C) -> (B) -> (A) -> G in { (b: B) -> (A) -> G in { (a: A) -> G in function(a, b, c, d, e, f) } } } } } } 23 | } 24 | 25 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G) -> H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> H { 26 | return { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> H in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> H in { (e: E) -> (D) -> (C) -> (B) -> (A) -> H in { (d: D) -> (C) -> (B) -> (A) -> H in { (c: C) -> (B) -> (A) -> H in { (b: B) -> (A) -> H in { (a: A) -> H in function(a, b, c, d, e, f, g) } } } } } } } 27 | } 28 | 29 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H) -> I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> I { 30 | return { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> I in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> I in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> I in { (e: E) -> (D) -> (C) -> (B) -> (A) -> I in { (d: D) -> (C) -> (B) -> (A) -> I in { (c: C) -> (B) -> (A) -> I in { (b: B) -> (A) -> I in { (a: A) -> I in function(a, b, c, d, e, f, g, h) } } } } } } } } 31 | } 32 | 33 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I) -> J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> J { 34 | return { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> J in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> J in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> J in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> J in { (e: E) -> (D) -> (C) -> (B) -> (A) -> J in { (d: D) -> (C) -> (B) -> (A) -> J in { (c: C) -> (B) -> (A) -> J in { (b: B) -> (A) -> J in { (a: A) -> J in function(a, b, c, d, e, f, g, h, i) } } } } } } } } } 35 | } 36 | 37 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J) -> K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K { 38 | return { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> K in { (e: E) -> (D) -> (C) -> (B) -> (A) -> K in { (d: D) -> (C) -> (B) -> (A) -> K in { (c: C) -> (B) -> (A) -> K in { (b: B) -> (A) -> K in { (a: A) -> K in function(a, b, c, d, e, f, g, h, i, j) } } } } } } } } } } 39 | } 40 | 41 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K) -> L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L { 42 | return { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> L in { (e: E) -> (D) -> (C) -> (B) -> (A) -> L in { (d: D) -> (C) -> (B) -> (A) -> L in { (c: C) -> (B) -> (A) -> L in { (b: B) -> (A) -> L in { (a: A) -> L in function(a, b, c, d, e, f, g, h, i, j, k) } } } } } } } } } } } 43 | } 44 | 45 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L) -> M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M { 46 | return { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> M in { (e: E) -> (D) -> (C) -> (B) -> (A) -> M in { (d: D) -> (C) -> (B) -> (A) -> M in { (c: C) -> (B) -> (A) -> M in { (b: B) -> (A) -> M in { (a: A) -> M in function(a, b, c, d, e, f, g, h, i, j, k, l) } } } } } } } } } } } } 47 | } 48 | 49 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M) -> N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N { 50 | return { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> N in { (e: E) -> (D) -> (C) -> (B) -> (A) -> N in { (d: D) -> (C) -> (B) -> (A) -> N in { (c: C) -> (B) -> (A) -> N in { (b: B) -> (A) -> N in { (a: A) -> N in function(a, b, c, d, e, f, g, h, i, j, k, l, m) } } } } } } } } } } } } } 51 | } 52 | 53 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N) -> O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O { 54 | return { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> O in { (e: E) -> (D) -> (C) -> (B) -> (A) -> O in { (d: D) -> (C) -> (B) -> (A) -> O in { (c: C) -> (B) -> (A) -> O in { (b: B) -> (A) -> O in { (a: A) -> O in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n) } } } } } } } } } } } } } } 55 | } 56 | 57 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) -> P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P { 58 | return { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> P in { (e: E) -> (D) -> (C) -> (B) -> (A) -> P in { (d: D) -> (C) -> (B) -> (A) -> P in { (c: C) -> (B) -> (A) -> P in { (b: B) -> (A) -> P in { (a: A) -> P in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) } } } } } } } } } } } } } } } 59 | } 60 | 61 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) -> Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q { 62 | return { (p: P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> Q in { (e: E) -> (D) -> (C) -> (B) -> (A) -> Q in { (d: D) -> (C) -> (B) -> (A) -> Q in { (c: C) -> (B) -> (A) -> Q in { (b: B) -> (A) -> Q in { (a: A) -> Q in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) } } } } } } } } } } } } } } } } 63 | } 64 | 65 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) -> R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R { 66 | return { (q: Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (p: P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> R in { (e: E) -> (D) -> (C) -> (B) -> (A) -> R in { (d: D) -> (C) -> (B) -> (A) -> R in { (c: C) -> (B) -> (A) -> R in { (b: B) -> (A) -> R in { (a: A) -> R in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q) } } } } } } } } } } } } } } } } } 67 | } 68 | 69 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R) -> S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S { 70 | return { (r: R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (q: Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (p: P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> S in { (e: E) -> (D) -> (C) -> (B) -> (A) -> S in { (d: D) -> (C) -> (B) -> (A) -> S in { (c: C) -> (B) -> (A) -> S in { (b: B) -> (A) -> S in { (a: A) -> S in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r) } } } } } } } } } } } } } } } } } } 71 | } 72 | 73 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S) -> T) -> (S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T { 74 | return { (s: S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (r: R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (q: Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (p: P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> T in { (e: E) -> (D) -> (C) -> (B) -> (A) -> T in { (d: D) -> (C) -> (B) -> (A) -> T in { (c: C) -> (B) -> (A) -> T in { (b: B) -> (A) -> T in { (a: A) -> T in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) } } } } } } } } } } } } } } } } } } } 75 | } 76 | 77 | public func flipCurry(_ function: @escaping (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T) -> U) -> (T) -> (S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U { 78 | return { (t: T) -> (S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (s: S) -> (R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (r: R) -> (Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (q: Q) -> (P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (p: P) -> (O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (o: O) -> (N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (n: N) -> (M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (m: M) -> (L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (l: L) -> (K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (k: K) -> (J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (j: J) -> (I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (i: I) -> (H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (h: H) -> (G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (g: G) -> (F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (f: F) -> (E) -> (D) -> (C) -> (B) -> (A) -> U in { (e: E) -> (D) -> (C) -> (B) -> (A) -> U in { (d: D) -> (C) -> (B) -> (A) -> U in { (c: C) -> (B) -> (A) -> U in { (b: B) -> (A) -> U in { (a: A) -> U in function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) } } } } } } } } } } } } } } } } } } } } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Base/FuncEquality.swift: -------------------------------------------------------------------------------- 1 | // Hacky Function equality 2 | // - https://gist.github.com/dankogai/b03319ce427544beb5a4 3 | // - http://qiita.com/dankogai/items/ab407918dba590016058 (Japanese) 4 | 5 | internal func peekFunc(_ f: (A) -> R) -> (fp: Int, ctx: Int) 6 | { 7 | let (_, low) = unsafeBitCast(f, to: (Int, Int).self) 8 | let offset = MemoryLayout.size == 8 ? 16 : 12 // 8 bit * 2 pointers, or 4 bit * 3 pointers 9 | let ptr = UnsafePointer(bitPattern: low + offset) 10 | return (ptr!.pointee, ptr!.successor().pointee) 11 | } 12 | 13 | internal func === (lhs: (A) -> R, rhs: (A) -> R) -> Bool 14 | { 15 | let (tl, tr) = (peekFunc(lhs), peekFunc(rhs)) 16 | return tl.0 == tr.0 && tl.1 == tr.1 17 | } 18 | 19 | internal func < (_ lhs: (A) -> R, _ rhs: (A) -> R) -> Bool 20 | { 21 | let ((fp1, ctx1), (fp2, ctx2)) = (peekFunc(lhs), peekFunc(rhs)) 22 | if fp1 == fp2 { 23 | return ctx1 < ctx2 24 | } 25 | else { 26 | return fp1 < fp2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Base/Operators.swift: -------------------------------------------------------------------------------- 1 | /// infixr 1 2 | precedencegroup Composition { associativity: right } 3 | infix operator >>> : Composition 4 | infix operator <<< : Composition 5 | 6 | /// infixl 1 7 | precedencegroup Monad { associativity: left higherThan: FunctorFlipped } 8 | infix operator >>- : Monad 9 | 10 | /// infixl 3 11 | precedencegroup Alternative { associativity: left higherThan: Monad } 12 | infix operator <|> : Alternative 13 | 14 | /// Infixl 4 15 | precedencegroup Applicative { associativity: left higherThan: Alternative } 16 | infix operator <*> : Applicative 17 | infix operator <**> : Applicative 18 | infix operator <* : Applicative 19 | infix operator *> : Applicative 20 | 21 | /// infixl 4 22 | precedencegroup Functor { associativity: left higherThan: Applicative } 23 | infix operator <^> : Functor 24 | infix operator <^ : Functor 25 | infix operator ^> : Functor 26 | 27 | /// infixl 1 (defined in Control.Lens) 28 | precedencegroup FunctorFlipped { associativity: left higherThan: Composition } 29 | infix operator <&> : FunctorFlipped 30 | 31 | precedencegroup FunctorFlippedHigh { associativity: left higherThan: ApplicativeRight } 32 | infix operator <&!> : FunctorFlippedHigh 33 | 34 | precedencegroup ApplicativeRight { associativity: right higherThan: Applicative } 35 | infix operator : ApplicativeRight 36 | 37 | prefix operator / 38 | -------------------------------------------------------------------------------- /Sources/Base/Prelude.swift: -------------------------------------------------------------------------------- 1 | /// Identity function. 2 | internal func id(_ a: A) -> A 3 | { 4 | return a 5 | } 6 | 7 | /// Constant function. 8 | internal func const(_ a: A) -> (B) -> A 9 | { 10 | return { _ in a } 11 | } 12 | 13 | /// Unary negation. 14 | internal func negate(_ x: N) -> N 15 | { 16 | return -x 17 | } 18 | 19 | /// Haskell `(:)` (cons operator) for replacing slow `[x] + xs`. 20 | internal func cons(_ x: C.Iterator.Element) -> (C) -> C 21 | { 22 | return { xs in 23 | var xs = xs 24 | xs.insert(x, at: xs.startIndex) 25 | return xs 26 | } 27 | } 28 | 29 | /// Extracts head and tail of `Collection`, returning nil if it is empty. 30 | internal func uncons(_ xs: C) -> (C.Iterator.Element, C.SubSequence)? 31 | { 32 | if let head = xs.first { 33 | let secondIndex = xs.index(after: xs.startIndex) 34 | return (head, xs.suffix(from: secondIndex)) 35 | } 36 | else { 37 | return nil 38 | } 39 | } 40 | 41 | /// `splitAt(count)(xs)` returns a tuple of `xs.prefix(upTo: count)` and `suffix(from: count)`, 42 | /// but either of those may be empty. 43 | /// - Precondition: `count >= 0` 44 | internal func splitAt(_ count: C.IndexDistance) -> (C) -> (C.SubSequence, C.SubSequence) 45 | { 46 | precondition(count >= 0, "`splitAt(count)` must have `count >= 0`.") 47 | 48 | return { xs in 49 | let midIndex = xs.index(xs.startIndex, offsetBy: count) 50 | if count <= xs.count { 51 | return (xs.prefix(upTo: midIndex), xs.suffix(from: midIndex)) 52 | } 53 | else { 54 | return (xs.prefix(upTo: midIndex), xs.suffix(0)) 55 | } 56 | } 57 | } 58 | 59 | /// Haskell's `mapMaybe`. 60 | internal func filterMap(_ f: @escaping (C.Iterator.Element) -> Element2?) -> (C) -> [Element2] 61 | { 62 | return { xs in 63 | guard !xs.isEmpty else { return [] } 64 | 65 | var ys = [Element2]() 66 | for x in xs { 67 | if let y = f(x) { 68 | ys.append(y) 69 | } 70 | } 71 | return ys 72 | } 73 | } 74 | 75 | /// `span(predicate)(xs)` returns a tuple where first element is longest prefix (possibly empty) of `xs` 76 | /// that satisfy `predicate` and second element is the remainder of `xs`. 77 | internal func span(_ predicate: @escaping (C.Iterator.Element) -> Bool) -> (C) -> (C, C) 78 | where C.SubSequence == C 79 | { 80 | return { xs in 81 | if let (x, xs2) = uncons(xs), predicate(x) { 82 | let (ys, zs) = span(predicate)(xs2) 83 | return (cons(x)(ys), zs) 84 | } 85 | else { 86 | return (C(), xs) 87 | } 88 | } 89 | } 90 | 91 | /// `groupBy(eq)(xs)` returns an array of `RangeReplaceableCollection` 92 | /// where first element in `xs` is tested and concatenated with next elements while `eq` is satisfied, 93 | /// then use its remainder to repeat until all elements are tested. 94 | internal func groupBy(_ eq: @escaping (C.Iterator.Element) -> (C.Iterator.Element) -> Bool) -> (C) -> [C] 95 | where C.SubSequence == C 96 | { 97 | return { xs in 98 | if let (x, xs2) = uncons(xs) { 99 | let (ys, zs) = span(eq(x))(xs2) 100 | return cons(cons(x)(ys))(groupBy(eq)(zs)) 101 | } 102 | else { 103 | return [] 104 | } 105 | } 106 | } 107 | 108 | /// Flips 1st and 2nd argument of binary operation. 109 | internal func flip(_ f: @escaping (A) -> (B) -> C) -> (B) -> (A) -> C 110 | { 111 | return { b in { a in f(a)(b) } } 112 | } 113 | 114 | /// Right-to-left composition. 115 | internal func <<< (f: @escaping (B) -> C, g: @escaping (A) -> B) -> (A) -> C 116 | { 117 | return { f(g($0)) } 118 | } 119 | 120 | /// Left-to-right composition. 121 | internal func >>> (f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C 122 | { 123 | return { g(f($0)) } 124 | } 125 | 126 | /// Fixed-point combinator. 127 | internal func fix(_ f: @escaping ((T) -> U) -> (T) -> U) -> (T) -> U 128 | { 129 | return { f(fix(f))($0) } 130 | } 131 | 132 | /// Haskell's `error`. 133 | internal func undefined(_ hint: String = "", file: StaticString = #file, line: UInt = #line) -> T 134 | { 135 | let message = hint == "" ? "" : ": \(hint)" 136 | fatalError("undefined \(T.self)\(message)", file: file, line: line) 137 | } 138 | -------------------------------------------------------------------------------- /Sources/FunRouter.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for FunRouter. 4 | FOUNDATION_EXPORT double FunRouterVersionNumber; 5 | 6 | //! Project version string for FunRouter. 7 | FOUNDATION_EXPORT const unsigned char FunRouterVersionString[]; 8 | 9 | // In this header, you should import all the public headers of your framework using statements like #import 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/Route1.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Function-based URL route. 4 | public struct Route1 5 | { 6 | let parse: ([String]) -> ([String], Value)? 7 | } 8 | 9 | extension Route1 10 | { 11 | public func run(_ pathComponents: [String]) -> Value? { 12 | return self.parse(pathComponents).map { $0.1 } 13 | } 14 | } 15 | 16 | // MARK: Alternative 17 | 18 | public func empty() -> Route1 19 | { 20 | return Route1 { _ in nil } 21 | } 22 | 23 | public func <|> (route1: Route1, route2: @autoclosure @escaping () -> Route1) -> Route1 24 | { 25 | return Route1 { pathComponents in 26 | let reply = route1.parse(pathComponents) 27 | switch reply { 28 | case .some: 29 | return reply 30 | case .none: 31 | return route2().parse(pathComponents) 32 | } 33 | } 34 | } 35 | 36 | // MARK: Applicative 37 | 38 | public func pure(_ value: Value) -> Route1 39 | { 40 | return Route1 { ($0, value) } 41 | } 42 | 43 | public func <*> (route1: Route1<(Value1) -> Value2>, route2: @autoclosure @escaping () -> Route1) -> Route1 44 | { 45 | return Route1 { pathComponents in 46 | let reply = route1.parse(pathComponents) 47 | switch reply { 48 | case let .some(pathComponents2, f): 49 | return (f <^> route2()).parse(pathComponents2) 50 | case .none: 51 | return nil 52 | } 53 | } 54 | } 55 | 56 | public func <**> (route1: Route1, route2: @autoclosure @escaping () -> Route1<(Value1) -> Value2>) -> Route1 57 | { 58 | return { a in { $0(a) } } <^> route1 <*> route2 59 | } 60 | 61 | public func <* (route1: Route1, route2: @autoclosure @escaping () -> Route1) -> Route1 62 | { 63 | return const <^> route1 <*> route2 64 | } 65 | 66 | public func *> (route1: Route1, route2: @autoclosure @escaping () -> Route1) -> Route1 67 | { 68 | return const(id) <^> route1 <*> route2 69 | } 70 | 71 | // MARK: Functor 72 | 73 | public func <^> (f: @escaping (Value1) -> Value2, route: Route1) -> Route1 74 | { 75 | return Route1 { pathComponents in 76 | let reply = route.parse(pathComponents) 77 | switch reply { 78 | case let .some(pathComponents2, value): 79 | return (pathComponents2, f(value)) 80 | case .none: 81 | return nil 82 | } 83 | } 84 | } 85 | 86 | public func <^ (value: Value2, route: Route1) -> Route1 87 | { 88 | return const(value) <^> route 89 | } 90 | 91 | public func ^> (route: Route1, value: Value2) -> Route1 92 | { 93 | return const(value) <^> route 94 | } 95 | 96 | // MARK: Route 97 | 98 | public func capture(_ parse: @escaping (String) -> Value?) -> Route1 99 | { 100 | return Route1 { pathComponents in 101 | if let (first, rest) = uncons(pathComponents), let parsed = parse(first) { 102 | return (Array(rest), parsed) 103 | } 104 | return nil 105 | } 106 | } 107 | 108 | public func int() -> Route1 109 | { 110 | return capture { Int($0) } 111 | } 112 | 113 | public func double() -> Route1 114 | { 115 | return capture { Double($0) } 116 | } 117 | 118 | public func string() -> Route1 119 | { 120 | return capture(id) 121 | } 122 | 123 | public func match(_ string: String) -> Route1<()> 124 | { 125 | return capture { $0 == string ? () : nil } 126 | } 127 | 128 | public func choice(_ routes: [Route1]) -> Route1 129 | { 130 | return routes.reduce(empty()) { $0 <|> $1 } 131 | } 132 | 133 | // MARK: Helper operators 134 | 135 | /// Flipped `<^>`. 136 | public func <&> (route: Route1, f: @escaping (Value1) -> Value2) -> Route1 137 | { 138 | return f <^> route 139 | } 140 | 141 | /// Flipped `<^>` with higher precedence than `<&>`. 142 | public func <&!> (route: Route1, f: @escaping (Value1) -> Value2) -> Route1 143 | { 144 | return f <^> route 145 | } 146 | 147 | /// `associativity: right` version of `<**>`. 148 | public func (route1: Route1, route2: @autoclosure @escaping () -> Route1<(Value1) -> Value2>) -> Route1 149 | { 150 | return route1 <**> route2 151 | } 152 | 153 | public prefix func / (string: String) -> Route1<()> 154 | { 155 | return match(string) 156 | } 157 | -------------------------------------------------------------------------------- /Sources/Route2.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Enum-based URL route (optimizable). 4 | public indirect enum Route2 5 | { 6 | case match(String, Route2) 7 | case capture(String, (String) -> Any?, (Any) -> Route2) // (description, parse, convert) 8 | case choice([Route2]) 9 | case term(Value) 10 | case zero 11 | } 12 | 13 | extension Route2 14 | { 15 | public func run(_ pathComponents: [String]) -> Value? 16 | { 17 | switch self { 18 | case let .match(string, route2): 19 | if let (head, tail) = uncons(pathComponents), string == head { 20 | return route2.run(Array(tail)) 21 | } 22 | else { 23 | return nil 24 | } 25 | case let .capture(_, parse, convert): 26 | if let (head, tail) = uncons(pathComponents) { 27 | if let parsed = parse(head) { 28 | return convert(parsed).run(Array(tail)) 29 | } 30 | else { 31 | return nil 32 | } 33 | } 34 | else { 35 | return nil 36 | } 37 | case let .choice(routes): 38 | if let (head, tail) = uncons(routes) { 39 | if let value1 = head.run(pathComponents) { 40 | return value1 41 | } 42 | else { 43 | return Route2.choice(Array(tail)).run(pathComponents) 44 | } 45 | } 46 | else { 47 | return nil 48 | } 49 | case let .term(value): 50 | return value 51 | case .zero: 52 | return nil 53 | } 54 | } 55 | 56 | fileprivate var _match: (String, Route2)? 57 | { 58 | guard case let .match(string, route) = self else { 59 | return nil 60 | } 61 | return (string, route) 62 | } 63 | 64 | fileprivate var _capture: (String, (String) -> Any?, (Any) -> Route2)? 65 | { 66 | guard case let .capture(desc, parse, convert) = self else { 67 | return nil 68 | } 69 | return (desc, parse, convert) 70 | } 71 | } 72 | 73 | extension Route2: CustomStringConvertible 74 | { 75 | public var description: String 76 | { 77 | switch self { 78 | case let .match(string, route2): 79 | return ".match(\"\(string)\", \(route2))" 80 | case let .capture(desc, _, _): 81 | return ".capture(\(desc))" 82 | case let .choice(routes): 83 | return ".choice(\(routes))" 84 | case let .term(value): 85 | return ".term(\(value))" 86 | case .zero: 87 | return ".zero" 88 | } 89 | } 90 | } 91 | 92 | // MARK: Alternative 93 | 94 | public func empty() -> Route2 95 | { 96 | return .zero 97 | } 98 | 99 | public func <|> (route1: Route2, route2: @autoclosure @escaping () -> Route2) -> Route2 100 | { 101 | return .choice([route1, route2()]) 102 | } 103 | 104 | // MARK: Applicative 105 | 106 | public func pure(_ value: Value) -> Route2 107 | { 108 | return .term(value) 109 | } 110 | 111 | public func <*> (route1: Route2<(Value1) -> Value2>, route2: @autoclosure @escaping () -> Route2) -> Route2 112 | { 113 | switch route1 { 114 | case let .match(string, route1b): 115 | return .match(string, route1b <*> route2()) 116 | case let .capture(desc, parse, convert): 117 | return .capture(desc, parse, { convert($0) <*> route2() }) 118 | case let .choice(routes): 119 | return .choice(routes.map { $0 <*> route2() }) 120 | case let .term(f): 121 | return f <^> route2() 122 | case .zero: 123 | return .zero 124 | } 125 | } 126 | 127 | public func <**> (route1: Route2, route2: @autoclosure @escaping () -> Route2<(Value1) -> Value2>) -> Route2 128 | { 129 | return { a in { $0(a) } } <^> route1 <*> route2 130 | } 131 | 132 | public func <* (route1: Route2, route2: @autoclosure @escaping () -> Route2) -> Route2 133 | { 134 | return const <^> route1 <*> route2 135 | } 136 | 137 | public func *> (route1: Route2, route2: @autoclosure @escaping () -> Route2) -> Route2 138 | { 139 | return const(id) <^> route1 <*> route2 140 | } 141 | 142 | // MARK: Functor 143 | 144 | public func <^> (f: @escaping (Value1) -> Value2, route: Route2) -> Route2 145 | { 146 | switch route { 147 | case let .match(string, route2): 148 | return .match(string, f <^> route2) 149 | case let .capture(desc, parse, convert): 150 | return .capture(desc, parse, { f <^> convert($0) }) 151 | case let .choice(routes): 152 | return .choice(routes.map { f <^> $0 }) 153 | case let .term(value): 154 | return .term(f(value)) 155 | case .zero: 156 | return .zero 157 | } 158 | } 159 | 160 | public func <^ (value: Value2, route: Route2) -> Route2 161 | { 162 | return const(value) <^> route 163 | } 164 | 165 | public func ^> (route: Route2, value: Value2) -> Route2 166 | { 167 | return const(value) <^> route 168 | } 169 | 170 | // MARK: Route 171 | 172 | public func capture(_ description: String, _ parse: @escaping (String) -> Value?) -> Route2 173 | { 174 | return .capture(description, parse, { .term($0 as! Value) }) 175 | } 176 | 177 | public func int() -> Route2 178 | { 179 | return capture("int") { Int($0) } 180 | } 181 | 182 | public func double() -> Route2 183 | { 184 | return capture("double") { Double($0) } 185 | } 186 | 187 | public func string() -> Route2 188 | { 189 | return capture("string", id) 190 | } 191 | 192 | public func match(_ string: String) -> Route2<()> 193 | { 194 | return .match(string, .term(())) 195 | } 196 | 197 | public func choice(_ routes: [Route2]) -> Route2 198 | { 199 | return .choice(routes) 200 | } 201 | 202 | public func optimize(_ route: Route2) -> Route2 203 | { 204 | switch route { 205 | case let .match(string, route2): 206 | return .match(string, optimize(route2)) 207 | case let .capture(desc, parse, convert): 208 | return .capture(desc, parse, { optimize(convert($0)) }) 209 | case let .choice(routes): 210 | return _optimizeChoice(routes) 211 | case .term, .zero: 212 | return route 213 | } 214 | } 215 | 216 | private func _optimizeChoice(_ routes: [Route2]) -> Route2 217 | { 218 | /// Ascending order, e.g. `.match("a") < .match("z") < .capture(_, lowerAddress, _) < .capture(_, higherAddress, _) < other`, 219 | /// to reorder before optimization. 220 | func isAscending(_ route1: Route2, _ route2: Route2) -> Bool 221 | { 222 | switch (route1, route2) { 223 | case let (.match(string1, nextRoute1), .match(string2, nextRoute2)): 224 | if string1 == string2 { 225 | return isAscending(nextRoute1, nextRoute2) 226 | } 227 | else { 228 | return string1 < string2 229 | } 230 | case (.match, _): 231 | return true 232 | case let (.capture(_, parse1, _), .capture(_, parse2, _)): 233 | return parse1 < parse2 234 | case (.capture, _): 235 | return true 236 | default: 237 | return false 238 | } 239 | } 240 | 241 | func isSameRouteHead(_ route1: Route2) -> (Route2) -> Bool 242 | { 243 | return { route2 in 244 | switch (route1, route2) { 245 | case let (.match(string1, _), .match(string2, _)) where string1 == string2: 246 | return true 247 | case let (.capture(_, parse1, _), .capture(_, parse2, _)) where parse1 === parse2: 248 | return true 249 | default: 250 | return false 251 | } 252 | } 253 | } 254 | 255 | func flatten(_ routes: C) -> Route2 256 | where C.SubSequence: Collection, C.Iterator.Element == Route2 257 | { 258 | guard let (r, rs) = uncons(routes) else { 259 | return .zero 260 | } 261 | 262 | guard !rs.isEmpty else { 263 | return r 264 | } 265 | 266 | switch r { 267 | case let .match(string, _): 268 | let subRoutes = filterMap({ $0._match?.1 })(routes) 269 | return .match(string, _optimizeChoice(subRoutes)) 270 | case let .capture(desc, parse, _): 271 | let converts = filterMap({ $0._capture?.2 })(routes) 272 | return .capture(desc, parse, { parsed in 273 | let routes = converts.map { $0(parsed) } 274 | return _optimizeChoice(routes) 275 | }) 276 | default: 277 | return .zero 278 | } 279 | } 280 | 281 | if routes.isEmpty { 282 | return .zero 283 | } 284 | 285 | // Sort, group by the same route-head, and flatten. 286 | let sortedRoutes = routes.sorted(by: isAscending) 287 | let arrangedRoutes = groupBy(isSameRouteHead)(ArraySlice(sortedRoutes)).map(flatten) 288 | 289 | return choice(arrangedRoutes) 290 | } 291 | 292 | // MARK: Route operators 293 | 294 | /// Flipped `<^>`. 295 | public func <&> (route: Route2, f: @escaping (Value1) -> Value2) -> Route2 296 | { 297 | return f <^> route 298 | } 299 | 300 | /// Flipped `<^>` with higher precedence than `<&>`. 301 | public func <&!> (route: Route2, f: @escaping (Value1) -> Value2) -> Route2 302 | { 303 | return f <^> route 304 | } 305 | 306 | /// `associativity: right` version of `<**>`. 307 | public func (route1: Route2, route2: @autoclosure @escaping () -> Route2<(Value1) -> Value2>) -> Route2 308 | { 309 | return route1 <**> route2 310 | } 311 | 312 | public prefix func / (string: String) -> Route2<()> 313 | { 314 | return match(string) 315 | } 316 | -------------------------------------------------------------------------------- /Specs/Fixtures/Sitemap.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Sitemap: Equatable { 4 | case foo(Int) // "/R/foo/{Int}" 5 | case bar(Double) // "/R/bar/{Double}" 6 | case baz(String) // "/R/baz/{String}" 7 | case fooBar // "/R/foo/bar 8 | 9 | case foo2(Int, Int) // "/{Int}/foo/{Int}" 10 | case bar2(Int, Double) // "/{Int}/bar/{Double}" 11 | case baz2(Int, String) // "/{Int}/baz/{String}" 12 | case fooBar2(Int) // "/{Int}/foo/bar" 13 | 14 | case notFound // 404 15 | 16 | static func ==(lhs: Sitemap, rhs: Sitemap) -> Bool { 17 | switch (lhs, rhs) { 18 | case let (.foo(l), .foo(r)) where l == r: 19 | return true 20 | case let (.bar(l), .bar(r)) where l == r: 21 | return true 22 | case let (.baz(l), .baz(r)) where l == r: 23 | return true 24 | case (.fooBar, .fooBar): 25 | return true 26 | 27 | case let (.foo2(l), .foo2(r)) where l == r: 28 | return true 29 | case let (.bar2(l), .bar2(r)) where l == r: 30 | return true 31 | case let (.baz2(l), .baz2(r)) where l == r: 32 | return true 33 | case let (.fooBar2(l), .fooBar2(r)) where l == r: 34 | return true 35 | 36 | case (.notFound, .notFound): 37 | return true 38 | default: 39 | return false 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Specs/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Specs/Route1Spec.swift: -------------------------------------------------------------------------------- 1 | import Curry 2 | import FunRouter 3 | import XCTest 4 | import Quick 5 | import Nimble 6 | 7 | private typealias Route = Route1 8 | 9 | class Route1Spec: QuickSpec 10 | { 11 | override func spec() 12 | { 13 | describe("'/Hello/{city}/{year}'") { 14 | 15 | it("basic") { 16 | let route: Route<(String, Int)> = 17 | curry { ($1, $2) } <^> match("Hello") <*> string() <*> int() 18 | 19 | do { 20 | let value = route.run(["Hello", "Budapest", "2016"]) 21 | expect(value?.0) == "Budapest" 22 | expect(value?.1) == 2016 23 | } 24 | 25 | do { 26 | let value = route.run(["Ya", "tu", "sabes"]) 27 | expect(value).to(beNil()) 28 | } 29 | } 30 | 31 | it("flipCurry") { 32 | let route: Route<(String, Int)> = 33 | /"Hello" string() int() <&!> flipCurry { ($1, $2) } 34 | 35 | do { 36 | let value = route.run(["Hello", "Budapest", "2016"]) 37 | expect(value?.0) == "Budapest" 38 | expect(value?.1) == 2016 39 | } 40 | 41 | do { 42 | let value = route.run(["Ya", "tu", "sabes"]) 43 | expect(value).to(beNil()) 44 | } 45 | } 46 | 47 | } 48 | 49 | describe("Complex route using `choice`") { 50 | 51 | it("basic") { 52 | let route: Route = 53 | match("R") 54 | *> choice([ 55 | match("foo") *> int() <&> Sitemap.foo, 56 | match("bar") *> double() <&> Sitemap.bar, 57 | match("baz") *> string() <&> Sitemap.baz, 58 | match("foo") *> match("bar") *> pure(Sitemap.fooBar), 59 | ]) 60 | <|> pure(Sitemap.notFound) 61 | 62 | expect(route.run(["R", "foo", "123"])) == .foo(123) 63 | expect(route.run(["R", "bar", "4.5"])) == .bar(4.5) 64 | expect(route.run(["R", "baz", "xyz"])) == .baz("xyz") 65 | expect(route.run(["R", "foo", "bar"])) == .fooBar 66 | expect(route.run(["R", "foo", "xxx"])) == .notFound 67 | } 68 | 69 | it("flipCurry") { 70 | let route: Route = 71 | int() 72 | choice([ 73 | /"foo" *> int() <&!> flipCurry { Sitemap.foo2($0, $1) }, 74 | /"bar" *> double() <&!> flipCurry { Sitemap.bar2($0, $1) }, 75 | /"baz" *> string() <&!> flipCurry { Sitemap.baz2($0, $1) }, 76 | // /"foo" *> /"bar" <&!> flipCurry { Sitemap.fooBar2($0.0) } // Expression was too complex... 77 | ]) 78 | <|> pure(Sitemap.notFound) 79 | 80 | expect(route.run(["0", "foo", "123"])) == .foo2(0, 123) 81 | expect(route.run(["0", "bar", "4.5"])) == .bar2(0, 4.5) 82 | expect(route.run(["0", "baz", "xyz"])) == .baz2(0, "xyz") 83 | // expect(route.run(["0", "foo", "bar"])) == .fooBar2(0) 84 | expect(route.run(["0", "foo", "xxx"])) == .notFound 85 | } 86 | 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Specs/Route2Spec.swift: -------------------------------------------------------------------------------- 1 | import Curry 2 | import FunRouter 3 | import XCTest 4 | import Quick 5 | import Nimble 6 | 7 | private typealias Route = Route2 8 | 9 | class Route2Spec: QuickSpec 10 | { 11 | override func spec() 12 | { 13 | describe("'/Hello/{city}/{year}'") { 14 | 15 | it("basic") { 16 | let route: Route<(String, Int)> = 17 | curry { ($1, $2) } <^> match("Hello") <*> string() <*> int() 18 | 19 | do { 20 | let value = route.run(["Hello", "Budapest", "2016"]) 21 | expect(value?.0) == "Budapest" 22 | expect(value?.1) == 2016 23 | } 24 | 25 | do { 26 | let value = route.run(["Ya", "tu", "sabes"]) 27 | expect(value).to(beNil()) 28 | } 29 | } 30 | 31 | it("flipCurry") { 32 | let route: Route<(String, Int)> = 33 | /"Hello" string() int() <&!> flipCurry { ($1, $2) } 34 | 35 | do { 36 | let value = route.run(["Hello", "Budapest", "2016"]) 37 | expect(value?.0) == "Budapest" 38 | expect(value?.1) == 2016 39 | } 40 | 41 | do { 42 | let value = route.run(["Ya", "tu", "sabes"]) 43 | expect(value).to(beNil()) 44 | } 45 | } 46 | 47 | } 48 | 49 | describe("Complex route using `choice`") { 50 | 51 | it("basic") { 52 | let route: Route = 53 | match("R") 54 | *> choice([ 55 | match("foo") *> int() <&> Sitemap.foo, 56 | match("bar") *> double() <&> Sitemap.bar, 57 | match("baz") *> string() <&> Sitemap.baz, 58 | match("foo") *> match("bar") *> pure(Sitemap.fooBar), 59 | ]) 60 | <|> pure(Sitemap.notFound) 61 | 62 | expect(route.run(["R", "foo", "123"])) == .foo(123) 63 | expect(route.run(["R", "bar", "4.5"])) == .bar(4.5) 64 | expect(route.run(["R", "baz", "xyz"])) == .baz("xyz") 65 | expect(route.run(["R", "foo", "bar"])) == .fooBar 66 | expect(route.run(["R", "foo", "xxx"])) == .notFound 67 | } 68 | 69 | it("flipCurry") { 70 | let route: Route = 71 | int() 72 | choice([ 73 | /"foo" *> int() <&!> flipCurry { Sitemap.foo2($0, $1) }, 74 | /"bar" *> double() <&!> flipCurry { Sitemap.bar2($0, $1) }, 75 | /"baz" *> string() <&!> flipCurry { Sitemap.baz2($0, $1) }, 76 | // /"foo" *> /"bar" <&!> flipCurry { Sitemap.fooBar2($0.0) } 77 | ]) 78 | <|> pure(Sitemap.notFound) 79 | 80 | expect(route.run(["0", "foo", "123"])) == .foo2(0, 123) 81 | expect(route.run(["0", "bar", "4.5"])) == .bar2(0, 4.5) 82 | expect(route.run(["0", "baz", "xyz"])) == .baz2(0, "xyz") 83 | // expect(route.run(["0", "foo", "bar"])) == .fooBar2(0) 84 | expect(route.run(["0", "foo", "xxx"])) == .notFound 85 | } 86 | 87 | } 88 | 89 | describe("Optimize") { 90 | 91 | it("optimizes `match`es inside `choice`") { 92 | let naiveRoute: Route = 93 | match("R") 94 | *> choice([ 95 | match("foo") *> int() <&> Sitemap.foo, 96 | match("bar") *> double() <&> Sitemap.bar, 97 | match("baz") *> string() <&> Sitemap.baz, 98 | match("foo") *> match("bar") *> pure(Sitemap.fooBar) 99 | ]) 100 | 101 | // Duplicated `match("foo")` in `choice` will be optimized. 102 | let optimizedRoute = optimize(naiveRoute) 103 | 104 | print("") 105 | print("before optimize =", naiveRoute) 106 | print(" after optimize =", optimizedRoute) 107 | print("") 108 | 109 | expect(naiveRoute.run(["R", "foo", "123"])) == .foo(123) 110 | expect(naiveRoute.run(["R", "bar", "4.5"])) == .bar(4.5) 111 | expect(naiveRoute.run(["R", "foo", "bar"])) == .fooBar 112 | 113 | expect(optimizedRoute.run(["R", "foo", "123"])) == .foo(123) 114 | expect(optimizedRoute.run(["R", "bar", "4.5"])) == .bar(4.5) 115 | expect(optimizedRoute.run(["R", "foo", "bar"])) == .fooBar 116 | } 117 | 118 | it("optimizes `capture`s inside `choice`") { 119 | let naiveRoute: Route = 120 | match("R") 121 | *> choice([ 122 | int() <* match("foo") <&> Sitemap.foo, 123 | match("bar") *> double() <&> Sitemap.bar, 124 | int() <* match("foo2") <&> { Sitemap.foo($0 * 2) }, 125 | ]) 126 | 127 | // Duplicated `int()` in `choice` will be optimized. 128 | let optimizedRoute = optimize(naiveRoute) 129 | 130 | print("") 131 | print("before optimize =", naiveRoute) 132 | print(" after optimize =", optimizedRoute) 133 | print("") 134 | 135 | expect(naiveRoute.run(["R", "123", "foo"])) == .foo(123) 136 | expect(naiveRoute.run(["R", "bar", "4.5"])) == .bar(4.5) 137 | expect(naiveRoute.run(["R", "123", "foo2"])) == .foo(246) 138 | 139 | expect(optimizedRoute.run(["R", "123", "foo"])) == .foo(123) 140 | expect(optimizedRoute.run(["R", "bar", "4.5"])) == .bar(4.5) 141 | expect(optimizedRoute.run(["R", "123", "foo2"])) == .foo(246) 142 | } 143 | 144 | } 145 | } 146 | } 147 | 148 | --------------------------------------------------------------------------------