2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | DemoApp.xcscheme
8 |
9 | orderHint
10 | 1
11 |
12 | SwiftScanner.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 | SwiftScannerTests.xcscheme
18 |
19 | orderHint
20 | 2
21 |
22 |
23 | SuppressBuildableAutocreation
24 |
25 | 084635E01DF6D60000BE9EF1
26 |
27 | primary
28 |
29 |
30 | 08906C491DF1A90400FC4209
31 |
32 | primary
33 |
34 |
35 | 08906C5C1DF1BBDD00FC4209
36 |
37 | primary
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/xcshareddata/xcbaselines/084635E01DF6D60000BE9EF1.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | 768D88BA-F54A-405E-8656-FEE29B4B4CC9
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2200
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro11,4
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone9,1
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/SwiftScanner/DemoApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/project.xcworkspace/xcuserdata/danielemm.xcuserdatad/xcdebugger/Expressions.xcexplist:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
12 |
13 |
15 |
16 |
18 |
19 |
20 |
21 |
23 |
24 |
26 |
27 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/SwiftScanner/DemoApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 |
4 | # Xcode
5 | #
6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
7 |
8 | ## Build generated
9 | build/
10 | DerivedData
11 |
12 | ## Various settings
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata
22 |
23 | ## Other
24 | *.xccheckout
25 | *.moved-aside
26 | *.xcuserstate
27 | *.xcscmblueprint
28 |
29 | ## Other
30 | *.moved-aside
31 | *.xccheckout
32 | *.xcscmblueprint
33 |
34 | ## Obj-C/Swift specific
35 | *.hmap
36 | *.ipa
37 |
38 | ## Playgrounds
39 | timeline.xctimeline
40 | playground.xcworkspace
41 |
42 | # Swift Package Manager
43 | #
44 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
45 | # Packages/
46 | .build/
47 |
48 | # Bundler
49 | .bundle
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | Pods/
58 |
59 | # Carthage
60 | #
61 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
62 | # Carthage/Checkouts
63 | # Carthage/Build
64 | Carthage
65 |
66 | # fastlane
67 | #
68 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
69 | # screenshots whenever they are needed.
70 | # For more information about the recommended setup visit:
71 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
72 |
73 | fastlane/report.xml
74 | fastlane/screenshots
75 |
76 | .DS_
77 |
--------------------------------------------------------------------------------
/SwiftScanner/DemoApp/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/SwiftScanner/DemoApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SwiftScanner/DemoApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // DemoApp
4 | //
5 | // Created by Daniele Margutti on 02/12/2016.
6 | // Copyright © 2016 Daniele Margutti. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/SwiftScannerTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/xcshareddata/xcschemes/SwiftScanner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/xcuserdata/danielemm.xcuserdatad/xcschemes/DemoApp.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Tests/SwiftScannerTests/NSScanner+Extenions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSScanner+Extenions.swift
3 | // SwiftScanner
4 | //
5 | // Created by Daniele Margutti on 06/12/2016.
6 | // Copyright © 2016 Daniele Margutti. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Scanner {
12 |
13 | // MARK: Strings
14 |
15 | /// Returns a string, scanned as long as characters from a given character set are encountered, or `nil` if none are found.
16 | func scanCharacters(from set: CharacterSet) -> String? {
17 | var value: NSString? = ""
18 | if scanCharacters(from: set, into: &value),
19 | let value = value as? String {
20 | return value
21 | }
22 | return nil
23 | }
24 |
25 | /// Returns a string, scanned until a character from a given character set are encountered, or the remainder of the scanner's string. Returns `nil` if the scanner is already `atEnd`.
26 | func scanUpToCharacters(from set: CharacterSet) -> String? {
27 | var value: NSString? = ""
28 | if scanUpToCharacters(from: set, into: &value),
29 | let value = value as? String {
30 | return value
31 | }
32 | return nil
33 | }
34 |
35 | /// Returns the given string if scanned, or `nil` if not found.
36 | @discardableResult func scanString(_ str: String) -> String? {
37 | var value: NSString? = ""
38 | if scanString(str, into: &value),
39 | let value = value as? String {
40 | return value
41 | }
42 | return nil
43 | }
44 |
45 | /// Returns a string, scanned until the given string is found, or the remainder of the scanner's string. Returns `nil` if the scanner is already `atEnd`.
46 | func scanUpTo(_ str: String) -> String? {
47 | var value: NSString? = ""
48 | if scanUpTo(str, into: &value),
49 | let value = value as? String {
50 | return value
51 | }
52 | return nil
53 | }
54 |
55 | // MARK: Numbers
56 |
57 | /// Returns a Double if scanned, or `nil` if not found.
58 | func scanDouble() -> Double? {
59 | var value = 0.0
60 | if scanDouble(&value) {
61 | return value
62 | }
63 | return nil
64 | }
65 |
66 | /// Returns a Float if scanned, or `nil` if not found.
67 | func scanFloat() -> Float? {
68 | var value: Float = 0.0
69 | if scanFloat(&value) {
70 | return value
71 | }
72 | return nil
73 | }
74 |
75 | /// Returns an Int if scanned, or `nil` if not found.
76 | func scanInteger() -> Int? {
77 | var value = 0
78 | if scanInt(&value) {
79 | return value
80 | }
81 | return nil
82 | }
83 |
84 | /// Returns an Int32 if scanned, or `nil` if not found.
85 | func scanInt() -> Int32? {
86 | var value: Int32 = 0
87 | if scanInt32(&value) {
88 | return value
89 | }
90 | return nil
91 | }
92 |
93 | /// Returns an Int64 if scanned, or `nil` if not found.
94 | func scanLongLong() -> Int64? {
95 | var value: Int64 = 0
96 | if scanInt64(&value) {
97 | return value
98 | }
99 | return nil
100 | }
101 |
102 | /// Returns a UInt64 if scanned, or `nil` if not found.
103 | func scanUnsignedLongLong() -> UInt64? {
104 | var value: UInt64 = 0
105 | if scanUnsignedLongLong(&value) {
106 | return value
107 | }
108 | return nil
109 | }
110 |
111 | /// Returns an NSDecimal if scanned, or `nil` if not found.
112 | func scanDecimal() -> Decimal? {
113 | var value = Decimal()
114 | if scanDecimal(&value) {
115 | return value
116 | }
117 | return nil
118 | }
119 |
120 | // MARK: Hex Numbers
121 |
122 | /// Returns a Double if scanned in hexadecimal, or `nil` if not found.
123 | func scanHexDouble() -> Double? {
124 | var value = 0.0
125 | if scanHexDouble(&value) {
126 | return value
127 | }
128 | return nil
129 | }
130 |
131 | /// Returns a Float if scanned in hexadecimal, or `nil` if not found.
132 | func scanHexFloat() -> Float? {
133 | var value: Float = 0.0
134 | if scanHexFloat(&value) {
135 | return value
136 | }
137 | return nil
138 | }
139 |
140 | /// Returns a UInt32 if scanned in hexadecimal, or `nil` if not found.
141 | func scanHexInt() -> UInt32? {
142 | var value: UInt32 = 0
143 | if scanHexInt32(&value) {
144 | return value
145 | }
146 | return nil
147 | }
148 |
149 | /// Returns a UInt64 if scanned in hexadecimal, or `nil` if not found.
150 | func scanHexLongLong() -> UInt64? {
151 | var value: UInt64 = 0
152 | if scanHexInt64(&value) {
153 | return value
154 | }
155 | return nil
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/SwiftScanner/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | true
44 | DEFINITION_ORDER
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | 1480964265592
122 |
123 |
124 | 1480964265592
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | file://$PROJECT_DIR$/DemoApp/ViewController.swift
170 | 23
171 |
172 |
173 | file://$PROJECT_DIR$/DemoApp/ViewController.swift
174 | 28
175 |
176 |
177 |
178 | file://$PROJECT_DIR$/DemoApp/ViewController.swift
179 | 32
180 |
181 |
182 |
183 | file://$PROJECT_DIR$/DemoApp/ViewController.swift
184 | 34
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://travis-ci.org/oarrabi/SwiftScanner)
6 | [](https://codecov.io/gh/oarrabi/SwiftScanner)
7 | [](https://travis-ci.org/oarrabi/SwiftScanner)
8 | [](https://travis-ci.org/oarrabi/SwiftScanner)
9 | [](https://travis-ci.org/oarrabi/SwiftScanner)
10 | [](https://cocoapods.org/pods/SwiftScanner)
11 | [](https://github.com/Carthage/Carthage)
12 |
13 |
14 | # SwiftScanner
15 | `SwiftScanner` is a pure native Swift implementation of a string scanner; with no dependecies, full unicode support (who does not love emoji?), lots of useful featurs and swift in mind, StringScanner is a good alternative to built-in Apple's `NSScanner`.
16 |
17 | ★★ Star our github repository to help us! ★★
18 |
19 | ## Related Projects
20 | I'm also working on several other projects you may like.
21 | Take a look below:
22 |
23 |
24 |
25 | | Library | Description |
26 | |-----------------|--------------------------------------------------|
27 | | [**SwiftDate**](https://github.com/malcommac/SwiftDate) | The best way to manage date/timezones in Swift |
28 | | [**Hydra**](https://github.com/malcommac/Hydra) | Write better async code: async/await & promises |
29 | | [**FlowKit**](https://github.com/malcommac/FlowKit) | A new declarative approach to table managment. Forget datasource & delegates. |
30 | | [**SwiftRichString**](https://github.com/malcommac/SwiftRichString) | Elegant & Painless NSAttributedString in Swift |
31 | | [**SwiftLocation**](https://github.com/malcommac/SwiftLocation) | Efficient location manager |
32 | | [**SwiftMsgPack**](https://github.com/malcommac/SwiftMsgPack) | Fast/efficient msgPack encoder/decoder |
33 |
34 |
35 | ## Main Features
36 | SwiftScanner is initialized with a string and mantain an internal index used to navigate backward and forward through the string using two main concepts:
37 |
38 | * `scan` to return string which also increment the internal index
39 | * `peek` to return a string without incrementing the internal index
40 |
41 | Results of these operations returns collected String or Indexes.
42 | If operation fail due to an error (ie. `eof`, `notFound`, `invalidInt`...) and exception is thrown, in pure Swift style.
43 |
44 | API Documentation
45 | -------
46 |
47 | * **[scanChar()](#scanChar)**
48 | * **[scanInt()](#scanInt)**
49 | * **[scanFloat()](#scanFloat)**
50 | * **[scanHexInt()](#scanHexInt)**
51 | * **[scan(upTo: UnicodeScalar)](#scanUpToChar)**
52 | * **[scan(upTo: CharacterSet)](#scanUpToCharset)**
53 | * **[scan(untilIn: CharacterSet)](#scanUntilInCharset)**
54 | * **[scan(upTo: String)](#scanUpToString)**
55 | * **[scan(until: Test)](#scanUntilTrue)**
56 | * **[scan(length: Int)](#scanLenght)**
57 | * **[peek(upTo: UnicodeScalar)](#peekUpToChar)**
58 | * **[peek(upTo: CharacterSet)](#peekUpToCharset)**
59 | * **[peek(untilIn: CharacterSet)](#peekUpUntilInCharset)**
60 | * **[peek(upTo: String)](#peekUpToString)**
61 | * **[peek(until: Test)](#peekUpUntil)**
62 | * **[match(UnicodeScalar)](#matchChar)**
63 | * **[match(String)](#matchString)**
64 | * **[reset()](#reset)**
65 | * **[peekAtEnd()](#peekAtEnd)**
66 | * **[skip(length: Int)](#skipLength)**
67 | * **[back(length: Int)](#backLength)**
68 |
69 | Other
70 | -------
71 | * **[Installation](#installation)**
72 | * **[Tests](#tests)**
73 | * **[Requirements](#requirements)**
74 | * **[Credits](#credits)**
75 |
76 | ### `scan` functions
77 |
78 |
79 | #### `func scanChar() throws -> UnicodeScalar`
80 | `scanChar` allows you to scan the next character after the current's scanner `position` and return it as `UnicodeScalar`.
81 | If operation succeded internal scanner's `position` is advanced by 1 character (as unicode).
82 | If operation fails an exception is thrown.
83 |
84 | Example:
85 | ```swift
86 | let scanner = StringScanner("Hello this is SwiftScanner")
87 | let firstChar = try! scanner.scanChar() // get 'H'
88 | ```
89 |
90 |
91 | #### `func scanInt() throws -> Int`
92 | Scan the next integer value after the current scanner's `position`; consume scalars from {0...9} until a non numeric value is encountered. Return the integer representation in base 10.
93 | Throw `.invalidInt` if scalar at current position is not in allowed range (may also return `.eof`).
94 | If operation succeded internal scanner's `position` is advanced by the number of character which represent an integer.
95 | If operation fails an exception is thrown.
96 |
97 | Example:
98 | ```swift
99 | let scanner = StringScanner("15 apples")
100 | let parsedInt = try! scanner.scanInt() // get Int=15
101 | ```
102 |
103 |
104 | #### `func scanFloat() throws -> Float`
105 | Scan for a float value (in format ##.##) and convert it to a valid Floast.
106 | If scan succeded scanner's `position` is updated at the end of the represented string, otherwise an exception (`.invalidFloat`, `.eof`) is thrown and index is not touched.
107 |
108 | Example:
109 | ```swift
110 | let scanner = StringScanner("45.54 $")
111 | let parsedFloat = try! scanner.scanFloat() // get Int=45.54
112 | ```
113 |
114 |
115 | #### `func scanHexInt(digits: BitDigits) throws -> Int`
116 | Scan an HEX digit expressed in these formats:
117 |
118 | * `0x[VALUE]` (example: `0x0000000000564534`)
119 | * `0X[VALUE]` (example: `0x0929`)
120 | * `#[VALUE]` (example: `#1602`)
121 |
122 | If scan succeded scanner's `position` is updated at the end of the represented string, otherwise an exception ((`.notFound`, )`.invalidHex`, `.eof`) is thrown and index is not touched.
123 |
124 | Example:
125 | ```swift
126 | let scanner = StringScanner("#1602")
127 | let value = try! scanner.scanHexInt(.bit16) // get Int=5634
128 |
129 | let scanner = StringScanner("#0x0929")
130 | let value = try! scanner.scanHexInt(.bit16) // get Int=2345
131 |
132 | let scanner = StringScanner("#0x0000000000564534")
133 | let value = try! scanner.scanHexInt(.bit64) // get Int=5653812
134 | ```
135 |
136 | #### `public func scan(upTo char: UnicodeScalar) throws -> String?`
137 | Scan until given character is found starting from current scanner `position` till the end of the source string.
138 | Scanner's `position` is updated only if character is found and set just before it.
139 | Throw an exception if `.eof` is reached or `.notFound` if char was not found (in this case scanner's position is not updated)
140 |
141 | Example:
142 | ```swift
143 | let scanner = StringScanner("Hello Daniele ")
144 | let partialString = try! scanner.scan(upTo: "") // get "Hello "
145 | ```
146 |
147 |
148 | #### `func scan(upTo charSet: CharacterSet) throws -> String?`
149 | Scan until given character's is found.
150 | Index is reported before the start of the sequence, scanner's `position` is updated only if sequence is found.
151 | Throw an exception if `.eof` is reached or `.notFound` if sequence was not found.
152 |
153 | Example:
154 | ```swift
155 | let scanner = StringScanner("Hello, I've at least 15 apples")
156 | let partialString = try! scanner.scan(upTo: CharacterSet.decimalDigits) // get "Hello, I've at least "
157 | ```
158 |
159 |
160 | #### `func scan(untilIn charSet: CharacterSet) throws -> String?`
161 | Scan, starting from scanner's `position` until the next character of the scanner is contained into given character set.
162 | Scanner's `position` is updated automatically at the end of the sequence if validated, otherwise it will not touched.
163 |
164 | Example:
165 | ```swift
166 | let scanner = StringScanner("HELLO i'm mark")
167 | let partialString = try! scanner.scan(untilIn: CharacterSet.lowercaseLetters) // get "HELLO"
168 | ```
169 |
170 |
171 | #### `func scan(upTo string: String) throws -> String?`
172 | Scan, starting from scanner's `position` until specified string is encountered.
173 | Scanner's `position` is updated automatically at the end of the sequence if validated, otherwise it will not touched.
174 |
175 | Example:
176 | ```swift
177 | let scanner = StringScanner("This is a simple test I've made")
178 | let partialString = try! scanner.scan(upTo: "I've") // get "This is a simple test "
179 | ```
180 |
181 |
182 | #### `func scan(untilTrue test: ((UnicodeScalar) -> (Bool))) -> String`
183 | Scan and consume at the scalar starting from current `position`, testing it with function test.
184 | If test returns `true`, the `position` increased.
185 | If `false`, the function returns.
186 |
187 | Example:
188 | ```swift
189 | let scanner = StringScanner("Never be satisfied 💪 and always push yourself! 😎 Do the things people say cannot be done")
190 | let delimiters = CharacterSet(charactersIn: "💪😎")
191 | while !scanner.isAtEnd {
192 | let block = scanner.scan(untilTrue: { char in
193 | return (delimiters.contains(char) == false)
194 | })
195 | // Print:
196 | // "Never be satisfied " (first iteration)
197 | // "and always push yourself!" (second iteration)
198 | // "Do the things people say cannot be done" (third iteration)
199 | print("Block: \(block)")
200 | try scanner.skip() // push over the character
201 | }
202 | ```
203 |
204 |
205 | #### `func scan(length: Int=1) -> String`
206 | Read next length characters and accumulate it
207 | If operation is succeded scanner's `position` are updated according to consumed scalars.
208 | If fails an exception is thrown and `position` is not updated.
209 |
210 | Example:
211 | ```swift
212 | let scanner = StringScanner("Never be satisfied")
213 | let partialString = scanner.scan(5) // "Never"
214 | ```
215 | ### `peek` functions
216 |
217 | Peek functions are the same as concept of `scan()` but unless it it does not update internal scanner's `position` index.
218 | These functions usually return only `starting index` of matched pattern.
219 |
220 |
221 | #### `func peek(upTo char: UnicodeScalar) -> String.UnicodeScalarView.Index`
222 | Peek until chracter is found starting from current scanner's `position`.
223 | Scanner's `position` is never updated.
224 | Throw an exception if `.eof` is reached or `.notFound` if char was not found.
225 |
226 | Example:
227 | ```swift
228 | let scanner = StringScanner("Never be satisfied")
229 | let index = try! scanner.peek(upTo: "b") // return 6
230 | ```
231 |
232 |
233 | #### `func peek(upTo charSet: CharacterSet) -> String.UnicodeScalarView.Index`
234 | Peek until one the characters specified by set is encountered
235 | Index is reported before the start of the sequence, but scanner's `position` is never updated.
236 | Throw an exception if .eof is reached or .notFound if sequence was not found
237 |
238 | Example:
239 | ```swift
240 | let scanner = StringScanner("You are in queue: 123 is your position")
241 | let index = try! scanner.peek(upTo: CharacterSet.decimalDigits) // return 18
242 | ```
243 |
244 |
245 | #### `func peek(untilIn charSet: CharacterSet) -> String.UnicodeScalarView.Index`
246 | Peek until the next character of the scanner is contained into given.
247 | Scanner's `position` is never updated.
248 |
249 | Example:
250 | ```swift
251 | let scanner = StringScanner("654 apples")
252 | let index = try! scanner.peek(untilIn: CharacterSet.decimalDigits) // return 3
253 | ```
254 |
255 |
256 | #### `func peek(upTo string: String) -> String.UnicodeScalarView.Index`
257 | Iterate until specified string is encountered without updating indexes.
258 | Scanner's `position` is never updated but it's reported the index just before found occourence.
259 |
260 | Example:
261 | ```swift
262 | let scanner = StringScanner("654 apples in the bug")
263 | let index = try! scanner.peek(upTo: "in") // return 11
264 | ```
265 |
266 |
267 | #### `func peek(untilTrue test: ((UnicodeScalar) -> (Bool))) -> String.UnicodeScalarView.Index`
268 | Peeks at the scalar at the current position, testing it with function test.
269 | It only peeks so current scanner's `position` is not increased at the end of the operation
270 |
271 | Example:
272 | ```swift
273 | let scanner = StringScanner("I'm very 💪 and 😎 Go!")
274 | let delimiters = CharacterSet(charactersIn: "💪😎")
275 | while !scanner.isAtEnd {
276 | let prevIndex = scanner.position
277 | let finalIndex = scanner.peek(untilTrue: { char in
278 | return (delimiters.contains(char) == false)
279 | })
280 | // Distance will return:
281 | // - 9 (first iteration)
282 | // - 5 (second iteration)
283 | // - 4 (third iteration)
284 | let distance = scanner.string.distance(from: prevIndex, to: finalIndex)
285 | try scanner.skip(length: distance + 1)
286 | }
287 | ```
288 | ### Other Functions
289 |
290 |
291 | #### `func match(_ char: UnicodeScalar) -> Bool`
292 | Return false if the scalar at the current position don't match given scalar.
293 | Advance scanner's `position` to the end of the match if match.
294 |
295 | ```swift
296 | let scanner = StringScanner("💪 and 😎")
297 | let match = scanner.match("😎") // return false
298 | ```
299 |
300 |
301 | #### `func match(_ match: String) -> Bool`
302 | Return false if scalars starting at the current position don't match scalars in given string.
303 | Advance scanner's `position` to the end of the match string if match.
304 |
305 | ```swift
306 | let scanner = StringScanner("I'm very 💪 and 😎 Go!")
307 | scanner.match("I'm very") // return true
308 | ```
309 |
310 |
311 | #### `func reset()`
312 | Move scanner's internal `position` to the start of the string.
313 |
314 |
315 | #### `func peekAtEnd()`
316 | Move to the index's end index.
317 |
318 |
319 | #### `func skip(length: Int = 1) throws`
320 | Attempt to advance scanner's by length
321 | If operation is not possible (reached the end of the string) it throws and current scanner's `position` of the index did not change
322 | If operation succeded scanner's `position` is updated.
323 |
324 |
325 | #### `func back(length: Int = 1) throws`
326 | Attempt to advance the position back by length
327 | If operation fails scanner's `position` is not touched
328 | If operation succeded scaner's `position` is modified according to new value
329 |
330 |
331 | ## Installation
332 | You can install Swiftline using CocoaPods, carthage and Swift package manager
333 |
334 | ### CocoaPods
335 | use_frameworks!
336 | pod 'SwiftScanner'
337 |
338 | ### Carthage
339 | github 'malcommac/SwiftScanner'
340 |
341 | ### Swift Package Manager
342 | Add swiftline as dependency in your `Package.swift`
343 |
344 | ```
345 | import PackageDescription
346 |
347 | let package = Package(name: "YourPackage",
348 | dependencies: [
349 | .Package(url: "https://github.com/malcommac/SwiftScanner.git", majorVersion: 0),
350 | ]
351 | )
352 | ```
353 |
354 |
355 | ## Tests
356 | Tests can be found [here](https://github.com/malcommac/SwiftScanner/tree/master/Tests).
357 |
358 | Run them with
359 | ```
360 | swift test
361 | ```
362 |
363 |
364 | ## Requirements
365 |
366 | Current version is compatible with:
367 |
368 | * **Swift 4.x** >= 1.0.4
369 | * **Swift 3.x**: up to 1.0.3
370 |
371 | * iOS 8 or later
372 | * macOS 10.10 or later
373 | * watchOS 2.0 or later
374 | * tvOS 9.0 or later
375 | * ...and virtually any platform which is compatible with Swift 3 and implements the Swift Foundation Library
376 |
377 |
378 |
379 | ## Credits & License
380 | SwiftScanner is owned and maintained by [Daniele Margutti](http://www.danielemargutti.com/).
381 |
382 | As open source creation any help is welcome!
383 |
384 | The code of this library is licensed under MIT License; you can use it in commercial products without any limitation.
385 |
386 | The only requirement is to add a line in your Credits/About section with the text below:
387 |
388 | ```
389 | Portions SwiftScanner - http://github.com/malcommac/SwiftScanner
390 | Created by Daniele Margutti and licensed under MIT License.
391 | ```
392 |
--------------------------------------------------------------------------------
/Tests/SwiftScannerTests/TestSwiftScanner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftScannerTests.swift
3 | // SwiftScannerTests
4 | //
5 | // Created by Daniele Margutti on 06/12/2016.
6 | // Copyright © 2016 Daniele Margutti. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import SwiftScanner
11 |
12 | class SwiftScannerTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testScanChar() {
30 | let test = "next char test ⛵️"
31 | let testScalars = test.unicodeScalars
32 | var idx = testScalars.startIndex
33 |
34 | let scanner = StringScanner(test)
35 | do {
36 | while !scanner.isAtEnd {
37 | let currentScalar = try scanner.scanChar()
38 | XCTAssert( (currentScalar == testScalars[idx]) , "Failed to validate scanChar()")
39 | idx = testScalars.index(after: idx)
40 | }
41 | } catch let err {
42 | XCTFail("scanChar() does not work properly: \(err)")
43 | }
44 | }
45 |
46 | func testInt() {
47 | let test = "12,24,36,45,1"
48 | let valid = [12,24,36,45,1]
49 | let scanner = StringScanner(test)
50 | var idx = 0
51 |
52 | do {
53 | while !scanner.isAtEnd {
54 | if scanner.consumed > 0 { try scanner.skip() }
55 | let currentInt = try scanner.scanInt()
56 | XCTAssert( (currentInt == valid[idx]) , "Failed to validate scanInt()")
57 | idx += 1
58 | }
59 | } catch let err {
60 | XCTFail("scanInt() does not work properly: \(err)")
61 | }
62 | }
63 |
64 | func testFloat() {
65 | let test = "12.56,34.5,33.4,3.4"
66 | let valid: [Float] = [12.56,34.5,33.4,3.4]
67 | let scanner = StringScanner(test)
68 | var idx = 0
69 |
70 | do {
71 | while !scanner.isAtEnd {
72 | if scanner.consumed > 0 { try scanner.skip() }
73 | let currentFloat = try scanner.scanFloat()
74 | XCTAssert( (currentFloat == valid[idx]) , "Failed to validate testFloat()")
75 | idx += 1
76 | }
77 | } catch let err {
78 | XCTFail("testFloat() does not work properly: \(err)")
79 | }
80 | }
81 |
82 | func testHEX16BitString() {
83 | let test = "#1602,#04D1,0x0929"
84 | let intValues = [5634,1233,2345]
85 | validateHEXValues(name: "testHEX16BitString()", string: test, digits: .bit16, validValues: intValues)
86 | }
87 |
88 | func testHEX32BitString() {
89 | let test = "0XAF2C0155,#FF003344,0x0000F2C4"
90 | let intValues = [2938896725,4278203204,62148]
91 | validateHEXValues(name: "testHEX32BitString()", string: test, digits: .bit32, validValues: intValues)
92 | }
93 |
94 | func testHEX64BitString() {
95 | let test = "0x0000000000564534"
96 | let intValues = [5653812]
97 | validateHEXValues(name: "testHEX64BitString()", string: test, digits: .bit64, validValues: intValues)
98 | }
99 |
100 | func validateHEXValues(name: String, string: String, digits: BitDigits, validValues: [Int]) {
101 | var idx = 0
102 | let scanner = StringScanner(string)
103 | do {
104 | while !scanner.isAtEnd {
105 | if scanner.consumed > 0 { try scanner.skip() }
106 | let currentInt = try scanner.scanHexInt(digits)
107 | XCTAssert( (currentInt == validValues[idx]) , "Failed to validate scanHexInt()")
108 | idx += 1
109 | }
110 | } catch let err {
111 | XCTFail("scanHexInt() does not work properly: \(err)")
112 | }
113 | }
114 |
115 | func testScanUpToUnicodeScalar() {
116 | let test = "hello again. i'm daniele. welcome here!"
117 | let validValues:[String] = ["hello again"," i'm daniele"," welcome here!"]
118 | let scanner = StringScanner(test)
119 | var idx = 0
120 |
121 | do {
122 | while !scanner.isAtEnd {
123 | let blockValue = try scanner.scan(upTo: ".")
124 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)")
125 | idx += 1
126 | if (idx < validValues.count) { try scanner.skip() }
127 | }
128 | } catch let err {
129 | XCTFail("scan(upTo:) does not work properly: \(err)")
130 | }
131 | }
132 |
133 | func testScanUpToCharset() {
134 | let test = "this a token;that's another.third one!the last one"
135 | let validValues: [String] = ["this a token","that's another","third one","the last one"]
136 | let scanner = StringScanner(test)
137 | var idx = 0
138 |
139 | do {
140 | while !scanner.isAtEnd {
141 | let blockValue = try scanner.scan(upTo: CharacterSet(charactersIn: ";.!"))
142 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)")
143 | idx += 1
144 | if (idx < validValues.count) { try scanner.skip() }
145 | }
146 | } catch let err {
147 | XCTFail("scan(upTo:) does not work properly: \(err)")
148 | }
149 | }
150 |
151 | func testScanUpUntilCharset() {
152 | let test = "daniele;mario;john;steve"
153 | let validValues: [String] = ["daniele","mario","john","steve"]
154 | let scanner = StringScanner(test)
155 | var idx = 0
156 |
157 | do {
158 | while !scanner.isAtEnd {
159 | let blockValue = try scanner.scan(untilIn: CharacterSet.lowercaseLetters)
160 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(until:)")
161 | idx += 1
162 | if (idx < validValues.count) { try scanner.skip() }
163 | }
164 | } catch let err {
165 | XCTFail("scan(until:) does not work properly: \(err)")
166 | }
167 | }
168 |
169 | func testScanUpToString() {
170 | let separator = ",\n"
171 | let test = "one\(separator)two\(separator)three\(separator)four\(separator)"
172 | let validValues: [String] = ["one","two","three","four"]
173 | let scanner = StringScanner(test)
174 | var idx = 0
175 |
176 | do {
177 | while !scanner.isAtEnd {
178 | let blockValue = try scanner.scan(upTo: separator)
179 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(upTo:)")
180 | if (idx < validValues.count) { try scanner.skip(length: separator.characters.count) }
181 | idx += 1
182 | }
183 | } catch let err {
184 | XCTFail("scan(upTo:) does not work properly: \(err)")
185 | }
186 |
187 | }
188 |
189 | func testScanLength() {
190 | let separator = ",\n"
191 | let test = "123\(separator)456\(separator)678\(separator)987"
192 | let validValues: [String] = ["123","456","678","987"]
193 |
194 | let scanner = StringScanner(test)
195 | var idx = 0
196 |
197 | do {
198 | while !scanner.isAtEnd {
199 | let blockValue = try scanner.scan(length: 3)
200 | XCTAssert( (blockValue == validValues[idx]) , "Failed to validate scan(length:)")
201 | idx += 1
202 | if (idx < validValues.count) {
203 | try scanner.skip(length: separator.characters.count)
204 | }
205 | }
206 | } catch let err {
207 | XCTFail("scan(length:) does not work properly: \(err)")
208 | }
209 |
210 | }
211 |
212 | func testPeekUpToChar() {
213 | let test = "abc;defg;hilmn;"
214 | let validDistances = [3,4,5]
215 | let scanner = StringScanner(test)
216 | var idx = 0
217 |
218 | do {
219 | while !scanner.isAtEnd {
220 | let currentPosition = scanner.position
221 | let separatorPosition = try scanner.peek(upTo: ";")
222 | let distance = scanner.string.distance(from: currentPosition, to: separatorPosition)
223 | try scanner.skip(length: distance+1)
224 | XCTAssert( (distance == validDistances[idx]) , "Failed to validate peek(upTo:)")
225 | idx += 1
226 | }
227 | } catch let err {
228 | XCTFail("peek(upTo:) does not work properly: \(err)")
229 | }
230 | }
231 |
232 | func testPeekUpToCharset() {
233 | let test = "hello, again!I'm daniele.And you?"
234 | let validDistances = [5,6,11,8]
235 | let scanner = StringScanner(test)
236 | var idx = 0
237 |
238 | do {
239 | while !scanner.isAtEnd {
240 | let currentPosition = scanner.position
241 | let separatorPosition = try scanner.peek(upTo: CharacterSet(charactersIn: ",!."))
242 | let distance = scanner.string.distance(from: currentPosition, to: separatorPosition)
243 | try scanner.skip(length: distance+1)
244 | XCTAssert( (distance == validDistances[idx]) , "Failed to validate peek(upTo:)")
245 | idx += 1
246 | }
247 | } catch let err {
248 | guard let error = err as? StringScannerError else { return }
249 | if case .eof = error { // handle last scanner.skip(length:)
250 | return
251 | }
252 | XCTFail("peek(upTo:) does not work properly: \(error)")
253 | }
254 | }
255 |
256 | func testPeekUpUntilCharset() {
257 | let test = "HELLOman!"
258 | let scanner = StringScanner(test)
259 |
260 | do {
261 | let startPosition = scanner.position
262 | let endPosition = try scanner.peek(untilIn: CharacterSet.uppercaseLetters)
263 | let distance = scanner.string.distance(from: startPosition, to: endPosition)
264 | XCTAssert( (distance == 5) , "Failed to validate peek(untilIn:)")
265 | } catch let err {
266 | XCTFail("peek(untilIn:) does not work properly: \(err)")
267 | }
268 | }
269 |
270 | func testPeekUpToString() {
271 | let test = "Never be satisfied 💪 and always push yourself!"
272 | let scanner = StringScanner(test)
273 | do {
274 | let startPosition = scanner.position
275 | let endPosition = try scanner.peek(upTo: "💪")
276 | let distance = scanner.string.distance(from: startPosition, to: endPosition)
277 | XCTAssert( (distance == 19) , "Failed to validate peek(upTo:String)")
278 | } catch let err {
279 | XCTFail("peek(upTo:String) does not work properly: \(err)")
280 | }
281 | }
282 |
283 | func testScanUntilTrueOnTest() {
284 | let test = "Never be satisfied 💪 and always push yourself! 😎 Do the things people say cannot be done"
285 | let validated = ["Never be satisfied "," and always push yourself! "," Do the things people say cannot be done"]
286 | let delimiters = CharacterSet(charactersIn: "💪😎")
287 | let scanner = StringScanner(test)
288 | var idx = 0
289 |
290 | do {
291 | while !scanner.isAtEnd {
292 | let block = scanner.scan(untilTrue: { char in
293 | return (delimiters.contains(char) == false)
294 | })
295 | XCTAssert( (block == validated[idx]) , "Failed to validate scan(untilTrue:)")
296 | try scanner.skip()
297 | idx += 1
298 | }
299 | } catch let err {
300 | guard let error = err as? StringScannerError else { return }
301 | if case .eof = error { // handle last scanner.skip(length:)
302 | return
303 | }
304 | XCTFail("scan(untilTrue:) does not work properly: \(error)")
305 | }
306 | }
307 |
308 | func testPeekUntilTrueOnTest() {
309 | let test = "I'm very 💪 and 😎 Go!"
310 | let delimiters = CharacterSet(charactersIn: "💪😎")
311 | var validatedDistances = [9,5,4]
312 | let scanner = StringScanner(test)
313 | var idx = 0
314 |
315 | do {
316 | while !scanner.isAtEnd {
317 | let prevIndex = scanner.position
318 | let finalIndex = scanner.peek(untilTrue: { char in
319 | return (delimiters.contains(char) == false)
320 | })
321 | let distance = scanner.string.distance(from: prevIndex, to: finalIndex)
322 | XCTAssert( (distance == validatedDistances[idx]) , "Failed to validate peek(untilTrue:)")
323 | try scanner.skip(length: distance + 1)
324 | idx += 1
325 | }
326 | } catch let err {
327 | guard let error = err as? StringScannerError else { return }
328 | if case .eof = error { // handle last scanner.skip(length:)
329 | return
330 | }
331 | XCTFail("scan(untilTrue:) does not work properly: \(error)")
332 | }
333 | }
334 |
335 | func testMatch() {
336 | let test_match = "hello man! push yourself!"
337 | let scanner = StringScanner(test_match)
338 |
339 | // test match
340 | let match = scanner.match("hello man!")
341 | XCTAssert( (match == true), "match() does not work properly")
342 |
343 | // test don't match
344 | scanner.reset() // reset from the start
345 | try! scanner.scan(upTo: "! ") // move to the next token
346 | try! scanner.skip(length: 2)
347 | let not_match = scanner.match("hello man")
348 | XCTAssert( (not_match == false), "match() does not work properly")
349 | }
350 |
351 | func testReset() {
352 | let test = "hello man! push yourself"
353 |
354 | func randomNumber(range: Range) -> Int {
355 | return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound))) + range.lowerBound
356 | }
357 |
358 | let scanner = StringScanner(test)
359 | let length = test.characters.count
360 |
361 | var idx = 0
362 | do {
363 | while idx < 10 {
364 | let distance = randomNumber(range: 0..= validatedLines.count {
387 | break
388 | }
389 | let backLen = backLenghts[idx]
390 | try scanner.back(length: backLen)
391 | let scanner_char = try scanner.scanChar()
392 | let valid_char = validatedLines[idx]
393 | let isValid = (String(scanner_char) == valid_char)
394 | XCTAssert( isValid, "Failed to validate back()")
395 | try scanner.back()
396 | idx += 1
397 | }
398 | } catch {
399 | XCTFail("back() does not work properly")
400 | }
401 | }
402 |
403 | func testBack() {
404 | let test = "hello, i'm daniele and this is a great library!"
405 | let scanner = StringScanner(test)
406 |
407 | do {
408 | scanner.peekAtEnd() // null termination index
409 | try scanner.back() // back from null termination char
410 | var positionInChar = 1 // back from null termination char
411 | while true {
412 | let test_charIdx = test.unicodeScalars.index(test.unicodeScalars.endIndex, offsetBy: -positionInChar)
413 | let test_char = test.unicodeScalars[test_charIdx]
414 | let scanner_char = scanner.string[scanner.position]
415 | XCTAssert( (test_char == scanner_char) , "Failed to validate back()")
416 |
417 | if positionInChar < test.characters.count {
418 | try scanner.back()
419 | positionInChar += 1
420 | } else {
421 | break
422 | }
423 | }
424 | } catch {
425 | XCTFail("back() does not work properly")
426 | }
427 | }
428 |
429 | func testSkipSpaces() {
430 | let test = "1 2 3 4 5 \t 6 7"
431 | let scanner = StringScanner(test)
432 | var numbers = [Int]()
433 | do {
434 | while !scanner.isAtEnd {
435 | try scanner.skip(charactersIn: .whitespaces)
436 | numbers.append(try scanner.scanInt())
437 | }
438 | } catch {
439 | XCTFail("skip(charactersIn:) does not work properly")
440 | }
441 |
442 | XCTAssertEqual(numbers, [1, 2, 3, 4, 5, 6, 7])
443 | }
444 |
445 | /*
446 | func getSamplePerformanceData() -> String {
447 | // Give here a sample file to test tags performance
448 | let filePath = Bundle(for: SwiftScannerTests.self).path(forResource: "sample_file", ofType: "html")!
449 | let source = try! String(contentsOfFile: filePath, encoding: String.Encoding.isoLatin1)
450 | return source
451 | }
452 |
453 | func testPerformanceonExtractTags_NSScanner() {
454 | let scanner = Scanner(string: self.getSamplePerformanceData())
455 | scanner.charactersToBeSkipped = nil
456 | var plain_text = ""
457 | var tagsList: [String] = []
458 |
459 | self.measure {
460 | scanner.scanLocation = 0
461 | while !scanner.isAtEnd {
462 | if let textString = scanner.scanUpToCharacters(from: CharacterSet(charactersIn: "<")) {
463 | plain_text += textString
464 | } else {
465 | // We have encountered a special entity or an open/close tag
466 | if scanner.scanString("<") != nil {
467 | // It's an open/close tag character
468 | let tag = scanner.scanUpTo(">") // get the raw name of the tag
469 | tagsList.append(tag!)
470 | scanner.scanString(">") // go ahead
471 | }
472 | }
473 | }
474 | print("testPerformanceonExtractTags_NSScanner: \(tagsList.count) tags found")
475 | tagsList.removeAll()
476 | }
477 | }
478 |
479 | func testPerformanceOnExtractTags() {
480 | let scanner = StringScanner(self.getSamplePerformanceData())
481 | var plain_text = ""
482 | var tagsList: [String] = []
483 |
484 | self.measure {
485 | do {
486 | scanner.reset()
487 | while !scanner.isAtEnd {
488 | if let text = try scanner.scan(upTo: CharacterSet(charactersIn: "<")) {
489 | plain_text += text
490 | } else {
491 | try! scanner.scanChar()
492 | let endTag = try! scanner.scan(upTo: CharacterSet(charactersIn: ">"))
493 | guard let tag = endTag else {
494 | continue
495 | }
496 | tagsList.append(tag)
497 | try! scanner.scanChar()
498 | }
499 | }
500 | print("testPerformanceOnExtractTags: \(tagsList.count) tags found")
501 | tagsList.removeAll()
502 | } catch let err {
503 | print("Error: \(err)")
504 | }
505 | }
506 | }
507 | */
508 | }
509 |
510 |
--------------------------------------------------------------------------------
/SwiftScanner/SwiftScanner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 084635E61DF6D60000BE9EF1 /* SwiftScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; };
11 | 084635ED1DF6D7AC00BE9EF1 /* SwiftScanner.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
12 | 08906C4F1DF1A90400FC4209 /* SwiftScanner.h in Headers */ = {isa = PBXBuildFile; fileRef = 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */; settings = {ATTRIBUTES = (Public, ); }; };
13 | 08906C601DF1BBDE00FC4209 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */; };
14 | 08906C621DF1BBDE00FC4209 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C611DF1BBDE00FC4209 /* ViewController.swift */; };
15 | 08906C651DF1BBDE00FC4209 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08906C631DF1BBDE00FC4209 /* Main.storyboard */; };
16 | 08906C671DF1BBDE00FC4209 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08906C661DF1BBDE00FC4209 /* Assets.xcassets */; };
17 | 08906C6A1DF1BBDE00FC4209 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */; };
18 | 08906C711DF1BBEB00FC4209 /* SwiftScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; };
19 | 08906C731DF1BC4600FC4209 /* SwiftScanner.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | 08906C771DF1C56C00FC4209 /* StringScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08906C751DF1C56C00FC4209 /* StringScanner.swift */; };
21 | 08C606ED1DF817E50014E07E /* NSScanner+Extenions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */; };
22 | 08C606EE1DF817E50014E07E /* TestSwiftScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXContainerItemProxy section */
26 | 084635E71DF6D60000BE9EF1 /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = 08906C411DF1A90400FC4209 /* Project object */;
29 | proxyType = 1;
30 | remoteGlobalIDString = 08906C491DF1A90400FC4209;
31 | remoteInfo = SwiftScanner;
32 | };
33 | 08906C6F1DF1BBE800FC4209 /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 08906C411DF1A90400FC4209 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 08906C491DF1A90400FC4209;
38 | remoteInfo = SwiftScanner;
39 | };
40 | /* End PBXContainerItemProxy section */
41 |
42 | /* Begin PBXCopyFilesBuildPhase section */
43 | 084635EC1DF6D7A200BE9EF1 /* Copy Frameworks */ = {
44 | isa = PBXCopyFilesBuildPhase;
45 | buildActionMask = 2147483647;
46 | dstPath = "";
47 | dstSubfolderSpec = 10;
48 | files = (
49 | 084635ED1DF6D7AC00BE9EF1 /* SwiftScanner.framework in Copy Frameworks */,
50 | );
51 | name = "Copy Frameworks";
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | 08906C721DF1BBEE00FC4209 /* Copy Frameworks */ = {
55 | isa = PBXCopyFilesBuildPhase;
56 | buildActionMask = 2147483647;
57 | dstPath = "";
58 | dstSubfolderSpec = 10;
59 | files = (
60 | 08906C731DF1BC4600FC4209 /* SwiftScanner.framework in Copy Frameworks */,
61 | );
62 | name = "Copy Frameworks";
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | /* End PBXCopyFilesBuildPhase section */
66 |
67 | /* Begin PBXFileReference section */
68 | 082292671DF816260077BAE8 /* SwiftScannerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftScannerTests-Bridging-Header.h"; sourceTree = ""; };
69 | 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftScannerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
70 | 084635E51DF6D60000BE9EF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
71 | 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
72 | 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftScanner.h; sourceTree = ""; };
73 | 08906C4E1DF1A90400FC4209 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
74 | 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
75 | 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
76 | 08906C611DF1BBDE00FC4209 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
77 | 08906C641DF1BBDE00FC4209 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
78 | 08906C661DF1BBDE00FC4209 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
79 | 08906C691DF1BBDE00FC4209 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
80 | 08906C6B1DF1BBDE00FC4209 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
81 | 08906C751DF1C56C00FC4209 /* StringScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StringScanner.swift; path = ../Sources/SwiftScanner/StringScanner.swift; sourceTree = ""; };
82 | 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSScanner+Extenions.swift"; path = "../../Tests/SwiftScannerTests/NSScanner+Extenions.swift"; sourceTree = ""; };
83 | 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestSwiftScanner.swift; path = ../../Tests/SwiftScannerTests/TestSwiftScanner.swift; sourceTree = ""; };
84 | /* End PBXFileReference section */
85 |
86 | /* Begin PBXFrameworksBuildPhase section */
87 | 084635DE1DF6D60000BE9EF1 /* Frameworks */ = {
88 | isa = PBXFrameworksBuildPhase;
89 | buildActionMask = 2147483647;
90 | files = (
91 | 084635E61DF6D60000BE9EF1 /* SwiftScanner.framework in Frameworks */,
92 | );
93 | runOnlyForDeploymentPostprocessing = 0;
94 | };
95 | 08906C461DF1A90400FC4209 /* Frameworks */ = {
96 | isa = PBXFrameworksBuildPhase;
97 | buildActionMask = 2147483647;
98 | files = (
99 | );
100 | runOnlyForDeploymentPostprocessing = 0;
101 | };
102 | 08906C5A1DF1BBDD00FC4209 /* Frameworks */ = {
103 | isa = PBXFrameworksBuildPhase;
104 | buildActionMask = 2147483647;
105 | files = (
106 | 08906C711DF1BBEB00FC4209 /* SwiftScanner.framework in Frameworks */,
107 | );
108 | runOnlyForDeploymentPostprocessing = 0;
109 | };
110 | /* End PBXFrameworksBuildPhase section */
111 |
112 | /* Begin PBXGroup section */
113 | 084635E21DF6D60000BE9EF1 /* Tests */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 084635E51DF6D60000BE9EF1 /* Info.plist */,
117 | 08C606EB1DF817E50014E07E /* NSScanner+Extenions.swift */,
118 | 08C606EC1DF817E50014E07E /* TestSwiftScanner.swift */,
119 | 082292671DF816260077BAE8 /* SwiftScannerTests-Bridging-Header.h */,
120 | );
121 | name = Tests;
122 | path = SwiftScannerTests;
123 | sourceTree = "";
124 | };
125 | 08906C401DF1A90400FC4209 = {
126 | isa = PBXGroup;
127 | children = (
128 | 08C606EF1DF818670014E07E /* Library */,
129 | 08906C4C1DF1A90400FC4209 /* Framework */,
130 | 08906C5E1DF1BBDE00FC4209 /* DemoApp */,
131 | 084635E21DF6D60000BE9EF1 /* Tests */,
132 | 08906C4B1DF1A90400FC4209 /* Products */,
133 | );
134 | sourceTree = "";
135 | };
136 | 08906C4B1DF1A90400FC4209 /* Products */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */,
140 | 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */,
141 | 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */,
142 | );
143 | name = Products;
144 | sourceTree = "";
145 | };
146 | 08906C4C1DF1A90400FC4209 /* Framework */ = {
147 | isa = PBXGroup;
148 | children = (
149 | 08906C4D1DF1A90400FC4209 /* SwiftScanner.h */,
150 | 08906C4E1DF1A90400FC4209 /* Info.plist */,
151 | );
152 | name = Framework;
153 | path = SwiftScanner;
154 | sourceTree = "";
155 | };
156 | 08906C5E1DF1BBDE00FC4209 /* DemoApp */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 08906C5F1DF1BBDE00FC4209 /* AppDelegate.swift */,
160 | 08906C611DF1BBDE00FC4209 /* ViewController.swift */,
161 | 08906C631DF1BBDE00FC4209 /* Main.storyboard */,
162 | 08906C661DF1BBDE00FC4209 /* Assets.xcassets */,
163 | 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */,
164 | 08906C6B1DF1BBDE00FC4209 /* Info.plist */,
165 | );
166 | path = DemoApp;
167 | sourceTree = "";
168 | };
169 | 08C606EF1DF818670014E07E /* Library */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 08906C751DF1C56C00FC4209 /* StringScanner.swift */,
173 | );
174 | name = Library;
175 | sourceTree = "";
176 | };
177 | /* End PBXGroup section */
178 |
179 | /* Begin PBXHeadersBuildPhase section */
180 | 08906C471DF1A90400FC4209 /* Headers */ = {
181 | isa = PBXHeadersBuildPhase;
182 | buildActionMask = 2147483647;
183 | files = (
184 | 08906C4F1DF1A90400FC4209 /* SwiftScanner.h in Headers */,
185 | );
186 | runOnlyForDeploymentPostprocessing = 0;
187 | };
188 | /* End PBXHeadersBuildPhase section */
189 |
190 | /* Begin PBXNativeTarget section */
191 | 084635E01DF6D60000BE9EF1 /* SwiftScannerTests */ = {
192 | isa = PBXNativeTarget;
193 | buildConfigurationList = 084635EB1DF6D60000BE9EF1 /* Build configuration list for PBXNativeTarget "SwiftScannerTests" */;
194 | buildPhases = (
195 | 084635DD1DF6D60000BE9EF1 /* Sources */,
196 | 084635DE1DF6D60000BE9EF1 /* Frameworks */,
197 | 084635DF1DF6D60000BE9EF1 /* Resources */,
198 | 084635EC1DF6D7A200BE9EF1 /* Copy Frameworks */,
199 | );
200 | buildRules = (
201 | );
202 | dependencies = (
203 | 084635E81DF6D60000BE9EF1 /* PBXTargetDependency */,
204 | );
205 | name = SwiftScannerTests;
206 | productName = SwiftScannerTests;
207 | productReference = 084635E11DF6D60000BE9EF1 /* SwiftScannerTests.xctest */;
208 | productType = "com.apple.product-type.bundle.unit-test";
209 | };
210 | 08906C491DF1A90400FC4209 /* SwiftScanner */ = {
211 | isa = PBXNativeTarget;
212 | buildConfigurationList = 08906C521DF1A90400FC4209 /* Build configuration list for PBXNativeTarget "SwiftScanner" */;
213 | buildPhases = (
214 | 08906C451DF1A90400FC4209 /* Sources */,
215 | 08906C461DF1A90400FC4209 /* Frameworks */,
216 | 08906C471DF1A90400FC4209 /* Headers */,
217 | 08906C481DF1A90400FC4209 /* Resources */,
218 | );
219 | buildRules = (
220 | );
221 | dependencies = (
222 | );
223 | name = SwiftScanner;
224 | productName = SwiftScanner;
225 | productReference = 08906C4A1DF1A90400FC4209 /* SwiftScanner.framework */;
226 | productType = "com.apple.product-type.framework";
227 | };
228 | 08906C5C1DF1BBDD00FC4209 /* DemoApp */ = {
229 | isa = PBXNativeTarget;
230 | buildConfigurationList = 08906C6C1DF1BBDE00FC4209 /* Build configuration list for PBXNativeTarget "DemoApp" */;
231 | buildPhases = (
232 | 08906C591DF1BBDD00FC4209 /* Sources */,
233 | 08906C5A1DF1BBDD00FC4209 /* Frameworks */,
234 | 08906C5B1DF1BBDD00FC4209 /* Resources */,
235 | 08906C721DF1BBEE00FC4209 /* Copy Frameworks */,
236 | );
237 | buildRules = (
238 | );
239 | dependencies = (
240 | 08906C701DF1BBE800FC4209 /* PBXTargetDependency */,
241 | );
242 | name = DemoApp;
243 | productName = DemoApp;
244 | productReference = 08906C5D1DF1BBDD00FC4209 /* DemoApp.app */;
245 | productType = "com.apple.product-type.application";
246 | };
247 | /* End PBXNativeTarget section */
248 |
249 | /* Begin PBXProject section */
250 | 08906C411DF1A90400FC4209 /* Project object */ = {
251 | isa = PBXProject;
252 | attributes = {
253 | LastSwiftUpdateCheck = 0810;
254 | LastUpgradeCheck = 1000;
255 | ORGANIZATIONNAME = "Daniele Margutti";
256 | TargetAttributes = {
257 | 084635E01DF6D60000BE9EF1 = {
258 | CreatedOnToolsVersion = 8.1;
259 | DevelopmentTeam = E5DU3FA699;
260 | LastSwiftMigration = 0810;
261 | ProvisioningStyle = Automatic;
262 | };
263 | 08906C491DF1A90400FC4209 = {
264 | CreatedOnToolsVersion = 8.1;
265 | DevelopmentTeam = E5DU3FA699;
266 | LastSwiftMigration = 0900;
267 | ProvisioningStyle = Automatic;
268 | };
269 | 08906C5C1DF1BBDD00FC4209 = {
270 | CreatedOnToolsVersion = 8.1;
271 | DevelopmentTeam = E5DU3FA699;
272 | ProvisioningStyle = Automatic;
273 | };
274 | };
275 | };
276 | buildConfigurationList = 08906C441DF1A90400FC4209 /* Build configuration list for PBXProject "SwiftScanner" */;
277 | compatibilityVersion = "Xcode 3.2";
278 | developmentRegion = English;
279 | hasScannedForEncodings = 0;
280 | knownRegions = (
281 | en,
282 | Base,
283 | );
284 | mainGroup = 08906C401DF1A90400FC4209;
285 | productRefGroup = 08906C4B1DF1A90400FC4209 /* Products */;
286 | projectDirPath = "";
287 | projectRoot = "";
288 | targets = (
289 | 08906C491DF1A90400FC4209 /* SwiftScanner */,
290 | 08906C5C1DF1BBDD00FC4209 /* DemoApp */,
291 | 084635E01DF6D60000BE9EF1 /* SwiftScannerTests */,
292 | );
293 | };
294 | /* End PBXProject section */
295 |
296 | /* Begin PBXResourcesBuildPhase section */
297 | 084635DF1DF6D60000BE9EF1 /* Resources */ = {
298 | isa = PBXResourcesBuildPhase;
299 | buildActionMask = 2147483647;
300 | files = (
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | };
304 | 08906C481DF1A90400FC4209 /* Resources */ = {
305 | isa = PBXResourcesBuildPhase;
306 | buildActionMask = 2147483647;
307 | files = (
308 | );
309 | runOnlyForDeploymentPostprocessing = 0;
310 | };
311 | 08906C5B1DF1BBDD00FC4209 /* Resources */ = {
312 | isa = PBXResourcesBuildPhase;
313 | buildActionMask = 2147483647;
314 | files = (
315 | 08906C6A1DF1BBDE00FC4209 /* LaunchScreen.storyboard in Resources */,
316 | 08906C671DF1BBDE00FC4209 /* Assets.xcassets in Resources */,
317 | 08906C651DF1BBDE00FC4209 /* Main.storyboard in Resources */,
318 | );
319 | runOnlyForDeploymentPostprocessing = 0;
320 | };
321 | /* End PBXResourcesBuildPhase section */
322 |
323 | /* Begin PBXSourcesBuildPhase section */
324 | 084635DD1DF6D60000BE9EF1 /* Sources */ = {
325 | isa = PBXSourcesBuildPhase;
326 | buildActionMask = 2147483647;
327 | files = (
328 | 08C606EE1DF817E50014E07E /* TestSwiftScanner.swift in Sources */,
329 | 08C606ED1DF817E50014E07E /* NSScanner+Extenions.swift in Sources */,
330 | );
331 | runOnlyForDeploymentPostprocessing = 0;
332 | };
333 | 08906C451DF1A90400FC4209 /* Sources */ = {
334 | isa = PBXSourcesBuildPhase;
335 | buildActionMask = 2147483647;
336 | files = (
337 | 08906C771DF1C56C00FC4209 /* StringScanner.swift in Sources */,
338 | );
339 | runOnlyForDeploymentPostprocessing = 0;
340 | };
341 | 08906C591DF1BBDD00FC4209 /* Sources */ = {
342 | isa = PBXSourcesBuildPhase;
343 | buildActionMask = 2147483647;
344 | files = (
345 | 08906C621DF1BBDE00FC4209 /* ViewController.swift in Sources */,
346 | 08906C601DF1BBDE00FC4209 /* AppDelegate.swift in Sources */,
347 | );
348 | runOnlyForDeploymentPostprocessing = 0;
349 | };
350 | /* End PBXSourcesBuildPhase section */
351 |
352 | /* Begin PBXTargetDependency section */
353 | 084635E81DF6D60000BE9EF1 /* PBXTargetDependency */ = {
354 | isa = PBXTargetDependency;
355 | target = 08906C491DF1A90400FC4209 /* SwiftScanner */;
356 | targetProxy = 084635E71DF6D60000BE9EF1 /* PBXContainerItemProxy */;
357 | };
358 | 08906C701DF1BBE800FC4209 /* PBXTargetDependency */ = {
359 | isa = PBXTargetDependency;
360 | target = 08906C491DF1A90400FC4209 /* SwiftScanner */;
361 | targetProxy = 08906C6F1DF1BBE800FC4209 /* PBXContainerItemProxy */;
362 | };
363 | /* End PBXTargetDependency section */
364 |
365 | /* Begin PBXVariantGroup section */
366 | 08906C631DF1BBDE00FC4209 /* Main.storyboard */ = {
367 | isa = PBXVariantGroup;
368 | children = (
369 | 08906C641DF1BBDE00FC4209 /* Base */,
370 | );
371 | name = Main.storyboard;
372 | sourceTree = "";
373 | };
374 | 08906C681DF1BBDE00FC4209 /* LaunchScreen.storyboard */ = {
375 | isa = PBXVariantGroup;
376 | children = (
377 | 08906C691DF1BBDE00FC4209 /* Base */,
378 | );
379 | name = LaunchScreen.storyboard;
380 | sourceTree = "";
381 | };
382 | /* End PBXVariantGroup section */
383 |
384 | /* Begin XCBuildConfiguration section */
385 | 084635E91DF6D60000BE9EF1 /* Debug */ = {
386 | isa = XCBuildConfiguration;
387 | buildSettings = {
388 | CLANG_ENABLE_MODULES = YES;
389 | DEVELOPMENT_TEAM = E5DU3FA699;
390 | INFOPLIST_FILE = SwiftScannerTests/Info.plist;
391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
392 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScannerTests;
393 | PRODUCT_NAME = "$(TARGET_NAME)";
394 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftScannerTests/SwiftScannerTests-Bridging-Header.h";
395 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
396 | SWIFT_VERSION = 4.2;
397 | };
398 | name = Debug;
399 | };
400 | 084635EA1DF6D60000BE9EF1 /* Release */ = {
401 | isa = XCBuildConfiguration;
402 | buildSettings = {
403 | CLANG_ENABLE_MODULES = YES;
404 | DEVELOPMENT_TEAM = E5DU3FA699;
405 | INFOPLIST_FILE = SwiftScannerTests/Info.plist;
406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
407 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScannerTests;
408 | PRODUCT_NAME = "$(TARGET_NAME)";
409 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftScannerTests/SwiftScannerTests-Bridging-Header.h";
410 | SWIFT_VERSION = 4.2;
411 | };
412 | name = Release;
413 | };
414 | 08906C501DF1A90400FC4209 /* Debug */ = {
415 | isa = XCBuildConfiguration;
416 | buildSettings = {
417 | ALWAYS_SEARCH_USER_PATHS = NO;
418 | CLANG_ANALYZER_NONNULL = YES;
419 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
420 | CLANG_CXX_LIBRARY = "libc++";
421 | CLANG_ENABLE_MODULES = YES;
422 | CLANG_ENABLE_OBJC_ARC = YES;
423 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
424 | CLANG_WARN_BOOL_CONVERSION = YES;
425 | CLANG_WARN_COMMA = YES;
426 | CLANG_WARN_CONSTANT_CONVERSION = YES;
427 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
428 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
429 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
430 | CLANG_WARN_EMPTY_BODY = YES;
431 | CLANG_WARN_ENUM_CONVERSION = YES;
432 | CLANG_WARN_INFINITE_RECURSION = YES;
433 | CLANG_WARN_INT_CONVERSION = YES;
434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
439 | CLANG_WARN_STRICT_PROTOTYPES = YES;
440 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
441 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
442 | CLANG_WARN_UNREACHABLE_CODE = YES;
443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
445 | COPY_PHASE_STRIP = NO;
446 | CURRENT_PROJECT_VERSION = 1;
447 | DEBUG_INFORMATION_FORMAT = dwarf;
448 | ENABLE_STRICT_OBJC_MSGSEND = YES;
449 | ENABLE_TESTABILITY = YES;
450 | GCC_C_LANGUAGE_STANDARD = gnu99;
451 | GCC_DYNAMIC_NO_PIC = NO;
452 | GCC_NO_COMMON_BLOCKS = YES;
453 | GCC_OPTIMIZATION_LEVEL = 0;
454 | GCC_PREPROCESSOR_DEFINITIONS = (
455 | "DEBUG=1",
456 | "$(inherited)",
457 | );
458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
460 | GCC_WARN_UNDECLARED_SELECTOR = YES;
461 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
462 | GCC_WARN_UNUSED_FUNCTION = YES;
463 | GCC_WARN_UNUSED_VARIABLE = YES;
464 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
465 | MTL_ENABLE_DEBUG_INFO = YES;
466 | ONLY_ACTIVE_ARCH = YES;
467 | SDKROOT = iphoneos;
468 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
469 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
470 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
471 | TARGETED_DEVICE_FAMILY = "1,2";
472 | VERSIONING_SYSTEM = "apple-generic";
473 | VERSION_INFO_PREFIX = "";
474 | };
475 | name = Debug;
476 | };
477 | 08906C511DF1A90400FC4209 /* Release */ = {
478 | isa = XCBuildConfiguration;
479 | buildSettings = {
480 | ALWAYS_SEARCH_USER_PATHS = NO;
481 | CLANG_ANALYZER_NONNULL = YES;
482 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
483 | CLANG_CXX_LIBRARY = "libc++";
484 | CLANG_ENABLE_MODULES = YES;
485 | CLANG_ENABLE_OBJC_ARC = YES;
486 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
487 | CLANG_WARN_BOOL_CONVERSION = YES;
488 | CLANG_WARN_COMMA = YES;
489 | CLANG_WARN_CONSTANT_CONVERSION = YES;
490 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
491 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
492 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
493 | CLANG_WARN_EMPTY_BODY = YES;
494 | CLANG_WARN_ENUM_CONVERSION = YES;
495 | CLANG_WARN_INFINITE_RECURSION = YES;
496 | CLANG_WARN_INT_CONVERSION = YES;
497 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
498 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
499 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
501 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
502 | CLANG_WARN_STRICT_PROTOTYPES = YES;
503 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
504 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
505 | CLANG_WARN_UNREACHABLE_CODE = YES;
506 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
507 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
508 | COPY_PHASE_STRIP = NO;
509 | CURRENT_PROJECT_VERSION = 1;
510 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
511 | ENABLE_NS_ASSERTIONS = NO;
512 | ENABLE_STRICT_OBJC_MSGSEND = YES;
513 | GCC_C_LANGUAGE_STANDARD = gnu99;
514 | GCC_NO_COMMON_BLOCKS = YES;
515 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
516 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
517 | GCC_WARN_UNDECLARED_SELECTOR = YES;
518 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
519 | GCC_WARN_UNUSED_FUNCTION = YES;
520 | GCC_WARN_UNUSED_VARIABLE = YES;
521 | IPHONEOS_DEPLOYMENT_TARGET = 10.1;
522 | MTL_ENABLE_DEBUG_INFO = NO;
523 | SDKROOT = iphoneos;
524 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
525 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
526 | TARGETED_DEVICE_FAMILY = "1,2";
527 | VALIDATE_PRODUCT = YES;
528 | VERSIONING_SYSTEM = "apple-generic";
529 | VERSION_INFO_PREFIX = "";
530 | };
531 | name = Release;
532 | };
533 | 08906C531DF1A90400FC4209 /* Debug */ = {
534 | isa = XCBuildConfiguration;
535 | buildSettings = {
536 | CLANG_ENABLE_MODULES = YES;
537 | CODE_SIGN_IDENTITY = "";
538 | DEFINES_MODULE = YES;
539 | DEVELOPMENT_TEAM = E5DU3FA699;
540 | DYLIB_COMPATIBILITY_VERSION = 1;
541 | DYLIB_CURRENT_VERSION = 1;
542 | DYLIB_INSTALL_NAME_BASE = "@rpath";
543 | INFOPLIST_FILE = SwiftScanner/Info.plist;
544 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
546 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScanner;
547 | PRODUCT_NAME = "$(TARGET_NAME)";
548 | SKIP_INSTALL = YES;
549 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
550 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
551 | SWIFT_VERSION = 4.2;
552 | };
553 | name = Debug;
554 | };
555 | 08906C541DF1A90400FC4209 /* Release */ = {
556 | isa = XCBuildConfiguration;
557 | buildSettings = {
558 | CLANG_ENABLE_MODULES = YES;
559 | CODE_SIGN_IDENTITY = "";
560 | DEFINES_MODULE = YES;
561 | DEVELOPMENT_TEAM = E5DU3FA699;
562 | DYLIB_COMPATIBILITY_VERSION = 1;
563 | DYLIB_CURRENT_VERSION = 1;
564 | DYLIB_INSTALL_NAME_BASE = "@rpath";
565 | INFOPLIST_FILE = SwiftScanner/Info.plist;
566 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
568 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.SwiftScanner;
569 | PRODUCT_NAME = "$(TARGET_NAME)";
570 | SKIP_INSTALL = YES;
571 | SWIFT_SWIFT3_OBJC_INFERENCE = Off;
572 | SWIFT_VERSION = 4.2;
573 | };
574 | name = Release;
575 | };
576 | 08906C6D1DF1BBDE00FC4209 /* Debug */ = {
577 | isa = XCBuildConfiguration;
578 | buildSettings = {
579 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
580 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
581 | DEVELOPMENT_TEAM = E5DU3FA699;
582 | INFOPLIST_FILE = DemoApp/Info.plist;
583 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
584 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.DemoApp;
585 | PRODUCT_NAME = "$(TARGET_NAME)";
586 | SWIFT_VERSION = 4.2;
587 | };
588 | name = Debug;
589 | };
590 | 08906C6E1DF1BBDE00FC4209 /* Release */ = {
591 | isa = XCBuildConfiguration;
592 | buildSettings = {
593 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
594 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
595 | DEVELOPMENT_TEAM = E5DU3FA699;
596 | INFOPLIST_FILE = DemoApp/Info.plist;
597 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
598 | PRODUCT_BUNDLE_IDENTIFIER = com.danielemargutti.DemoApp;
599 | PRODUCT_NAME = "$(TARGET_NAME)";
600 | SWIFT_VERSION = 4.2;
601 | };
602 | name = Release;
603 | };
604 | /* End XCBuildConfiguration section */
605 |
606 | /* Begin XCConfigurationList section */
607 | 084635EB1DF6D60000BE9EF1 /* Build configuration list for PBXNativeTarget "SwiftScannerTests" */ = {
608 | isa = XCConfigurationList;
609 | buildConfigurations = (
610 | 084635E91DF6D60000BE9EF1 /* Debug */,
611 | 084635EA1DF6D60000BE9EF1 /* Release */,
612 | );
613 | defaultConfigurationIsVisible = 0;
614 | defaultConfigurationName = Release;
615 | };
616 | 08906C441DF1A90400FC4209 /* Build configuration list for PBXProject "SwiftScanner" */ = {
617 | isa = XCConfigurationList;
618 | buildConfigurations = (
619 | 08906C501DF1A90400FC4209 /* Debug */,
620 | 08906C511DF1A90400FC4209 /* Release */,
621 | );
622 | defaultConfigurationIsVisible = 0;
623 | defaultConfigurationName = Release;
624 | };
625 | 08906C521DF1A90400FC4209 /* Build configuration list for PBXNativeTarget "SwiftScanner" */ = {
626 | isa = XCConfigurationList;
627 | buildConfigurations = (
628 | 08906C531DF1A90400FC4209 /* Debug */,
629 | 08906C541DF1A90400FC4209 /* Release */,
630 | );
631 | defaultConfigurationIsVisible = 0;
632 | defaultConfigurationName = Release;
633 | };
634 | 08906C6C1DF1BBDE00FC4209 /* Build configuration list for PBXNativeTarget "DemoApp" */ = {
635 | isa = XCConfigurationList;
636 | buildConfigurations = (
637 | 08906C6D1DF1BBDE00FC4209 /* Debug */,
638 | 08906C6E1DF1BBDE00FC4209 /* Release */,
639 | );
640 | defaultConfigurationIsVisible = 0;
641 | defaultConfigurationName = Release;
642 | };
643 | /* End XCConfigurationList section */
644 | };
645 | rootObject = 08906C411DF1A90400FC4209 /* Project object */;
646 | }
647 |
--------------------------------------------------------------------------------
/Sources/SwiftScanner/StringScanner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringScanner.swift
3 | // StringScanner
4 | //
5 | // Created by Daniele Margutti on 03/12/2016.
6 | // Web: http://www.danielemargutti.com
7 | // Mail: hello@danielemargutti.com
8 | // Twitter: @danielemargutti
9 | // Copyright © 2016 Daniele Margutti. All rights reserved.
10 | //
11 | // The MIT License (MIT)
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in all
21 | // copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 | // SOFTWARE.
30 |
31 |
32 | import Foundation
33 |
34 | /// Throwable errors of the scanner
35 | ///
36 | /// - eof: end of file is reached
37 | /// - invalidInput: invalid input passed to caller function
38 | /// - notFound: failed to match or search value into the scanner's source string
39 | /// - invalidInt: invalid int found, not the expected format
40 | /// - invalidFloat: invalid float found, not the expected format
41 | /// - invalidHEX: invalid HEX found, not the expected format
42 | public enum StringScannerError: Error {
43 | case eof
44 | case notFound
45 | case invalidInput
46 | case invalidInt
47 | case invalidFloat
48 | case invalidHEX
49 | }
50 |
51 | /// Bit digits representation for HEX values
52 | public enum BitDigits: Int {
53 | case bit8 = 8
54 | case bit16 = 16
55 | case bit32 = 32
56 | case bit64 = 64
57 |
58 | var length: Int {
59 | switch self {
60 | case .bit8: return 2
61 | case .bit16: return 4
62 | case .bit32: return 8
63 | case .bit64: return 16
64 | }
65 | }
66 | }
67 |
68 | public class StringScanner {
69 |
70 | public typealias SIndex = String.UnicodeScalarView.Index
71 | public typealias SString = String.UnicodeScalarView
72 |
73 | //---------------
74 | //MARK: Variables
75 | //---------------
76 |
77 | /// Thats the current data of the scanner.
78 | /// Internally it's represented by `String.UnicodeScalarView`
79 | public fileprivate(set) var string: SString
80 |
81 | /// Current scanner's index position
82 | public fileprivate(set) var position: SIndex
83 |
84 | /// Get the first index of the source string
85 | public var startIndex: SIndex {
86 | return self.string.startIndex
87 | }
88 |
89 | /// Get the last index of the source string
90 | public var endIndex: SIndex {
91 | return self.string.endIndex
92 | }
93 |
94 | /// Number of scalars consumed up to `position`
95 | /// Since String.UnicodeScalarView.Index is not a RandomAccessIndex,
96 | /// this makes determining the position *much* easier)
97 | public fileprivate(set) var consumed: Int
98 |
99 | /// Return true if scanner reached the end of the string
100 | public var isAtEnd: Bool {
101 | return (self.position == string.endIndex)
102 | }
103 |
104 | /// Returns the content of scanner's string from current value of the position till the end
105 | /// `position` its not updated by the operation.
106 | ///
107 | /// - Returns: remainder string till the end of source's data
108 | public var remainder: String {
109 | var remString: String = ""
110 | var idx = self.position
111 | while idx != self.string.endIndex {
112 | remString.unicodeScalars.append(self.string[idx])
113 | idx = self.string.index(after:idx)
114 | }
115 | return remString
116 | }
117 |
118 | //-----------------
119 | //MARK: Init
120 | //-----------------
121 |
122 | /// Initialize a new scanner instance from given source string
123 | ///
124 | /// - Parameter string: source string
125 | public init(_ string: String) {
126 | self.string = string.unicodeScalars
127 | self.position = self.string.startIndex
128 | self.consumed = 0
129 | }
130 |
131 | //-----------------
132 | //MARK: Scan Values
133 | //-----------------
134 |
135 | /// If the current scanner position is not at eof return the next scalar at position and move `position` to the next index
136 | /// Otherwise it throws with .eof and `position` is not updated.
137 | ///
138 | /// - Returns: the next scalar
139 | /// - Throws: throw .eof if reached
140 | @discardableResult
141 | public func scanChar() throws -> UnicodeScalar {
142 | guard self.position != self.string.endIndex else {
143 | throw StringScannerError.eof
144 | }
145 | let char = self.string[self.position]
146 | self.position = self.string.index(after: self.position)
147 | self.consumed += 1
148 | return char
149 | }
150 |
151 | /// Scan the next integer value after the current scalar position; consume scalars from {0..9} until a non numeric
152 | /// value is encountered. Return the integer representation in base 10.
153 | /// `position` index is advanced to the end of the number.
154 | /// Throws .invalidInt if scalar at the current `position` is not in the range `"0"` to `"9"`
155 | ///
156 | /// - Returns: read integer in base 10
157 | /// - Throws: throw .invalidInt if non numeric value is encountered
158 | public func scanInt() throws -> Int {
159 | var parsedInt = 0
160 | try self.session(peek: true, accumulate: false, block: { i,c in
161 | while i != self.string.endIndex && self.string[i] >= "0" && self.string[i] <= "9" {
162 | parsedInt = parsedInt * 10 + Int(self.string[i].value - UnicodeScalar("0").value )
163 | i = self.string.index(after: i)
164 | c += 1
165 | }
166 | if i == self.position {
167 | throw StringScannerError.invalidInt
168 | }
169 | // okay valid, store changes to index
170 | self.position = i
171 | self.consumed = c
172 | })
173 | return parsedInt
174 | }
175 |
176 | /// Scan for float value (xx.xx) and convert it into Float.
177 | /// Return the float representation in base 10.
178 | /// `position` index is advanced to the end of the number only if conversion works, otherwise it will be not updated and
179 | /// .invalidFloat is thrown.
180 | ///
181 | /// - Returns: parsed float value
182 | /// - Throws: throw an exception .invalidFloat or .eof according to the error
183 | public func scanFloat() throws -> Float {
184 | let prevConsumed = self.consumed
185 |
186 | func throwAndBackBy(length: Int) {
187 | self.position = self.string.index(self.position, offsetBy: -length)
188 | self.consumed -= length
189 | }
190 |
191 | guard let strValue = try self.scan(untilIn: CharacterSet(charactersIn: "-+0123456789.")) else {
192 | throw StringScannerError.invalidFloat
193 | }
194 | guard let value = Float(strValue) else {
195 | throw StringScannerError.invalidFloat
196 | }
197 | return value
198 | }
199 |
200 | /// Scan an HEX digit expressed as 0x/0X or # where number is the value expressed with according bit number
201 | /// If scan succeded it return parsed value and `position` is updated at the end of the value.
202 | /// If scan fail function throws and no values are updated
203 | ///
204 | /// - Parameter digits: type of digits to parse (8,16,32 or 64 bits)
205 | /// - Returns: the int value in base 10 converted from HEX base
206 | /// - Throws: throw .hexExpected if value is not expressed in HEX string according to specified digits
207 | public func scanHexInt(_ digits: BitDigits = .bit16) throws -> Int {
208 |
209 | func parseHexInt(_ digits: BitDigits, _ consumed: inout Int) throws -> Int {
210 | let strValue = try self.scan(length: digits.length)
211 | consumed += digits.length
212 | guard let parsedInt = Int(strValue, radix: 16) else {
213 | throw StringScannerError.invalidHEX
214 | }
215 | return parsedInt
216 | }
217 |
218 | var value: Int = 0
219 | var consumed: Int = 0
220 | if self.string[self.position] == "#" { // the format start with #, numbers is following
221 | try self.move(1, accumulate: false)
222 | consumed += 1 // move on to digits and parse them
223 | value = try parseHexInt(digits, &consumed)
224 | } else {
225 | do {
226 | let prefix = try self.scan(length: 2).uppercased()
227 | consumed += 2
228 | guard prefix == "0X" else { // hex is in 0x or 0X format followed by values
229 | throw StringScannerError.invalidHEX
230 | }
231 | value = try parseHexInt(digits, &consumed)
232 | } catch {
233 | // something went wrong and value cannot be parsed,
234 | // go back with the `position` index and throw an errro
235 | try! self.back(length: consumed)
236 | throw StringScannerError.invalidHEX
237 | }
238 | }
239 | return value
240 | }
241 |
242 | /// Scan until given character is found starting from current scanner `position` till the end of the source string.
243 | /// Scanner's `position` is updated only if character is found and set just before it.
244 | /// Throw an exception if .eof is reached or .notFound if char was not found (in this case scanner's position is not updated)
245 | ///
246 | /// - Parameter char: scalar to search
247 | /// - Returns: the string until the character (excluded)
248 | /// - Throws: throw an exception on .eof or .notFound
249 | public func scan(upTo char: UnicodeScalar) throws -> String? {
250 | return try self.move(peek: false, upTo: char).string
251 | }
252 |
253 | /// Scan until given character's is found.
254 | /// Index is reported before the start of the sequence, scanner's `position` is updated only if sequence is found.
255 | /// Throw an exception if .eof is reached or .notFound if sequence was not found
256 | ///
257 | /// - Parameter charSet: character set to search
258 | /// - Returns: found index
259 | /// - Throws: throw .eof or .notFound
260 | public func scan(upTo charSet: CharacterSet) throws -> String? {
261 | return try self.move(peek: false, accumulate: true, upToCharSet: charSet).string
262 | }
263 |
264 | /// Scan until the next character of the scanner is contained into given character set
265 | /// Scanner's position is updated automatically at the end of the sequence if validated, otherwise it will not touched.
266 | ///
267 | /// - Parameter charSet: chracters set
268 | /// - Returns: the string accumulated scanning until chars set is evaluated
269 | /// - Throws: throw .eof or .notFound
270 | public func scan(untilIn charSet: CharacterSet) throws -> String? {
271 | return try self.move(peek: false, accumulate: true, untilIn: charSet).string
272 | }
273 |
274 | /// Scan until specified string is encountered and update indexes if found
275 | /// Throw an exception if .eof is reached or string is not found
276 | ///
277 | /// - Parameter string: string to search
278 | /// - Returns: string up to search string (excluded)
279 | /// - Throws: throw .eof or .notFound
280 | @discardableResult
281 | public func scan(upTo string: String) throws -> String? {
282 | return try self.move(peek: false, upTo: string).string
283 | }
284 |
285 | /// Scan and consume at the scalar starting from current position, testing it with function test.
286 | /// If test returns `true`, the `position` increased. If `false`, the function returns.
287 | ///
288 | /// - Parameter test: test to pass
289 | /// - Returns: accumulated string
290 | @discardableResult
291 | public func scan(untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> String {
292 | return self.move(peek: false, accumulate: true, untilTrue: test).string!
293 | }
294 |
295 | /// Read next length characters and accumulate it
296 | /// If operation is succeded scanner's `position` are updated according to consumed scalars.
297 | /// If fails an exception is thrown and `position` is not updated
298 | ///
299 | /// - Parameter length: number of scalar to ad
300 | /// - Returns: captured string
301 | /// - Throws: throw an .eof exception
302 | @discardableResult
303 | public func scan(length: Int = 1) throws -> String {
304 | return try self.move(length, accumulate: true).string!
305 | }
306 |
307 | //-----------------
308 | //MARK: Peek Values
309 | //-----------------
310 |
311 | /// Peek until chracter is found starting from current scanner's `position`.
312 | /// Scanner's position is never updated.
313 | /// Throw an exception if .eof is reached or .notFound if char was not found.
314 | ///
315 | /// - Parameter char: scalar to search
316 | /// - Returns: the index found
317 | /// - Throws: throw an exception on .eof or .notFound
318 | public func peek(upTo char: UnicodeScalar) throws -> SIndex {
319 | return try self.move(peek: true, upTo: char).index
320 | }
321 |
322 | /// Peek until one the characters specified by set is encountered
323 | /// Index is reported before the start of the sequence, but scanner's `position` is never updated.
324 | /// Throw an exception if .eof is reached or .notFound if sequence was not found
325 | ///
326 | /// - Parameter charSet: scalar set to search
327 | /// - Returns: found index
328 | /// - Throws: throw .eof or .notFound
329 | public func peek(upTo charSet: CharacterSet) throws -> SIndex {
330 | return try self.move(peek: true, accumulate: false, upToCharSet: charSet).index
331 | }
332 |
333 | /// Peek until the next character of the scanner is contained into given.
334 | /// Scanner's `position` is never updated.
335 | ///
336 | /// - Parameter charSet: characters set to evaluate
337 | /// - Returns: the index at the end of the sequence
338 | /// - Throws: throw .eof or .notFound
339 | public func peek(untilIn charSet: CharacterSet) throws -> SIndex {
340 | let (endIndex,_) = try self.move(peek: true, accumulate: false, untilIn: charSet)
341 | return endIndex
342 | }
343 |
344 | /// Iterate until specified string is encountered without updating indexes.
345 | /// Scanner's `position` is never updated but it's reported the index just before found occourence.
346 | ///
347 | /// - Parameter string: string to search
348 | /// - Returns: index where found string was found
349 | /// - Throws: throw .eof or .notFound
350 | public func peek(upTo string: String) throws -> SIndex {
351 | return try self.move(peek: true, upTo: string).index
352 | }
353 |
354 | /// Peeks at the scalar at the current position, testing it with function test.
355 | /// It only peeks so current scanner's `position` is not increased at the end of the operation
356 | ///
357 | /// - Parameter test: test to pass
358 | public func peek(untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> SIndex {
359 | return self.move(peek: true, accumulate: false, untilTrue: test).index
360 | }
361 |
362 | //-----------
363 | //MARK: Match
364 | //-----------
365 |
366 | /// Throw if the scalar at the current position don't match given scalar.
367 | /// Advance scanner's `position` to the end of the match.
368 | ///
369 | /// - Parameter char: scalar to match
370 | /// - Throws: throw if does not match or index reaches eof
371 | @discardableResult
372 | public func match(_ char: UnicodeScalar) -> Bool {
373 | guard self.position != self.string.endIndex else {
374 | return false
375 | }
376 | if self.string[self.position] != char {
377 | return false
378 | }
379 | // Advance by one scalar, the one we matched
380 | self.position = self.string.index(after: self.position)
381 | self.consumed += 1
382 | return true
383 | }
384 |
385 | /// Throw if scalars starting at the current position don't match scalars in given string.
386 | /// Advance scanner's `position` to the end of the match string.
387 | ///
388 | /// - Parameter string: string to match
389 | /// - Throws: throw if does not match or index reaches eof
390 | public func match(_ match: String) -> Bool {
391 | var result: Bool = true
392 | try! self.session(peek: false, accumulate: false, block: { i,c in
393 | for char in match.unicodeScalars {
394 | if i == self.string.endIndex {
395 | result = false
396 | break
397 | }
398 | if self.string[i] != char {
399 | result = false
400 | break
401 | }
402 | i = self.string.index(after: i)
403 | c += 1
404 | }
405 | })
406 | return result
407 | }
408 |
409 | //------------
410 | //MARK: Others
411 | //------------
412 |
413 | /// Move scanner's `position` to the start of the string
414 | public func reset() {
415 | self.position = self.string.startIndex
416 | self.consumed = 0
417 | }
418 |
419 | /// Move to the index's end index
420 | public func peekAtEnd() {
421 | self.position = self.string.endIndex
422 | let distance = self.string.distance(from: self.string.startIndex, to: self.position)
423 | self.consumed = distance
424 | }
425 |
426 | /// Attempt to advance scanner's by length
427 | /// If operation is not possible (reached the end of the string) it throws and current scanner's `position` of the index did not change
428 | /// If operation succeded scanner's `position` is updated.
429 | ///
430 | /// - Parameter length: length to advance
431 | /// - Throws: throw if .eof
432 | public func skip(length: Int = 1) throws {
433 | try self.move(length, accumulate: false)
434 | }
435 |
436 | /// Attempt to advance scanner past all characters in the provided
437 | /// character set.
438 | /// If the operation is not possible (reached the end of the string),
439 | /// it throws and current scanner's `position` of the index did not change
440 | /// If operation succeded, the scanner's `position` is updated.
441 | ///
442 | /// - Parameter characterSet: The set of characters to skip.
443 | /// - Throws: throw if .eof
444 | public func skip(charactersIn characterSet: CharacterSet) throws {
445 | _ = try self.move(peek: false, accumulate: false, whileIn: characterSet)
446 | }
447 |
448 | /// Attempt to advance the position back by length
449 | /// If operation fails scanner's `position` is not touched
450 | /// If operation succeded scaner's`position` is modified according to new value
451 | ///
452 | /// - Parameter length: length to move
453 | /// - Throws: throw if .eof
454 | public func back(length: Int = 1) throws {
455 | guard length <= self.consumed else { // more than we can go back
456 | throw StringScannerError.invalidInput
457 | }
458 | if length == 1 {
459 | self.position = self.string.index(self.position, offsetBy: -1)
460 | self.consumed -= 1
461 | return
462 | }
463 |
464 | var l = length
465 | while l > 0 {
466 | self.position = self.string.index(self.position, offsetBy: -1)
467 | self.consumed -= 1
468 | l -= 1
469 | }
470 | }
471 |
472 | //--------------------
473 | //MARK: Private Funcs
474 | //--------------------
475 |
476 | @discardableResult
477 | private func move(_ length: Int = 1, accumulate: Bool) throws -> (index: SIndex, string: String?) {
478 |
479 | if length == 1 && self.position != self.string.endIndex {
480 | // Special case if proposed length is a single character
481 | self.position = self.string.index(after: self.position)
482 | self.consumed += 1
483 | return (self.position, String(self.string[self.string.index(before: self.position)]))
484 | }
485 |
486 | // Use temporary indexes and don't touch the real ones until we are
487 | // sure the operation succeded
488 | var proposedIdx = self.position
489 | var initialPosition = self.position
490 | var proposedConsumed = 0
491 |
492 | var remaining = length
493 | while remaining > 0 {
494 | if proposedIdx == self.string.endIndex {
495 | throw StringScannerError.eof
496 | }
497 | proposedIdx = self.string.index(after: proposedIdx)
498 | proposedConsumed += 1
499 | remaining -= 1
500 | }
501 |
502 | var result: String? = nil
503 | if accumulate == true { // if user need accumulate string we want to provide it
504 | result = ""
505 | result!.reserveCapacity( (proposedConsumed - self.consumed) ) // just an optimization
506 | while initialPosition != proposedIdx {
507 | result!.unicodeScalars.append(self.string[initialPosition])
508 | initialPosition = self.string.index(after: initialPosition)
509 | }
510 | }
511 | // Write changes only if skip operation succeded
512 | self.position = proposedIdx
513 | self.consumed = proposedConsumed
514 | return (self.position,result)
515 | }
516 |
517 |
518 | /// Move the index until scalar at given index is part of passed char set, then return the index til it and accumulated string (if requested)
519 | ///
520 | /// - Parameters:
521 | /// - peek: peek to perform a non destructive operation to scanner's `position`
522 | /// - accumulate: accumulate return a valid string in output sum of the scan operation
523 | /// - charSet: character set target of the operation
524 | /// - Returns: index and content of the string
525 | /// - Throws: throw .notFound if string is not found or .eof if end of file is reached
526 | private func move(peek: Bool, accumulate: Bool, untilIn charSet: CharacterSet) throws -> (index: SIndex, string: String?) {
527 | if charSet.contains(self.string[self.position]) == false { // ops
528 | throw StringScannerError.notFound
529 | }
530 |
531 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in
532 | while i != self.string.endIndex && charSet.contains(self.string[i]) {
533 | i = self.string.index(after: i)
534 | c += 1
535 | }
536 | })
537 | }
538 |
539 | /// Move the index while scalar at current index is part of passed char set,
540 | /// then return the index after it and the accumulated string (if requested)
541 | ///
542 | /// - Parameters:
543 | /// - peek: peek to perform a non destructive operation to scanner's `position`
544 | /// - accumulate: accumulate return a valid string in output sum of the scan operation
545 | /// - charSet: character set target of the operation
546 | /// - Returns: index and content of the string
547 | /// - Throws: throw .notFound if string is not found or .eof if end of file is reached
548 | private func move(peek: Bool, accumulate: Bool,
549 | whileIn charSet: CharacterSet) throws -> (index: SIndex, string: String?) {
550 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in
551 | while i != self.string.endIndex && charSet.contains(self.string[i]) {
552 | i = self.string.index(after: i)
553 | c += 1
554 | }
555 | })
556 | }
557 |
558 | /// Move up to passed scalar is found
559 | ///
560 | /// - Parameters:
561 | /// - peek: peek to perform a non destructive operation to scanner's `position`
562 | /// - char: given scalar to search
563 | /// - Returns: index til found character and accumulated string
564 | /// - Throws: throw .notFound or .eof
565 | @discardableResult
566 | private func move(peek: Bool, upTo char: UnicodeScalar) throws -> (index: SIndex, string: String?) {
567 | return try self.session(peek: peek, accumulate: true, block: { i,c in
568 | // continue moving forward until we reach the end of scanner's string
569 | // or current character at scanner's string current position differs from we are searching for
570 | while i != self.string.endIndex && self.string[i] != char {
571 | i = self.string.index(after: i)
572 | c += 1
573 | }
574 | })
575 | }
576 |
577 | /// Move next scalar until a character specified in character set is found and return the index and accumulated string (if requested)
578 | ///
579 | /// - Parameters:
580 | /// - peek: peek to perform a non destructive operation to scanner's `position`
581 | /// - accumulate: true to get accumulated string til found index
582 | /// - charSet: character set target of the operation
583 | /// - Returns: index and accumulated string
584 | /// - Throws: throw .eof or .notFound
585 | @discardableResult
586 | private func move(peek: Bool, accumulate: Bool, upToCharSet charSet: CharacterSet) throws -> (index: SIndex, string: String?) {
587 | return try self.session(peek: peek, accumulate: accumulate, block: { i,c in
588 | // continue moving forward until we reach the end of scanner's string
589 | // or current character at scanner's string current position differs from we are searching for
590 | while i != self.string.endIndex && charSet.contains(self.string[i]) == false {
591 | i = self.string.index(after: i)
592 | c += 1
593 | }
594 | })
595 | }
596 |
597 | /// Move next scalar until specified test for current scalar return true, then get the index and accumulated string
598 | ///
599 | /// - Parameters:
600 | /// - peek: peek to perform a non destructive operation to scanner's `position`
601 | /// - accumulate: true to get accumulated string until test return true
602 | /// - test: test
603 | /// - Returns: throw .eof or .notFound
604 | @discardableResult
605 | private func move(peek: Bool, accumulate: Bool, untilTrue test: ((UnicodeScalar) -> (Bool)) ) -> (index: SIndex, string: String?) {
606 | return try! self.session(peek: peek, accumulate: accumulate, block: { i,c in
607 | while i != self.string.endIndex {
608 | if test(self.string[i]) == false { // test is not passed, we return
609 | return
610 | }
611 | i = self.string.index(after: i)
612 | c += 1
613 | }
614 | })
615 | }
616 |
617 |
618 | /// Move next scalar until specified string is found, then get the index and accumulated string
619 | ///
620 | /// - Parameters:
621 | /// - peek: peek to perform a non destructive operation to scanner's `position`
622 | /// - string: string to search
623 | /// - Returns: index and string
624 | /// - Throws: throw .eof or .notFound
625 | @discardableResult
626 | private func move(peek: Bool, upTo string: String) throws -> (index: SIndex, string: String?) {
627 | let search = string.unicodeScalars
628 | guard let firstSearchChar = search.first else { // Invalid search string
629 | throw StringScannerError.invalidInput
630 | }
631 | if search.count == 1 { // If we are searching for a single char we want to forward call to the specific func
632 | return try self.move(peek: peek, upTo: firstSearchChar)
633 | }
634 |
635 | return try self.session(peek: peek, accumulate: true, block: { i,c in
636 |
637 | let remainderSearch = search[search.index(after: search.startIndex).. (Void) )
703 | throws -> (index: SIndex, string: String?) {
704 | // Keep in track with status of the position and consumed indexes before anything change
705 | var initialPosition = self.position
706 | let initialConsumed = self.consumed
707 |
708 | var sessionPosition = self.position
709 | var sessionConsumed = 0
710 |
711 | // execute the real code into block
712 | try block(&sessionPosition,&sessionConsumed)
713 |
714 | if sessionConsumed == 0 {
715 | return (sessionPosition,nil)
716 | }
717 |
718 | var result: String? = nil
719 | if accumulate == true {
720 | result = ""
721 | result!.reserveCapacity( (sessionConsumed - initialConsumed) ) // just an optimization
722 | while initialPosition != sessionPosition {
723 | result!.unicodeScalars.append(self.string[initialPosition])
724 | initialPosition = self.string.index(after: initialPosition)
725 | }
726 | }
727 |
728 | if peek == false { // Write changes to the main scanner's indexes
729 | self.position = sessionPosition
730 | self.consumed += sessionConsumed
731 | }
732 | return (sessionPosition,result)
733 | }
734 |
735 | }
736 |
--------------------------------------------------------------------------------