├── .gitignore
├── .swift-version
├── .travis.yml
├── FootlessParser.podspec
├── FootlessParser.xcodeproj
├── FootlessParserTests_Info.plist
├── FootlessParser_Info.plist
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── FootlessParser.xcscheme
│ └── xcschememanagement.plist
├── LICENSE.txt
├── Package.swift
├── README.md
├── Sources
└── FootlessParser
│ ├── Converters.swift
│ ├── Error.swift
│ ├── Parser+Operators.swift
│ ├── Parser.swift
│ ├── Parsers.swift
│ └── StringParser.swift
└── Tests
├── FootlessParserTests
├── Grammar
│ ├── CSV-quotes-large.csv
│ ├── CSV-quotes.csv
│ ├── CSV.swift
│ └── Examples.swift
├── Parser+Operators_Tests.swift
├── Parser_Tests.swift
├── PerformanceTests.swift
├── StringParser_Tests.swift
└── TestHelpers.swift
└── LinuxMain.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | .DS_Store
4 | /build/
5 | *.pbxuser
6 | !default.pbxuser
7 | *.mode1v3
8 | !default.mode1v3
9 | *.mode2v3
10 | !default.mode2v3
11 | *.perspectivev3
12 | !default.perspectivev3
13 | xcuserdata
14 | *.xccheckout
15 | *.moved-aside
16 | DerivedData
17 | *.hmap
18 | *.ipa
19 | /.build
20 | /Packages
21 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - name: "Linux Swift 4.1.3"
4 | os: linux
5 | language: generic
6 | dist: trusty
7 | sudo: required
8 | env:
9 | - SWIFT_BRANCH=swift-4.1.3-release
10 | - SWIFT_VERSION=swift-4.1.3-RELEASE
11 | install:
12 | - mkdir swift
13 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
14 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
15 |
16 | - name: "Linux Swift 4.2.3"
17 | os: linux
18 | language: generic
19 | dist: trusty
20 | sudo: required
21 | env:
22 | - SWIFT_BRANCH=swift-4.2.3-release
23 | - SWIFT_VERSION=swift-4.2.3-RELEASE
24 | install:
25 | - mkdir swift
26 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
27 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
28 |
29 | - name: "Linux Swift 5.0"
30 | os: linux
31 | language: generic
32 | dist: trusty
33 | sudo: required
34 | env:
35 | - SWIFT_BRANCH=swift-5.0-release
36 | - SWIFT_VERSION=swift-5.0-RELEASE
37 | install:
38 | - mkdir swift
39 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
40 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
41 |
42 | - name: "macOS Xcode 9.4"
43 | os: osx
44 | osx_image: xcode9.4
45 | language: generic
46 | sudo: required
47 |
48 | - name: "macOS Xcode 10.2"
49 | os: osx
50 | osx_image: xcode10.2
51 | language: generic
52 | sudo: required
53 | install:
54 | - gem install xcpretty-travis-formatter
55 | script:
56 | - swift --version
57 | - swift package reset
58 | - swift build
59 | - swift test
60 | - xcodebuild test -scheme FootlessParser | xcpretty -f `xcpretty-travis-formatter`
61 | - pod repo update --silent; pod lib lint --allow-warnings
62 |
63 | script:
64 | - swift --version
65 | - swift package reset
66 | - swift build
67 | - swift test
68 |
--------------------------------------------------------------------------------
/FootlessParser.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'FootlessParser'
3 | s.version = '0.5.2'
4 | s.summary = 'A simple parser combinator written in Swift'
5 | s.description = 'FootlessParser is a simple and pretty naive implementation of a parser combinator in Swift. It enables infinite lookahead, non-ambiguous parsing with error reporting.'
6 | s.homepage = 'https://github.com/kareman/FootlessParser'
7 | s.license = { type: 'MIT', file: 'LICENSE.txt' }
8 | s.author = { 'Kare Morstol' => 'kare@nottoobadsoftware.com' }
9 | s.source = { git: 'https://github.com/kareman/FootlessParser.git', tag: s.version.to_s }
10 | s.source_files = 'Sources/FootlessParser/*.swift'
11 | s.osx.deployment_target = '10.10'
12 | s.ios.deployment_target = '9.0'
13 | end
14 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/FootlessParserTests_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/FootlessParser_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 400
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | OBJ_35 /* Converters.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Converters.swift */; };
11 | OBJ_36 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Error.swift */; };
12 | OBJ_38 /* Parser+Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Parser+Operators.swift */; };
13 | OBJ_39 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Parser.swift */; };
14 | OBJ_40 /* Parsers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Parsers.swift */; };
15 | OBJ_41 /* StringParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* StringParser.swift */; };
16 | OBJ_49 /* Parser+Operators_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Parser+Operators_Tests.swift */; };
17 | OBJ_50 /* Parser_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* Parser_Tests.swift */; };
18 | OBJ_51 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* PerformanceTests.swift */; };
19 | OBJ_52 /* StringParser_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* StringParser_Tests.swift */; };
20 | OBJ_53 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* TestHelpers.swift */; };
21 | OBJ_54 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* CSV.swift */; };
22 | OBJ_55 /* Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* Examples.swift */; };
23 | OBJ_57 /* FootlessParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* FootlessParser.framework */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXContainerItemProxy section */
27 | BA746BB51E41367500B9A0F4 /* PBXContainerItemProxy */ = {
28 | isa = PBXContainerItemProxy;
29 | containerPortal = OBJ_1 /* Project object */;
30 | proxyType = 1;
31 | remoteGlobalIDString = OBJ_30;
32 | remoteInfo = FootlessParser;
33 | };
34 | /* End PBXContainerItemProxy section */
35 |
36 | /* Begin PBXFileReference section */
37 | OBJ_10 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; };
38 | OBJ_12 /* Parser+Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Parser+Operators.swift"; sourceTree = ""; };
39 | OBJ_13 /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; };
40 | OBJ_14 /* Parsers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parsers.swift; sourceTree = ""; };
41 | OBJ_15 /* StringParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringParser.swift; sourceTree = ""; };
42 | OBJ_19 /* Parser+Operators_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Parser+Operators_Tests.swift"; sourceTree = ""; };
43 | OBJ_20 /* Parser_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser_Tests.swift; sourceTree = ""; };
44 | OBJ_21 /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; };
45 | OBJ_22 /* StringParser_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringParser_Tests.swift; sourceTree = ""; };
46 | OBJ_23 /* TestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; };
47 | OBJ_25 /* CSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = ""; };
48 | OBJ_26 /* Examples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Examples.swift; sourceTree = ""; };
49 | OBJ_28 /* FootlessParser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FootlessParser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50 | OBJ_29 /* FootlessParserTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = FootlessParserTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
51 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
52 | OBJ_9 /* Converters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converters.swift; sourceTree = ""; };
53 | /* End PBXFileReference section */
54 |
55 | /* Begin PBXFrameworksBuildPhase section */
56 | OBJ_42 /* Frameworks */ = {
57 | isa = PBXFrameworksBuildPhase;
58 | buildActionMask = 0;
59 | files = (
60 | );
61 | runOnlyForDeploymentPostprocessing = 0;
62 | };
63 | OBJ_56 /* Frameworks */ = {
64 | isa = PBXFrameworksBuildPhase;
65 | buildActionMask = 0;
66 | files = (
67 | OBJ_57 /* FootlessParser.framework in Frameworks */,
68 | );
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | /* End PBXFrameworksBuildPhase section */
72 |
73 | /* Begin PBXGroup section */
74 | OBJ_16 /* Tests */ = {
75 | isa = PBXGroup;
76 | children = (
77 | OBJ_17 /* FootlessParserTests */,
78 | );
79 | path = Tests;
80 | sourceTree = "";
81 | };
82 | OBJ_17 /* FootlessParserTests */ = {
83 | isa = PBXGroup;
84 | children = (
85 | OBJ_19 /* Parser+Operators_Tests.swift */,
86 | OBJ_20 /* Parser_Tests.swift */,
87 | OBJ_21 /* PerformanceTests.swift */,
88 | OBJ_22 /* StringParser_Tests.swift */,
89 | OBJ_23 /* TestHelpers.swift */,
90 | OBJ_24 /* Grammar */,
91 | );
92 | name = FootlessParserTests;
93 | path = Tests/FootlessParserTests;
94 | sourceTree = SOURCE_ROOT;
95 | };
96 | OBJ_24 /* Grammar */ = {
97 | isa = PBXGroup;
98 | children = (
99 | OBJ_25 /* CSV.swift */,
100 | OBJ_26 /* Examples.swift */,
101 | );
102 | path = Grammar;
103 | sourceTree = "";
104 | };
105 | OBJ_27 /* Products */ = {
106 | isa = PBXGroup;
107 | children = (
108 | OBJ_28 /* FootlessParser.framework */,
109 | OBJ_29 /* FootlessParserTests.xctest */,
110 | );
111 | name = Products;
112 | sourceTree = BUILT_PRODUCTS_DIR;
113 | };
114 | OBJ_5 = {
115 | isa = PBXGroup;
116 | children = (
117 | OBJ_6 /* Package.swift */,
118 | OBJ_7 /* Sources */,
119 | OBJ_16 /* Tests */,
120 | OBJ_27 /* Products */,
121 | );
122 | sourceTree = "";
123 | };
124 | OBJ_7 /* Sources */ = {
125 | isa = PBXGroup;
126 | children = (
127 | OBJ_8 /* FootlessParser */,
128 | );
129 | path = Sources;
130 | sourceTree = "";
131 | };
132 | OBJ_8 /* FootlessParser */ = {
133 | isa = PBXGroup;
134 | children = (
135 | OBJ_9 /* Converters.swift */,
136 | OBJ_10 /* Error.swift */,
137 | OBJ_12 /* Parser+Operators.swift */,
138 | OBJ_13 /* Parser.swift */,
139 | OBJ_14 /* Parsers.swift */,
140 | OBJ_15 /* StringParser.swift */,
141 | );
142 | name = FootlessParser;
143 | path = Sources/FootlessParser;
144 | sourceTree = SOURCE_ROOT;
145 | };
146 | /* End PBXGroup section */
147 |
148 | /* Begin PBXNativeTarget section */
149 | OBJ_30 /* FootlessParser */ = {
150 | isa = PBXNativeTarget;
151 | buildConfigurationList = OBJ_31 /* Build configuration list for PBXNativeTarget "FootlessParser" */;
152 | buildPhases = (
153 | OBJ_34 /* Sources */,
154 | OBJ_42 /* Frameworks */,
155 | );
156 | buildRules = (
157 | );
158 | dependencies = (
159 | );
160 | name = FootlessParser;
161 | productName = FootlessParser;
162 | productReference = OBJ_28 /* FootlessParser.framework */;
163 | productType = "com.apple.product-type.framework";
164 | };
165 | OBJ_43 /* FootlessParserTests */ = {
166 | isa = PBXNativeTarget;
167 | buildConfigurationList = OBJ_44 /* Build configuration list for PBXNativeTarget "FootlessParserTests" */;
168 | buildPhases = (
169 | OBJ_47 /* Sources */,
170 | OBJ_56 /* Frameworks */,
171 | );
172 | buildRules = (
173 | );
174 | dependencies = (
175 | OBJ_58 /* PBXTargetDependency */,
176 | );
177 | name = FootlessParserTests;
178 | productName = FootlessParserTests;
179 | productReference = OBJ_29 /* FootlessParserTests.xctest */;
180 | productType = "com.apple.product-type.bundle.unit-test";
181 | };
182 | /* End PBXNativeTarget section */
183 |
184 | /* Begin PBXProject section */
185 | OBJ_1 /* Project object */ = {
186 | isa = PBXProject;
187 | attributes = {
188 | LastUpgradeCheck = 9999;
189 | TargetAttributes = {
190 | OBJ_30 = {
191 | LastSwiftMigration = 1020;
192 | };
193 | OBJ_43 = {
194 | LastSwiftMigration = 1020;
195 | };
196 | };
197 | };
198 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "FootlessParser" */;
199 | compatibilityVersion = "Xcode 3.2";
200 | developmentRegion = English;
201 | hasScannedForEncodings = 0;
202 | knownRegions = (
203 | English,
204 | en,
205 | );
206 | mainGroup = OBJ_5;
207 | productRefGroup = OBJ_27 /* Products */;
208 | projectDirPath = "";
209 | projectRoot = "";
210 | targets = (
211 | OBJ_30 /* FootlessParser */,
212 | OBJ_43 /* FootlessParserTests */,
213 | );
214 | };
215 | /* End PBXProject section */
216 |
217 | /* Begin PBXSourcesBuildPhase section */
218 | OBJ_34 /* Sources */ = {
219 | isa = PBXSourcesBuildPhase;
220 | buildActionMask = 0;
221 | files = (
222 | OBJ_35 /* Converters.swift in Sources */,
223 | OBJ_36 /* Error.swift in Sources */,
224 | OBJ_38 /* Parser+Operators.swift in Sources */,
225 | OBJ_39 /* Parser.swift in Sources */,
226 | OBJ_40 /* Parsers.swift in Sources */,
227 | OBJ_41 /* StringParser.swift in Sources */,
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | OBJ_47 /* Sources */ = {
232 | isa = PBXSourcesBuildPhase;
233 | buildActionMask = 0;
234 | files = (
235 | OBJ_49 /* Parser+Operators_Tests.swift in Sources */,
236 | OBJ_50 /* Parser_Tests.swift in Sources */,
237 | OBJ_51 /* PerformanceTests.swift in Sources */,
238 | OBJ_52 /* StringParser_Tests.swift in Sources */,
239 | OBJ_53 /* TestHelpers.swift in Sources */,
240 | OBJ_54 /* CSV.swift in Sources */,
241 | OBJ_55 /* Examples.swift in Sources */,
242 | );
243 | runOnlyForDeploymentPostprocessing = 0;
244 | };
245 | /* End PBXSourcesBuildPhase section */
246 |
247 | /* Begin PBXTargetDependency section */
248 | OBJ_58 /* PBXTargetDependency */ = {
249 | isa = PBXTargetDependency;
250 | target = OBJ_30 /* FootlessParser */;
251 | targetProxy = BA746BB51E41367500B9A0F4 /* PBXContainerItemProxy */;
252 | };
253 | /* End PBXTargetDependency section */
254 |
255 | /* Begin XCBuildConfiguration section */
256 | OBJ_3 /* Debug */ = {
257 | isa = XCBuildConfiguration;
258 | buildSettings = {
259 | COMBINE_HIDPI_IMAGES = YES;
260 | COPY_PHASE_STRIP = NO;
261 | DEBUG_INFORMATION_FORMAT = dwarf;
262 | DYLIB_INSTALL_NAME_BASE = "@rpath";
263 | ENABLE_NS_ASSERTIONS = YES;
264 | GCC_OPTIMIZATION_LEVEL = 0;
265 | IPHONEOS_DEPLOYMENT_TARGET = 10.3;
266 | MACOSX_DEPLOYMENT_TARGET = 10.10;
267 | ONLY_ACTIVE_ARCH = YES;
268 | OTHER_SWIFT_FLAGS = "-DXcode";
269 | PRODUCT_NAME = "$(TARGET_NAME)";
270 | SDKROOT = macosx;
271 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
272 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
273 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
274 | SWIFT_VERSION = 4.2;
275 | USE_HEADERMAP = NO;
276 | VALID_ARCHS = "i386 x86_64 armv7 armv7s arm64";
277 | };
278 | name = Debug;
279 | };
280 | OBJ_32 /* Debug */ = {
281 | isa = XCBuildConfiguration;
282 | buildSettings = {
283 | ENABLE_TESTABILITY = YES;
284 | FRAMEWORK_SEARCH_PATHS = (
285 | "$(inherited)",
286 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
287 | );
288 | HEADER_SEARCH_PATHS = "$(inherited)";
289 | INFOPLIST_FILE = FootlessParser.xcodeproj/FootlessParser_Info.plist;
290 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
291 | OTHER_LDFLAGS = "$(inherited)";
292 | OTHER_SWIFT_FLAGS = "$(inherited)";
293 | PRODUCT_BUNDLE_IDENTIFIER = FootlessParser;
294 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
295 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
296 | SWIFT_VERSION = 5.0;
297 | TARGET_NAME = FootlessParser;
298 | };
299 | name = Debug;
300 | };
301 | OBJ_33 /* Release */ = {
302 | isa = XCBuildConfiguration;
303 | buildSettings = {
304 | ENABLE_TESTABILITY = YES;
305 | FRAMEWORK_SEARCH_PATHS = (
306 | "$(inherited)",
307 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
308 | );
309 | HEADER_SEARCH_PATHS = "$(inherited)";
310 | INFOPLIST_FILE = FootlessParser.xcodeproj/FootlessParser_Info.plist;
311 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
312 | OTHER_LDFLAGS = "$(inherited)";
313 | OTHER_SWIFT_FLAGS = "$(inherited)";
314 | PRODUCT_BUNDLE_IDENTIFIER = FootlessParser;
315 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
316 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
317 | SWIFT_VERSION = 5.0;
318 | TARGET_NAME = FootlessParser;
319 | };
320 | name = Release;
321 | };
322 | OBJ_4 /* Release */ = {
323 | isa = XCBuildConfiguration;
324 | buildSettings = {
325 | COMBINE_HIDPI_IMAGES = YES;
326 | COPY_PHASE_STRIP = YES;
327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
328 | DYLIB_INSTALL_NAME_BASE = "@rpath";
329 | GCC_OPTIMIZATION_LEVEL = s;
330 | IPHONEOS_DEPLOYMENT_TARGET = 10.3;
331 | MACOSX_DEPLOYMENT_TARGET = 10.10;
332 | OTHER_SWIFT_FLAGS = "-DXcode";
333 | PRODUCT_NAME = "$(TARGET_NAME)";
334 | SDKROOT = macosx;
335 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
336 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
337 | SWIFT_OPTIMIZATION_LEVEL = "-O";
338 | SWIFT_VERSION = 4.2;
339 | USE_HEADERMAP = NO;
340 | VALID_ARCHS = "i386 x86_64 armv7 armv7s arm64";
341 | };
342 | name = Release;
343 | };
344 | OBJ_45 /* Debug */ = {
345 | isa = XCBuildConfiguration;
346 | buildSettings = {
347 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
348 | FRAMEWORK_SEARCH_PATHS = (
349 | "$(inherited)",
350 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
351 | );
352 | HEADER_SEARCH_PATHS = "$(inherited)";
353 | INFOPLIST_FILE = FootlessParser.xcodeproj/FootlessParserTests_Info.plist;
354 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks @loader_path/../Frameworks";
355 | OTHER_LDFLAGS = "$(inherited)";
356 | OTHER_SWIFT_FLAGS = "$(inherited)";
357 | SWIFT_VERSION = 5.0;
358 | TARGET_NAME = FootlessParserTests;
359 | };
360 | name = Debug;
361 | };
362 | OBJ_46 /* Release */ = {
363 | isa = XCBuildConfiguration;
364 | buildSettings = {
365 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
366 | FRAMEWORK_SEARCH_PATHS = (
367 | "$(inherited)",
368 | "$(PLATFORM_DIR)/Developer/Library/Frameworks",
369 | );
370 | HEADER_SEARCH_PATHS = "$(inherited)";
371 | INFOPLIST_FILE = FootlessParser.xcodeproj/FootlessParserTests_Info.plist;
372 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks @loader_path/../Frameworks";
373 | OTHER_LDFLAGS = "$(inherited)";
374 | OTHER_SWIFT_FLAGS = "$(inherited)";
375 | SWIFT_VERSION = 5.0;
376 | TARGET_NAME = FootlessParserTests;
377 | };
378 | name = Release;
379 | };
380 | /* End XCBuildConfiguration section */
381 |
382 | /* Begin XCConfigurationList section */
383 | OBJ_2 /* Build configuration list for PBXProject "FootlessParser" */ = {
384 | isa = XCConfigurationList;
385 | buildConfigurations = (
386 | OBJ_3 /* Debug */,
387 | OBJ_4 /* Release */,
388 | );
389 | defaultConfigurationIsVisible = 0;
390 | defaultConfigurationName = Debug;
391 | };
392 | OBJ_31 /* Build configuration list for PBXNativeTarget "FootlessParser" */ = {
393 | isa = XCConfigurationList;
394 | buildConfigurations = (
395 | OBJ_32 /* Debug */,
396 | OBJ_33 /* Release */,
397 | );
398 | defaultConfigurationIsVisible = 0;
399 | defaultConfigurationName = Debug;
400 | };
401 | OBJ_44 /* Build configuration list for PBXNativeTarget "FootlessParserTests" */ = {
402 | isa = XCConfigurationList;
403 | buildConfigurations = (
404 | OBJ_45 /* Debug */,
405 | OBJ_46 /* Release */,
406 | );
407 | defaultConfigurationIsVisible = 0;
408 | defaultConfigurationName = Debug;
409 | };
410 | /* End XCConfigurationList section */
411 | };
412 | rootObject = OBJ_1 /* Project object */;
413 | }
414 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/xcshareddata/xcschemes/FootlessParser.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
74 |
75 |
77 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SchemeUserState
5 |
6 | FootlessParser.xcscheme
7 |
8 |
9 | SuppressBuildableAutocreation
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Kåre Morstøl, NotTooBad Software (nottoobadsoftware.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.1
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "FootlessParser",
7 | products: [
8 | .library(name: "FootlessParser", targets: ["FootlessParser"]),
9 | ],
10 | targets: [
11 | .target(name: "FootlessParser"),
12 | .testTarget(name: "FootlessParserTests", dependencies: ["FootlessParser"]),
13 | ]
14 | )
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/Carthage/Carthage)
2 |
3 | Swift 5, 4.2 and 4.1 | [Swift 2.2+](https://github.com/kareman/FootlessParser/tree/swift2.2%2B)
4 |
5 | # FootlessParser
6 |
7 | FootlessParser is a simple and pretty naive implementation of a parser combinator in Swift. It enables infinite lookahead, non-ambiguous parsing with error reporting.
8 |
9 | There is [a series of blog posts about the development](https://nottoobadsoftware.com/blog/footlessparser/) and [documentation from the source code](https://kareman.github.io/FootlessParser/).
10 |
11 | ## Introduction
12 |
13 | In short, FootlessParser lets you define parsers like this:
14 |
15 | ```swift
16 | let parser = function1 <^> parser1 <*> parser2 <|> parser3
17 | ```
18 |
19 | `function1` and `parser3` return the same type.
20 |
21 | `parser` will pass the input to `parser1` followed by `parser2`, pass their results to `function1` and return its result. If that fails it will pass the original input to `parser3` and return its result.
22 |
23 | ## Definitions
24 |
25 | ### Parser
26 |
27 | A function which takes some input (a sequence of tokens) and returns either the output and the remaining unparsed part of the input, or an error description if it fails.
28 |
29 | ### Token
30 |
31 | A single item from the input. Like a character from a string, an element from an array or a string from a list of command line arguments.
32 |
33 | ### Parser Input
34 |
35 | Most often text, but can also be an array or really any collection of anything, provided it conforms to CollectionType.
36 |
37 | ## Parsers
38 |
39 | The general idea is to combine very simple parsers into more complex ones. So `char("a")` creates a parser which checks if the next token from the input is an "a". If it is it returns that "a", otherwise it returns an error. You can then use operators and functions like `zeroOrMore` and `optional` to create ever more complex parsers. For more check out [the full list of functions](https://kareman.github.io/FootlessParser/Functions.html).
40 |
41 | ## Operators
42 |
43 | #### <^> (map)
44 |
45 | `function <^> parser1` creates a new parser which runs parser1\. If it succeeds it passes the output to `function` and returns the result.
46 |
47 | #### <*> (apply)
48 |
49 | `function <^> parser1 <*> parser2` creates a new parser which first runs parser1\. If it succeeds it runs parser2\. If that also succeeds it passes both outputs to `function` and returns the result.
50 |
51 | The <*> operator requires its left parser to return a function and is normally used together with <^>. `function` must take 2 parameters of the correct types, and it must be curried, like this:
52 |
53 | ```swift
54 | func function (a: A) -> (B) -> C
55 | ```
56 |
57 | This is because <*> returns the output of 2 parsers and it doesn't know what to do with them. If you want them returned in a tuple, an array or e.g. added together you can do so in the function before <^> .
58 |
59 | If there are 3 parsers and 2 <*> the function must take 3 parameters, and so on.
60 |
61 | #### <*
62 |
63 | The same as the <*> above, except it discards the result of the parser to its right. Since it only returns one output it doesn't need to be used together with <^> . But you can of course if you want the output converted to something else.
64 |
65 | #### *>
66 |
67 | The same as <* , but discards the result of the parser to its left.
68 |
69 | #### <|> (choice)
70 |
71 | ```
72 | parser1 <|> parser2 <|> parser3
73 | ```
74 |
75 | This operator tries all the parsers in order and returns the result of the first one that succeeds.
76 |
77 | #### >>- (flatmap)
78 |
79 | ```
80 | parser1 >>- ( o -> parser2 )
81 | ```
82 |
83 | This does the same as the flatmap functions in the Swift Standard Library. It creates a new parser which first tries parser1\. If it fails it returns the error, if it succeeds it passes the output to the function which uses it to create parser2\. It then runs parser2 and returns its output or error.
84 |
85 | ## Example
86 |
87 | ### Real life usage
88 |
89 | - [oleander/BitBarParser](https://github.com/oleander/BitBarParser/blob/master/Parser/Parser/Parser.swift) - lets you put the output from any script/program in your Mac OS X Menu Bar.
90 | - [banjun/NorthLayout](https://github.com/banjun/NorthLayout/blob/master/Classes/VFLSyntax.swift) - autolayout views in code.
91 |
92 | ### [CSV](https://www.computerhope.com/jargon/c/csv.htm) parser
93 |
94 | ```swift
95 | let delimiter = "," as Character
96 | let quote = "\"" as Character
97 | let newline = "\n" as Character
98 |
99 | let cell = char(quote) *> zeroOrMore(not(quote)) <* char(quote)
100 | <|> zeroOrMore(noneOf([delimiter, newline]))
101 |
102 | let row = extend <^> cell <*> zeroOrMore(char(delimiter) *> cell) <* char(newline)
103 | let csvparser = zeroOrMore(row)
104 | ```
105 |
106 | Here a cell (or field) either:
107 |
108 | - begins with a ", then contains anything, including commas, tabs and newlines, and ends with a " (both quotes are discarded)
109 | - or is unquoted and contains anything but a comma or a newline.
110 |
111 | Each row then consists of one or more cells, separated by commas and ended by a newline. The `extend` function joins the cells together into an array. Finally the `csvparser` collects zero or more rows into an array.
112 |
113 | To perform the actual parsing:
114 |
115 | ```swift
116 | do {
117 | let output = try parse(csvparser, csvtext)
118 | // output is an array of all the rows,
119 | // where each row is an array of all its cells.
120 | } catch {
121 |
122 | }
123 | ```
124 |
125 | ### Recursive expression
126 |
127 | ```swift
128 | func add(a: Int) -> (Int) -> Int { return { b in a + b } }
129 | func multiply(a: Int) -> (Int) -> Int { return { b in a + b } }
130 |
131 | let nr = { Int($0)! } <^> oneOrMore(oneOf("0123456789"))
132 |
133 | var expression: Parser!
134 |
135 | let factor = nr <|> lazy (char("(") *> expression <* char(")"))
136 |
137 | var term: Parser!
138 | term = lazy (multiply <^> factor <* char("*") <*> term <|> factor)
139 |
140 | expression = lazy (add <^> term <* char("+") <*> expression <|> term)
141 |
142 | do {
143 | let result = try parse(expression, "(1+(2+4))+3")
144 | } catch {
145 |
146 | }
147 | ```
148 |
149 | `expression` parses input like `"12"`, `"1+2+3"`, `"(1+2)"`, `"12*3+1"` and `"12*(3+1)"` and returns the result as an Int.
150 |
151 | All parsers which refer to themselves directly or indirectly must be pre-declared as variable implicitly unwrapped optionals (`var expression: Parser!`). And to avoid infinte recursion the definitions must use the `lazy` function.
152 |
153 | ## Installation
154 |
155 | ### Using [Carthage](https://github.com/Carthage/Carthage)
156 |
157 | ```
158 | github "kareman/FootlessParser"
159 | ```
160 |
161 | Then run `carthage update`.
162 |
163 | Then follow the installation instructions in [Carthage's README][carthage-installation].
164 |
165 | ### Using [CocoaPods](https://cocoapods.org/)
166 |
167 | Add `FootlessParser` to your `Podfile` file.
168 |
169 | ```
170 | pod 'FootlessParser', '~> 0.5.2'
171 | ```
172 |
173 | Then run `pod install` to install it.
174 |
175 | [carthage-installation]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application
176 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/Converters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Converters.swift
3 | // FootlessParser
4 | //
5 | // Released under the MIT License (MIT), http://opensource.org/licenses/MIT
6 | //
7 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
8 | //
9 |
10 | /** Return a collection containing x and all elements of xs. Works with strings and arrays. */
11 | public func extend
12 |
13 | (_ xs: C) -> (A) -> C where C.Iterator.Element == A {
14 | var result = xs
15 | return { x in
16 | // not satisfied with this way of doing it, but RangeReplaceableCollectionType has only mutable methods.
17 | result.append(x)
18 | return result
19 | }
20 | }
21 |
22 | /** Return a collection containing x and all elements of xs. Works with strings and arrays. */
23 | public func extend
24 |
25 | (_ x: A) -> (C) -> C where C.Iterator.Element == A {
26 | var result = C()
27 | result.append(x)
28 | return { xs in
29 | // not satisfied with this way of doing it, but RangeReplaceableCollectionType has only mutable methods.
30 | result.append(contentsOf: xs)
31 | return result
32 | }
33 | }
34 |
35 | /** Join 2 collections together. */
36 | public func extend
37 |
38 | (_ xs1: C1) -> (C2) -> C1 where C1.Iterator.Element == C2.Iterator.Element {
39 | var xs1 = xs1
40 | return { xs2 in
41 | xs1.append(contentsOf: xs2)
42 | return xs1
43 | }
44 | }
45 |
46 | /** Create a tuple of the arguments. */
47 | public func tuple (_ a: A) -> (B) -> (A, B) {
48 | return { b in
49 | return (a, b)
50 | }
51 | }
52 |
53 | public func tuple (_ a: A) -> (B) -> (C) -> (A, B, C) {
54 | return { b in { c in (a, b, c) }
55 | }
56 | }
57 |
58 | public func tuple (_ a: A) -> (B) -> (C) -> (D) -> (A, B, C, D) {
59 | return { b in { c in { d in (a, b, c, d) } } }
60 | }
61 |
62 | public func tuple (_ a: A) -> (B) -> (C) -> (D) -> (E) -> (A, B, C, D, E) {
63 | return { b in { c in { d in { e in (a, b, c, d, e) } } } }
64 | }
65 |
66 | /**
67 | Create a curried function of a normal function.
68 |
69 | Usage:
70 | ```
71 | func myFunc(a: A, b: B) -> C { ... }
72 |
73 | let parser = curry(myFunc) <^> string("hello") <*> string("world")
74 | ```
75 | */
76 | public func curry(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
77 | return { a in { b in f(a, b) } }
78 | }
79 |
80 | /**
81 | Create a curried function of a normal function.
82 |
83 | Usage:
84 | ```
85 | func myFunc(a: A, b: B, c: C) -> D { ... }
86 |
87 | let parser = curry(myFunc) <^> string("hello,") <*> string("dear") <*> string("world")
88 | ```
89 | */
90 | public func curry(_ f: @escaping (A, B, C) -> D) -> (A) -> (B) -> (C) -> D {
91 | return { a in { b in { c in f(a, b, c) } } }
92 | }
93 |
94 | /**
95 | Create a curried function of a normal function.
96 |
97 | Usage:
98 | ```
99 | func myFunc(a: A, b: B, c: C, d: D) -> E { ... }
100 |
101 | let parser = curry(myFunc) <^> string("a") <*> string("b") <*> string("c") <*> string("d")
102 | ```
103 | */
104 | public func curry(_ f: @escaping (A, B, C, D) -> E) -> (A) -> (B) -> (C) -> (D) -> E {
105 | return { a in { b in { c in { d in f(a, b, c, d) } } } }
106 | }
107 |
108 |
109 | /**
110 | Create a curried function of a normal function.
111 |
112 | Usage:
113 | ```
114 | func myFunc(a: A, b: B, c: C, d: D) -> E { ... }
115 |
116 | let parser = curry(myFunc) <^> string("a") <*> string("b") <*> string("c") <*> string("d")
117 | ```
118 | */
119 | public func curry(_ f: @escaping (A, B, C, D, E) -> F) -> (A) -> (B) -> (C) -> (D) -> (E) -> F {
120 | return { a in { b in { c in { d in { e in f(a, b, c, d, e) } } } } }
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/Error.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum CountError: Error {
4 | case NegativeCount
5 | }
6 |
7 |
8 | public enum ParseError: Error {
9 | case Mismatch(AnyCollection, String, String)
10 | }
11 |
12 | extension ParseError: CustomStringConvertible {
13 | public var description: String {
14 | switch self {
15 | case let .Mismatch(remainder, expected, actual): return "Expected \(expected), actual \(actual) at position -\(remainder.count)"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/Parser+Operators.swift:
--------------------------------------------------------------------------------
1 | precedencegroup ApplyGroup {
2 | associativity: right
3 | higherThan: ComparisonPrecedence
4 | }
5 |
6 | precedencegroup FlatMapGroup {
7 | higherThan: ApplyGroup
8 | associativity: left
9 | }
10 |
11 | precedencegroup MapApplyGroup {
12 | higherThan: FlatMapGroup
13 | associativity: left
14 | }
15 |
16 | infix operator >>-: FlatMapGroup
17 |
18 | /**
19 | FlatMap a function over a parser.
20 | - If the parser fails, the function will not be evaluated and the error is returned.
21 | - If the parser succeeds, the function will be applied to the output, and the resulting parser is run with the remaining input.
22 | - parameter p: A parser of type Parser
23 | - parameter f: A transformation function from type A to Parser
24 | - returns: A parser of type Parser
25 | */
26 | public func >>- (p: Parser, f: @escaping (A) throws -> Parser) -> Parser {
27 | return Parser { input in
28 | let result = try p.parse(input)
29 | return try f(result.output).parse(result.remainder)
30 | }
31 | }
32 |
33 |
34 | infix operator <^>: MapApplyGroup
35 |
36 | /**
37 | Map a function over a parser
38 | - If the parser fails, the function will not be evaluated and the parser error is returned.
39 | - If the parser succeeds, the function will be applied to the output.
40 | - parameter f: A function from type A to type B
41 | - parameter p: A parser of type Parser
42 | - returns: A parser of type Parser
43 | */
44 | public func <^> (f: @escaping (A) throws -> B, p: Parser) -> Parser {
45 | return Parser { input in
46 | let result = try p.parse(input)
47 | return (try f(result.output), result.remainder)
48 | }
49 | }
50 |
51 |
52 | infix operator <*>: MapApplyGroup
53 |
54 | /**
55 | Apply a parser returning a function to another parser.
56 |
57 | - If the first parser fails, its error will be returned.
58 | - If it succeeds, the resulting function will be applied to the 2nd parser.
59 |
60 | - parameter fp: A parser with a function A->B as output
61 | - parameter p: A parser of type Parser
62 |
63 | - returns: A parser of type Parser
64 | */
65 | public func <*> (fp: ParserB>, p: Parser) -> Parser {
66 | return fp >>- { $0 <^> p }
67 | }
68 |
69 | infix operator <*: MapApplyGroup
70 |
71 | /**
72 | Apply both parsers, but only return the output from the first one.
73 |
74 | - If the first parser fails, its error will be returned.
75 | - If the 2nd parser fails, its error will be returned.
76 |
77 | - returns: A parser of the same type as the first parser.
78 | */
79 | public func <* (p1: Parser, p2: Parser) -> Parser {
80 | return { x in { _ in x } } <^> p1 <*> p2
81 | }
82 |
83 | infix operator *>: MapApplyGroup
84 |
85 | /**
86 | Apply both parsers, but only return the output from the 2nd one.
87 |
88 | - If the first parser fails, its error will be returned.
89 | - If the 2nd parser fails, its error will be returned.
90 |
91 | - returns: A parser of the same type as the 2nd parser.
92 | */
93 | public func *> (p1: Parser, p2: Parser) -> Parser {
94 | return { _ in { x in x } } <^> p1 <*> p2
95 | }
96 |
97 |
98 | /**
99 | Create a parser which doesn't consume input and returns this parameter.
100 |
101 | - parameter a: A value of type A
102 | */
103 | public func pure (_ a: A) -> Parser {
104 | return Parser { input in (a, input) }
105 | }
106 |
107 |
108 | infix operator <|>: ApplyGroup
109 |
110 | /**
111 | Apply one of 2 parsers.
112 |
113 | - If the first parser succeeds, return its results.
114 | - Else if the 2nd parser succeeds, return its results.
115 | - If they both fail, return the failure from the parser that got the furthest.
116 |
117 | Has infinite lookahead. The 2nd parser starts from the same position in the input as the first one.
118 | */
119 | public func <|> (l: Parser, r: Parser) -> Parser {
120 | return Parser { input in
121 | do {
122 | return try l.parse(input)
123 | } catch ParseError.Mismatch(let lr, let le, let la) {
124 | do {
125 | return try r.parse(input)
126 | } catch ParseError.Mismatch(let rr, let re, let ra) {
127 | if lr.count <= rr.count {
128 | throw ParseError.Mismatch(lr, le, la)
129 | } else {
130 | throw ParseError.Mismatch(rr, re, ra)
131 | }
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/Parser.swift:
--------------------------------------------------------------------------------
1 | // would've liked a generic typealias here.
2 | public struct Parser {
3 | public typealias ParseFunction = (AnyCollection) throws -> (output: Output, remainder: AnyCollection)
4 | public let parse: ParseFunction
5 |
6 | public init( parse: @escaping ParseFunction ) {
7 | self.parse = parse
8 | }
9 | }
10 |
11 | public func satisfy
12 | (expect: @autoclosure @escaping () -> String, condition: @escaping (T) -> Bool) -> Parser {
13 | return Parser { input in
14 | if let next = input.first {
15 | if condition(next) {
16 | return (next, input.dropFirst())
17 | } else {
18 | throw ParseError.Mismatch(input, expect(), String(describing:next))
19 | }
20 | } else {
21 | throw ParseError.Mismatch(input, expect(), "EOF")
22 | }
23 | }
24 | }
25 |
26 | public func token(_ token: T) -> Parser {
27 | return satisfy(expect: String(describing:token)) { $0 == token }
28 | }
29 |
30 | /** Match several tokens in a row. */
31 | public func tokens (_ xs: C) -> Parser where C.Iterator.Element == T {
32 | let length = xs.count
33 | return count(length, any()) >>- { parsedtokens in
34 | return parsedtokens.elementsEqual(xs) ? pure(xs) : fail(.Mismatch(AnyCollection(parsedtokens), String(describing:xs), String(describing:parsedtokens)))
35 | }
36 | }
37 |
38 | /** Return whatever the next token is. */
39 | public func any () -> Parser {
40 | return satisfy(expect: "anything") { _ in true }
41 | }
42 |
43 | /** Try parser, if it fails return 'otherwise' without consuming input. */
44 | public func optional (_ p: Parser, otherwise: A) -> Parser {
45 | return p <|> pure(otherwise)
46 | }
47 |
48 | /** Try parser, if it fails return nil without consuming input. */
49 | public func optional (_ p: Parser) -> Parser {
50 | return Parser { input in
51 | do {
52 | let (result, remainder) = try p.parse(input)
53 | return (result, remainder)
54 | } catch is ParseError {
55 | return (nil, input)
56 | }
57 | }
58 | }
59 |
60 | /** Delay creation of parser until it is needed. */
61 | public func lazy (_ f: @autoclosure @escaping () -> Parser) -> Parser {
62 | return Parser { input in try f().parse(input) }
63 | }
64 |
65 | /** Apply parser once, then repeat until it fails. Returns an array of the results. */
66 | public func oneOrMore (_ p: Parser) -> Parser {
67 | return Parser { input in
68 | var (first, remainder) = try p.parse(input)
69 | var result = [first]
70 | while true {
71 | do {
72 | let next = try p.parse(remainder)
73 | result.append(next.output)
74 | remainder = next.remainder
75 | } catch {
76 | return (result, remainder)
77 | }
78 | }
79 | }
80 | }
81 |
82 | /** Repeat parser until it fails. Returns an array of the results. */
83 | public func zeroOrMore (_ p: Parser) -> Parser {
84 | return optional( oneOrMore(p), otherwise: [] )
85 | }
86 |
87 | /** Repeat parser 'n' times. If 'n' == 0 it always succeeds and returns []. */
88 | public func count (_ n: Int, _ p: Parser) -> Parser {
89 | return Parser { input in
90 | var input = input
91 | var results = [A]()
92 | for _ in 0.. (_ r: ClosedRange, _ p: Parser) -> Parser {
107 | precondition(r.lowerBound >= 0, "Count must be >= 0")
108 | return extend <^> count(r.lowerBound, p) <*> ( count(r.count-1, p) <|> zeroOrMore(p) )
109 | }
110 |
111 | /**
112 | Repeat parser as many times as possible within the given range.
113 | count(2..<3, p) is identical to count(2, p)
114 | - parameter r: A positive half open integer range.
115 | */
116 | public func count (_ r: Range, _ p: Parser) -> Parser {
117 | precondition(r.lowerBound >= 0, "Count must be >= 0")
118 | return extend <^> count(r.lowerBound, p) <*> ( count(r.count-1, p) <|> zeroOrMore(p) )
119 | }
120 |
121 | /** Succeed if the next token is in the provided collection. */
122 | public func oneOf (_ collection: C) -> Parser where C.Iterator.Element == T {
123 | return satisfy(expect: "one of '\(String(describing:collection))'") { collection.contains($0) }
124 | }
125 |
126 | /** Succeed if the next token is _not_ in the provided collection. */
127 | public func noneOf (_ collection: C) -> Parser where C.Iterator.Element == T {
128 | return satisfy(expect: "something not in '\(collection)'") { !collection.contains($0) }
129 | }
130 |
131 | /** Match anything but this. */
132 | public func not (_ token: T) -> Parser {
133 | return satisfy(expect: "anything but '\(token)'") { $0 != token }
134 | }
135 |
136 | /** Verify that input is empty. */
137 | public func eof () -> Parser {
138 | return Parser { input in
139 | if let next = input.first {
140 | throw ParseError.Mismatch(input, "EOF", String(describing:next))
141 | }
142 | return ((), input)
143 | }
144 | }
145 |
146 | /** Fail with the given error message. Ignores input. */
147 | public func fail (_ error: ParseError) -> Parser {
148 | return Parser { _ in throw error }
149 | }
150 |
151 | /**
152 | Parse all of input with parser.
153 | Failure to consume all of input will result in a ParserError.
154 | - parameter p: A parser.
155 | - parameter input: A collection, like a string or an array.
156 | - returns: Output from the parser.
157 | - throws: ParserError.
158 | */
159 | public func parse(_ p: Parser, _ c: [T]) throws -> A {
160 | return try ( p <* eof() ).parse(AnyCollection(c)).output
161 | }
162 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/Parsers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Parsers.swift
3 | // FootlessParser
4 | //
5 | // Released under the MIT License (MIT), http://opensource.org/licenses/MIT
6 | //
7 | // Copyright (c) 2016 Bouke Haarsma. All rights reserved.
8 |
9 | import Foundation
10 |
11 | /**
12 | Parser that matches all the whitespace characters.
13 |
14 | Matches the character set containing the characters in Unicode General
15 | Category Zs and CHARACTER TABULATION (U+0009).
16 | */
17 | public let whitespace = char(CharacterSet.whitespaces, name: "whitespace")
18 |
19 | /**
20 | Parser that matches all the newline characters.
21 |
22 | Matches the character set containing the newline characters (U+000A ~ U+000D,
23 | U+0085, U+2028, and U+2029).
24 | */
25 | public let newline = char(CharacterSet.newlines, name: "newline")
26 |
27 | /**
28 | Parser that matches all the whitespace and newline characters.
29 |
30 | Matches the character set containing characters in Unicode General Category
31 | Z*, U+000A ~ U+000D, and U+0085.
32 | */
33 | public let whitespacesOrNewline = char(CharacterSet.whitespacesAndNewlines, name: "whitespacesOrNewline")
34 |
35 | /**
36 | Parser that matches all the decimal digit characters.
37 |
38 | Matches the character set containing the characters in the category of Decimal
39 | Numbers.
40 | */
41 | public let digit = char(CharacterSet.decimalDigits, name: "digit")
42 |
43 | /**
44 | Parser that matches all the alphanumeric characters.
45 |
46 | Matches the character set containing the characters in Unicode General
47 | Categories L*, M*, and N*.
48 | */
49 | public let alphanumeric = char(CharacterSet.alphanumerics, name: "alphanumeric")
50 |
--------------------------------------------------------------------------------
/Sources/FootlessParser/StringParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringParser.swift
3 | // FootlessParser
4 | //
5 | // Released under the MIT License (MIT), http://opensource.org/licenses/MIT
6 | //
7 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
8 | //
9 |
10 | import Foundation
11 |
12 | /** Match a single character. */
13 | public func char (_ c: Character) -> Parser {
14 | return satisfy(expect: String(c)) { $0 == c }
15 | }
16 |
17 | /** Join two strings */
18 | public func extend (_ a: String) -> (String) -> String {
19 | return { b in a + b }
20 | }
21 |
22 | /** Join a character with a string. */
23 | public func extend (_ a: Character) -> (String) -> String {
24 | return { b in String(a) + b }
25 | }
26 |
27 | /** Apply character parser once, then repeat until it fails. Returns a string. */
28 | public func oneOrMore (_ p: Parser) -> Parser {
29 | return { (cs: [Character]) in String(cs) } <^> oneOrMore(p)
30 | }
31 |
32 | /** Repeat character parser until it fails. Returns a string. */
33 | public func zeroOrMore (_ p: Parser) -> Parser {
34 | return optional( oneOrMore(p), otherwise: "" )
35 | }
36 |
37 | /** Repeat character parser 'n' times and return as string. If 'n' == 0 it always succeeds and returns "". */
38 | public func count (_ n: Int, _ p: Parser) -> Parser {
39 | return n == 0 ? pure("") : extend <^> p <*> count(n-1, p)
40 | }
41 |
42 | /**
43 | Repeat parser as many times as possible within the given range.
44 | count(2...2, p) is identical to count(2, p)
45 | - parameter r: A positive closed integer range.
46 | */
47 | public func count (_ r: ClosedRange, _ p: Parser) -> Parser {
48 | precondition(r.lowerBound >= 0, "Count must be >= 0")
49 | return extend <^> count(r.lowerBound, p) <*> ( count(r.count-1, p) <|> zeroOrMore(p) )
50 | }
51 |
52 | /**
53 | Repeat parser as many times as possible within the given range.
54 | count(2..<3, p) is identical to count(2, p)
55 | - parameter r: A positive half open integer range.
56 | */
57 | public func count (_ r: Range, _ p: Parser) -> Parser {
58 | precondition(r.lowerBound >= 0, "Count must be >= 0")
59 | return extend <^> count(r.lowerBound, p) <*> ( count(r.count-1, p) <|> zeroOrMore(p) )
60 | }
61 |
62 | /**
63 | Match a string
64 | - parameter: string to match
65 | - note: consumes either the full string or nothing, even on a partial match.
66 | */
67 | public func string (_ s: String) -> Parser {
68 | let count = s.count
69 | return Parser { input in
70 | guard input.startIndex < input.endIndex else {
71 | throw ParseError.Mismatch(input, s, "EOF")
72 | }
73 | guard let endIndex = input.index(input.startIndex, offsetBy: count, limitedBy: input.endIndex) else {
74 | throw ParseError.Mismatch(input, s, String(input))
75 | }
76 | let next = input[input.startIndex.. Parser {
88 | return oneOf(Set(s))
89 | }
90 |
91 | /** Succeed if the next character is _not_ in the provided string. */
92 | public func noneOf (_ s: String) -> Parser {
93 | return noneOf(Set(s))
94 | }
95 |
96 | /**
97 | Produces a character if the character and succeeding do not match any of the strings.
98 | - parameter: strings to _not_ match
99 | - note: consumes only produced characters
100 | */
101 | public func noneOf(_ strings: [String]) -> Parser {
102 | return Parser { input in
103 | guard let next = input.first else {
104 | throw ParseError.Mismatch(input, "anything but \(strings)", "EOF")
105 | }
106 | for string in strings {
107 | guard string.first == next else { continue }
108 | let offset = string.count
109 | guard input.count >= offset else { continue }
110 | let endIndex = input.index(input.startIndex, offsetBy: offset)
111 | guard endIndex <= input.endIndex else { continue }
112 | let peek = input[input.startIndex.. Parser {
120 | return satisfy(expect: name) {
121 | return String($0).rangeOfCharacter(from: set) != nil
122 | }
123 | }
124 |
125 | /**
126 | Parse all of the string with parser.
127 | Failure to consume all of input will result in a ParserError.
128 | - parameter p: A parser of characters.
129 | - parameter input: A string.
130 | - returns: Output from the parser.
131 | - throws: A ParserError.
132 | */
133 | public func parse (_ p: Parser, _ s: String) throws -> A {
134 | return try (p <* eof()).parse(AnyCollection(s)).output
135 | }
136 |
137 | public func print(error: ParseError, in s: String) {
138 | if case ParseError.Mismatch(let remainder, let expected, let actual) = error {
139 | let index = s.index(s.endIndex, offsetBy: -remainder.count)
140 | let (lineRange, row, pos) = position(of: index, in: s)
141 | let line = s[lineRange.lowerBound.. (line: Range, row: Int, pos: Int) {
151 | var head = string.startIndex.. zeroOrMore(not(quote)) <* char(quote)
18 | <|> zeroOrMore(noneOf([delimiter, newline]))
19 |
20 | let row = extend <^> cell <*> zeroOrMore( char(delimiter) *> cell ) <* char(newline)
21 |
22 |
23 | class CSV: XCTestCase {
24 |
25 | func testCell () {
26 | assertParseSucceeds(cell, "row", result: "row")
27 | assertParseSucceeds(cell, "\"quoted row\"", result: "quoted row")
28 | }
29 |
30 | func testRow () {
31 | assertParseSucceeds(row, "alpha,bravo,\"charlie\",delta\n", result: ["alpha", "bravo", "charlie", "delta"])
32 | }
33 |
34 | func testParseCSVQuotesReturningArray () {
35 | let filepath = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("CSV-quotes.csv").path
36 | let movieratings = try! String(contentsOfFile: filepath, encoding: .utf8)
37 |
38 | measure {
39 | do {
40 | let result = try parse(zeroOrMore(row), movieratings)
41 | XCTAssertEqual(result.count, 101)
42 | } catch {
43 | XCTFail(String(describing: error))
44 | }
45 | }
46 | }
47 |
48 | func testParseLargeCSVQuotesReturningArray () {
49 | let filepath = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("CSV-quotes-large.csv").path
50 | let movieratings = try! String(contentsOfFile: filepath, encoding: .utf8)
51 |
52 | measure {
53 | do {
54 | let result = try parse(zeroOrMore(row), movieratings)
55 | XCTAssertEqual(result.count, 1715)
56 | } catch {
57 | XCTFail(String(describing: error))
58 | }
59 | }
60 | }
61 | }
62 |
63 | extension CSV {
64 | public static var allTests = [
65 | ("testCell", testCell),
66 | ("testRow", testRow),
67 | ("testParseCSVQuotesReturningArray", testParseCSVQuotesReturningArray),
68 | ("testParseLargeCSVQuotesReturningArray", testParseLargeCSVQuotesReturningArray),
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/Grammar/Examples.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Examples.swift
3 | // FootlessParser
4 | //
5 | // Created by Kåre Morstøl on 17.06.15.
6 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
7 | //
8 |
9 | import FootlessParser
10 | import XCTest
11 |
12 | class Examples: XCTestCase {
13 |
14 | func testXMLTagParser () {
15 |
16 | let opentag = char("<") *> oneOrMore(not(">")) <* char(">")
17 | let closetag = { (tagname: String) in char("<") *> string(tagname) <* string("/>") }
18 |
19 | let tag = tuple <^> opentag <*> oneOrMore(not("<")) >>- { (name, content) in
20 | return { _ in (name, content) } <^> closetag(name)
21 | }
22 |
23 | let (name, content) = try! parse(tag, "content")
24 |
25 | XCTAssertEqual(name, "a")
26 | XCTAssertEqual(content, "content")
27 |
28 | assertParseFails(tag, "content")
29 | assertParseFails(tag, "a content")
30 | assertParseFails(tag, "")
31 | }
32 |
33 | func testRecursiveExpressionParser () {
34 | func add (a:Int, b:Int) -> Int { return a + b }
35 | func multiply (a:Int, b:Int) -> Int { return a * b }
36 |
37 | let nr = {Int($0)!} <^> oneOrMore(oneOf("0123456789"))
38 |
39 | var expression: Parser!
40 |
41 | let factor = nr <|> lazy( char("(") *> expression <* char(")") )
42 |
43 | var term: Parser!
44 | term = lazy( curry(multiply) <^> factor <* char("*") <*> term <|> factor )
45 |
46 | expression = lazy( curry(add) <^> term <* char("+") <*> expression <|> term )
47 |
48 | assertParseSucceeds(expression, "12", result: 12)
49 | assertParseSucceeds(expression, "1+2+3", result: 6)
50 | assertParseSucceeds(expression, "(1+2)", result: 3)
51 | assertParseSucceeds(expression, "(1+(2+4))+3", result: 10)
52 | assertParseSucceeds(expression, "12*(3+1)", result: 48)
53 | assertParseSucceeds(expression, "12*3+1", result: 37)
54 | }
55 | }
56 |
57 | extension Examples {
58 | public static var allTests = [
59 | ("testXMLTagParser", testXMLTagParser),
60 | ("testRecursiveExpressionParser", testRecursiveExpressionParser),
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/Parser+Operators_Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Parser+Operators_Tests.swift
3 | // FootlessParser
4 | //
5 | // Created by Kåre Morstøl on 06.04.15.
6 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
7 | //
8 |
9 | import FootlessParser
10 | import XCTest
11 |
12 | class Pure_Tests: XCTestCase {
13 |
14 | func testPureReturnsInput () {
15 | let parser: Parser = pure(1)
16 |
17 | assertParseSucceeds(parser, [2,3,4], result: 1)
18 | }
19 |
20 | func testPureDoesNotConsume () {
21 | let parser: Parser = pure(1)
22 | var input = [2,3,4]
23 |
24 | assertParseSucceeds(parser, &input, result: 1, consumed: 0)
25 | }
26 | }
27 |
28 | class FlatMap_Tests: XCTestCase {
29 |
30 | // return a >>= f = f a
31 | func testLeftIdentityLaw () {
32 | let leftside = pure(1) >>- token
33 | let rightside = token(1)
34 |
35 | assertParsesEqually(leftside, rightside, input: [1], shouldSucceed: true)
36 | assertParsesEqually(leftside, rightside, input: [9], shouldSucceed: false)
37 | }
38 |
39 | // m >>= return = m
40 | func testRightIdentityLaw () {
41 | let leftside = token(1) >>- pure
42 | let rightside = token(1)
43 |
44 | assertParsesEqually(leftside, rightside, input: [1], shouldSucceed: true)
45 | assertParsesEqually(leftside, rightside, input: [9], shouldSucceed: false)
46 | }
47 |
48 | // (m >>= f) >>= g = m >>= (\x -> f x >>= g)
49 | func testAssociativityLaw () {
50 | func timesTwo(x: Int) -> Parser {
51 | let x2 = x * 2
52 | return satisfy(expect: "\(x2)") { $0 == x2 }
53 | }
54 |
55 | let leftside = (any() >>- token) >>- timesTwo
56 | let rightside = any() >>- { token($0) >>- timesTwo }
57 |
58 | assertParsesEqually(leftside, rightside, input: [1,1,2], shouldSucceed: true)
59 | assertParsesEqually(leftside, rightside, input: [9,9,9], shouldSucceed: false)
60 |
61 | let noparens = any() >>- token >>- timesTwo
62 | assertParsesEqually(leftside, noparens, input: [1,1,2], shouldSucceed: true)
63 | assertParsesEqually(leftside, noparens, input: [9,9,9], shouldSucceed: false)
64 | }
65 | }
66 |
67 | /** The identity function; returns its argument. */
68 | public func id (x: A) -> A {
69 | return x
70 | }
71 |
72 | class Map_Tests: XCTestCase {
73 |
74 | // map id = id
75 | func testTheIdentityLaw () {
76 | let leftside = id <^> token(1)
77 | let rightside = token(1)
78 |
79 | assertParsesEqually(leftside, rightside, input: [1], shouldSucceed: true)
80 | assertParsesEqually(leftside, rightside, input: [9], shouldSucceed: false)
81 | }
82 |
83 | func testAppliesFunctionToResultOnSuccess () {
84 | let parser = String.init <^> token(1)
85 |
86 | var input = [1]
87 |
88 | assertParseSucceeds(parser, &input, result: "1")
89 | XCTAssert(input == [], "Input should be empty")
90 | }
91 |
92 | func testReturnsErrorOnFailure () {
93 | let parser = String.init <^> token(1)
94 |
95 | assertParseFails(parser, [9])
96 | }
97 | }
98 |
99 | func sum (a: Int) -> (Int) -> Int { return { b in return a + b } }
100 | func product (a: Int) -> (Int) -> Int { return { b in return a * b } }
101 |
102 | class Apply_Tests: XCTestCase {
103 |
104 | func testWith2Parsers () {
105 | let parser = sum <^> any() <*> any()
106 |
107 | assertParseSucceeds(parser, [1,1], result: 2)
108 | assertParseSucceeds(parser, [10,3], result: 13)
109 | }
110 |
111 | func testDiscardingRightParser () {
112 | let parser = token(1) <* token(2)
113 |
114 | assertParseSucceeds(parser, [1,2], result: 1)
115 | assertParseFails(parser, [1,3])
116 | assertParseFails(parser, [2,2])
117 | }
118 |
119 | func testingDiscardingLeftParser () {
120 | let parser = token(1) *> token(2)
121 |
122 | assertParseSucceeds( parser, [1,2], result: 2)
123 | assertParseFails(parser, [2,2])
124 | assertParseFails(parser, [1,3])
125 | }
126 | }
127 |
128 | class Choice_Tests: XCTestCase {
129 |
130 | func testParsesOneOrTheOther () {
131 | let parser = token(1) <|> token(2)
132 |
133 | assertParseSucceeds(parser, [1])
134 | assertParseSucceeds(parser, [2])
135 | assertParseFails(parser, [3])
136 | }
137 |
138 | func testWithApplyOperator () {
139 | let parser = sum <^> token(1) <*> token(2) <|> sum <^> token(3) <*> token(4)
140 |
141 | assertParseSucceeds(parser, [1,2], result: 3)
142 | assertParseSucceeds(parser, [3,4], result: 7)
143 | assertParseFails(parser, [1,3])
144 | }
145 |
146 | func testWithApplyAndIdenticalBeginning () {
147 | let parser = sum <^> token(1) <*> token(2) <|> sum <^> token(1) <*> token(4)
148 |
149 | assertParseSucceeds(parser, [1,2], result: 3)
150 | assertParseSucceeds(parser, [1,4], result: 5)
151 | assertParseFails(parser, [1,1])
152 | }
153 |
154 | func test2ChoicesWithApplyAndIdenticalBeginning () {
155 | let parser =
156 | sum <^> token(1) <*> token(2) <|>
157 | sum <^> token(1) <*> token(3) <|>
158 | sum <^> token(1) <*> token(4)
159 |
160 | assertParseSucceeds(parser, [1,2], result: 3)
161 | assertParseSucceeds(parser, [1,3], result: 4)
162 | assertParseSucceeds(parser, [1,4], result: 5)
163 | assertParseFails(parser, [1,5])
164 | }
165 |
166 | func testLeftSideIsTriedFirst () {
167 | let parser = product <^> token(1) <*> token(2) <|> sum <^> token(1) <*> token(2)
168 |
169 | assertParseSucceeds(parser, [1,2], result: 2)
170 | }
171 |
172 | func testBacktracking() {
173 | let parser = string("food") <|> string("foot")
174 | assertParseSucceeds(parser, "food", result: "food")
175 | assertParseSucceeds(parser, "foot", result: "foot")
176 | assertParseFails(parser, "fool")
177 | }
178 | }
179 |
180 | extension Pure_Tests {
181 | public static var allTests = [
182 | ("testPureReturnsInput", testPureReturnsInput),
183 | ("testPureDoesNotConsume", testPureDoesNotConsume),
184 | ]
185 | }
186 |
187 | extension FlatMap_Tests {
188 | public static var allTests = [
189 | ("testLeftIdentityLaw", testLeftIdentityLaw),
190 | ("testRightIdentityLaw", testRightIdentityLaw),
191 | ("testAssociativityLaw", testAssociativityLaw),
192 | ]
193 | }
194 |
195 | extension Map_Tests {
196 | public static var allTests = [
197 | ("testTheIdentityLaw", testTheIdentityLaw),
198 | ("testAppliesFunctionToResultOnSuccess", testAppliesFunctionToResultOnSuccess),
199 | ("testReturnsErrorOnFailure", testReturnsErrorOnFailure),
200 | ]
201 | }
202 |
203 | extension Apply_Tests {
204 | public static var allTests = [
205 | ("testWith2Parsers", testWith2Parsers),
206 | ("testDiscardingRightParser", testDiscardingRightParser),
207 | ("testingDiscardingLeftParser", testingDiscardingLeftParser),
208 | ]
209 | }
210 |
211 | extension Choice_Tests {
212 | public static var allTests = [
213 | ("testParsesOneOrTheOther", testParsesOneOrTheOther),
214 | ("testWithApplyOperator", testWithApplyOperator),
215 | ("testWithApplyAndIdenticalBeginning", testWithApplyAndIdenticalBeginning),
216 | ("test2ChoicesWithApplyAndIdenticalBeginning", test2ChoicesWithApplyAndIdenticalBeginning),
217 | ("testLeftSideIsTriedFirst", testLeftSideIsTriedFirst),
218 | ("testBacktracking", testBacktracking),
219 | ]
220 | }
221 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/Parser_Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Parser_Tests.swift
3 | // FootlessParser
4 | //
5 | // Released under the MIT License (MIT), http://opensource.org/licenses/MIT
6 | //
7 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
8 | //
9 |
10 | import FootlessParser
11 | import XCTest
12 |
13 | class Parser_Tests: XCTestCase {
14 |
15 | func testSingleTokenParser () {
16 | let parser = token(1)
17 |
18 | var input = [1]
19 |
20 | assertParseSucceeds(parser, &input, result: 1, consumed: 1)
21 | XCTAssert(input == [], "Input should be empty")
22 | }
23 |
24 | func testSeveralTokenParsers () {
25 | var input = Array("abc")
26 |
27 | for character in "abc" {
28 | let parser = token(character)
29 | assertParseSucceeds(parser, &input, result: character, consumed: 1)
30 | }
31 | XCTAssert(input == [], "Input should be empty")
32 | }
33 |
34 | func testFailingParserReturnsError () {
35 | let parser = token(2)
36 |
37 | assertParseFails(parser, [1])
38 | }
39 |
40 | func testTokensParser () {
41 | let parser = tokens([1,2,3,4])
42 |
43 | assertParseSucceeds(parser, [1,2,3,4,5], result: [1,2,3,4], consumed: 4)
44 | assertParseFails(parser, [])
45 | assertParseFails(parser, [1,2,3])
46 | assertParseFails(parser, [1,2,3,5])
47 | }
48 |
49 | func testStringTokensParser () {
50 | let parser = string("abc")
51 |
52 | assertParseSucceeds(parser, "abcde", result: "abc", consumed: 3)
53 | assertParseFails(parser, "")
54 | assertParseFails(parser, "ab")
55 | assertParseFails(parser, "abx")
56 | }
57 |
58 | func testEmptyStringTokensParser () {
59 | let parser = string("")
60 |
61 | assertParseSucceeds(parser, "abcde", result: "", consumed: 0)
62 | }
63 |
64 | func testAnyParser () {
65 | let parser: Parser = any()
66 |
67 | var input = Array("abc")
68 |
69 | assertParseSucceeds(parser, &input, result: "a", consumed: 1)
70 | assertParseSucceeds(parser, &input, result: "b", consumed: 1)
71 | }
72 |
73 | func testOptionalParser () {
74 | let parser = optional( char("a"), otherwise: "x" )
75 |
76 | var input = Array("abc")
77 |
78 | assertParseSucceeds(parser, &input, result: "a", consumed: 1)
79 | assertParseSucceeds(parser, &input, result: "x", consumed: 0)
80 | }
81 |
82 | func testOneOrMoreParser () {
83 | let parser = oneOrMore(token(1))
84 |
85 | assertParseSucceeds(parser, [1], result: [1], consumed: 1)
86 | assertParseSucceeds(parser, [1,1,1], result: [1,1,1], consumed: 3)
87 | assertParseSucceeds(parser, [1,1,1,9], result: [1,1,1], consumed: 3)
88 | }
89 |
90 | func testZeroOrMoreParser () {
91 | let parser = zeroOrMore(token(1))
92 |
93 | assertParseSucceeds(parser, [], result: [])
94 | assertParseSucceeds(parser, [9], result: [], consumed: 0)
95 | assertParseSucceeds(parser, [1], result: [1])
96 | assertParseSucceeds(parser, [1,1,1], result: [1,1,1])
97 | assertParseSucceeds(parser, [1,1,1,9], result: [1,1,1], consumed: 3)
98 | }
99 |
100 | func testCountParser () {
101 | let parser = count(3, token(1))
102 |
103 | assertParseSucceeds(parser, [1,1,1,1], result: [1,1,1], consumed: 3)
104 | assertParseFails(parser, [1,1])
105 | assertParseFails(parser, [1,2,1])
106 | assertParseFails(parser, [])
107 | }
108 |
109 | func testCount1Parser () {
110 | let parser = count(1, token(1))
111 |
112 | assertParseSucceeds(parser, [1,1,1,1], result: [1], consumed: 1)
113 | assertParseFails(parser, [2,2])
114 | assertParseFails(parser, [])
115 | }
116 |
117 | func testCountParser0TimesWithoutConsumingInput () {
118 | let parser = count(0, token(1))
119 |
120 | assertParseSucceeds(parser, [1,1,1,1], result: [], consumed: 0)
121 | assertParseSucceeds(parser, [2,2,2,2], result: [], consumed: 0)
122 | assertParseSucceeds(parser, [], result: [], consumed: 0)
123 | }
124 |
125 | func testCountRangeOfLength3 () {
126 | let parser = count(2...4, token(1))
127 |
128 | assertParseFails(parser, [])
129 | assertParseFails(parser, [1])
130 | assertParseSucceeds(parser, [1,1], result: [1,1], consumed: 2)
131 | assertParseSucceeds(parser, [1,1,1,2], result: [1,1,1], consumed: 3)
132 | assertParseSucceeds(parser, [1,1,1,1,1,1], result: [1,1,1,1], consumed: 4)
133 | }
134 |
135 | func testCountRangeOfLength2 () {
136 | let parser = count(2...3, token(1))
137 |
138 | assertParseFails(parser, [])
139 | assertParseFails(parser, [1,2])
140 | assertParseSucceeds(parser, [1,1], result: [1,1], consumed: 2)
141 | assertParseSucceeds(parser, [1,1,1,2], result: [1,1,1], consumed: 3)
142 | assertParseSucceeds(parser, [1,1,1,1,1,1], result: [1,1,1], consumed: 3)
143 | }
144 |
145 | func testCountRangeOfLength1 () {
146 | let parser = count(2...2, token(1))
147 |
148 | assertParseFails(parser, [])
149 | assertParseFails(parser, [1,2])
150 | assertParseSucceeds(parser, [1,1], result: [1,1], consumed: 2)
151 | assertParseSucceeds(parser, [1,1,1], result: [1,1], consumed: 2)
152 | }
153 |
154 | func testCountRangeFrom0 () {
155 | let parser = count(0...2, token(1))
156 |
157 | assertParseSucceeds(parser, [])
158 | assertParseSucceeds(parser, [2,2])
159 | assertParseSucceeds(parser, [1,2])
160 | assertParseSucceeds(parser, [1,1], result: [1,1], consumed: 2)
161 | assertParseSucceeds(parser, [1,1,1,2], result: [1,1], consumed: 2)
162 | }
163 |
164 | func testOneOfParser () {
165 | let parser = oneOf("abc")
166 |
167 | assertParseSucceeds(parser, "a", result: "a")
168 | assertParseSucceeds(parser, "b", result: "b")
169 | assertParseSucceeds(parser, "c", result: "c")
170 | assertParseFails(parser, "d")
171 | assertParseSucceeds(parser, "ax", result: "a", consumed: 1)
172 | }
173 |
174 | func testNoneOfParser () {
175 | let parser = noneOf("abc")
176 |
177 | assertParseFails(parser, "a")
178 | assertParseFails(parser, "b")
179 | assertParseFails(parser, "c")
180 | assertParseSucceeds(parser, "d", result: "d")
181 | assertParseSucceeds(parser, "da", result: "d", consumed: 1)
182 | }
183 |
184 | func testNotParser () {
185 | let parser = not("a" as Character)
186 |
187 | assertParseSucceeds(parser, "b", result: "b")
188 | assertParseSucceeds(parser, "c", result: "c")
189 | assertParseFails(parser, "a")
190 | }
191 |
192 | func testEofParser () {
193 | let parser = token(1) <* eof()
194 |
195 | assertParseSucceeds(parser, [1], result: 1)
196 | assertParseFails(parser, [1,2])
197 | assertParseSucceeds(token(1), [1,2])
198 | }
199 |
200 | func testParsingAString () {
201 | let parser = zeroOrMore(char("a"))
202 |
203 | XCTAssertEqual(try! parse(parser, "a"), "a")
204 | XCTAssertEqual(try! parse(parser, "aaaa"), "aaaa")
205 | XCTempAssertThrowsError { _ = try parse(parser, "aaab") }
206 | }
207 |
208 | func testParsingAnArray () {
209 | let parser = zeroOrMore(token(1))
210 |
211 | XCTAssertEqual(try! parse(parser, []), [])
212 | XCTAssertEqual(try! parse(parser, [1]), [1])
213 | XCTAssertEqual(try! parse(parser, [1,1,1]), [1,1,1])
214 | XCTempAssertThrowsError { _ = try parse(parser, [1,2]) }
215 | }
216 | }
217 |
218 | extension Parser_Tests {
219 | public static var allTests = [
220 | ("testSingleTokenParser", testSingleTokenParser),
221 | ("testSeveralTokenParsers", testSeveralTokenParsers),
222 | ("testFailingParserReturnsError", testFailingParserReturnsError),
223 | ("testTokensParser", testTokensParser),
224 | ("testStringTokensParser", testStringTokensParser),
225 | ("testEmptyStringTokensParser", testEmptyStringTokensParser),
226 | ("testAnyParser", testAnyParser),
227 | ("testOptionalParser", testOptionalParser),
228 | ("testOneOrMoreParser", testOneOrMoreParser),
229 | ("testZeroOrMoreParser", testZeroOrMoreParser),
230 | ("testCountParser", testCountParser),
231 | ("testCount1Parser", testCount1Parser),
232 | ("testCountParser0TimesWithoutConsumingInput", testCountParser0TimesWithoutConsumingInput),
233 | ("testCountRangeOfLength3", testCountRangeOfLength3),
234 | ("testCountRangeOfLength2", testCountRangeOfLength2),
235 | ("testCountRangeOfLength1", testCountRangeOfLength1),
236 | ("testCountRangeFrom0", testCountRangeFrom0),
237 | ("testOneOfParser", testOneOfParser),
238 | ("testNoneOfParser", testNoneOfParser),
239 | ("testNotParser", testNotParser),
240 | ("testEofParser", testEofParser),
241 | ("testParsingAString", testParsingAString),
242 | ("testParsingAnArray", testParsingAnArray),
243 | ]
244 | }
245 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/PerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PerformanceTests.swift
3 | // FootlessParser
4 | //
5 | // Created by Bouke Haarsma on 16-05-16.
6 | //
7 | //
8 |
9 | import FootlessParser
10 | import XCTest
11 |
12 | class PerformanceTests: XCTestCase {
13 | func testZeroOrMoreGeneric () {
14 | let parser = zeroOrMore(token(1))
15 | let input = Array(repeating: 1, count: 1000)
16 | measure {
17 | self.assertParseSucceeds(parser, input, consumed: 1000)
18 | }
19 | }
20 |
21 | func testZeroOrMoreString () {
22 | let parser = zeroOrMore(char("a"))
23 | let input = String(repeating: "a", count: 1000)
24 | measure {
25 | self.assertParseSucceeds(parser, input, consumed: 1000)
26 | }
27 | }
28 |
29 | func testOneOrMoreGeneric() {
30 | let parser = oneOrMore(token(1))
31 |
32 | assertParseSucceeds(parser, [1], result: [1], consumed: 1)
33 | assertParseSucceeds(parser, [1,1,1], result: [1,1,1], consumed: 3)
34 | assertParseSucceeds(parser, [1,1,1,9], result: [1,1,1], consumed: 3)
35 |
36 | let input = Array(repeating: 1, count: 1000)
37 | measure {
38 | self.assertParseSucceeds(parser, input, consumed: 1000)
39 | }
40 | }
41 |
42 | func testOneOrMoreString () {
43 | let parser = oneOrMore(char("a"))
44 |
45 | assertParseSucceeds(parser, "a", result: "a")
46 | assertParseSucceeds(parser, "aaa", result: "aaa")
47 | assertParseSucceeds(parser, "aaab", result: "aaa", consumed: 3)
48 |
49 | let input = String(repeating: "a", count: 1000)
50 | measure {
51 | self.assertParseSucceeds(parser, input, consumed: 1000)
52 | }
53 | }
54 |
55 | func testCount1000Generic () {
56 | let parser = count(1000, token(1))
57 | let input = Array(repeating: 1, count: 1000)
58 | measure {
59 | self.assertParseSucceeds(parser, input, consumed: 1000)
60 | }
61 | }
62 |
63 | func testCount1000String () {
64 | let parser = count(1000, char("a"))
65 | let input = String(repeating: "a", count: 1000)
66 | measure {
67 | self.assertParseSucceeds(parser, input, consumed: 1000)
68 | }
69 | }
70 |
71 | func testRange0To1000Generic () {
72 | let parser = count(0...1000, token(1))
73 | let input = Array(repeating: 1, count: 1000)
74 | measure {
75 | self.assertParseSucceeds(parser, input, consumed: 1000)
76 | }
77 | }
78 |
79 | func testRange0To1000String () {
80 | let parser = count(0...1000, char("a"))
81 | let input = String(repeating: "a", count: 1000)
82 | measure {
83 | self.assertParseSucceeds(parser, input, consumed: 1000)
84 | }
85 | }
86 |
87 | func testBacktrackingLeftString() {
88 | let parser = string("food") <|> string("foot")
89 | measure {
90 | for _ in 0..<1000 {
91 | self.assertParseSucceeds(parser, "food")
92 | }
93 | }
94 | }
95 |
96 | func testBacktrackingRightString() {
97 | let parser = string("food") <|> string("foot")
98 | measure {
99 | for _ in 0..<1000 {
100 | self.assertParseSucceeds(parser, "foot")
101 | }
102 | }
103 | }
104 |
105 | func testBacktrackingFailString() {
106 | let parser = string("food") <|> string("foot")
107 | measure {
108 | for _ in 0..<1000 {
109 | self.assertParseFails(parser, "fool")
110 | }
111 | }
112 | }
113 |
114 | func testCSVRow() {
115 | measure {
116 | for _ in 0..<1000 {
117 | _ = try! parse(row, "Hello,\"Dear World\",\"Hello\",Again\n")
118 | }
119 | }
120 | }
121 | }
122 |
123 | extension PerformanceTests {
124 | public static var allTests = [
125 | ("testZeroOrMoreGeneric", testZeroOrMoreGeneric),
126 | ("testZeroOrMoreString", testZeroOrMoreString),
127 | ("testOneOrMoreGeneric", testOneOrMoreGeneric),
128 | ("testOneOrMoreString", testOneOrMoreString),
129 | ("testCount1000Generic", testCount1000Generic),
130 | ("testCount1000String", testCount1000String),
131 | ("testRange0To1000Generic", testRange0To1000Generic),
132 | ("testRange0To1000String", testRange0To1000String),
133 | ("testBacktrackingLeftString", testBacktrackingLeftString),
134 | ("testBacktrackingRightString", testBacktrackingRightString),
135 | ("testBacktrackingFailString", testBacktrackingFailString),
136 | ("testCSVRow", testCSVRow),
137 | ]
138 | }
139 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/StringParser_Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringParser_Tests.swift
3 | // FootlessParser
4 | //
5 | // Released under the MIT License (MIT), http://opensource.org/licenses/MIT
6 | //
7 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
8 | //
9 |
10 | import FootlessParser
11 | import XCTest
12 |
13 | class StringParser_Tests: XCTestCase {
14 |
15 | func testCharParser () {
16 | let parser = char("a")
17 |
18 | var input = Array("a")
19 |
20 | assertParseFails(parser, "b")
21 | assertParseSucceeds(parser, &input, result: "a")
22 | XCTAssert(input == [], "Input should be empty")
23 | }
24 |
25 | func testOffsetForNoneOf () {
26 | let input = "AB"
27 | let parser = zeroOrMore(noneOf(["BC"]))
28 | // fatal error: cannot increment beyond endIndex
29 | let _ = try! parser.parse(AnyCollection(input))
30 | assertParseSucceeds(parser, input, result: "AB")
31 | }
32 |
33 | func testOneOrMoreParserForCharacters () {
34 | let parser = oneOrMore(char("a"))
35 |
36 | assertParseSucceeds(parser, "a", result: "a")
37 | assertParseSucceeds(parser, "aaa", result: "aaa")
38 | assertParseSucceeds(parser, "aaab", result: "aaa", consumed: 3)
39 | }
40 |
41 | func testZeroOrMoreParserForCharacters () {
42 | let parser = zeroOrMore(char("a"))
43 |
44 | assertParseSucceeds(parser, "", result: "")
45 | assertParseSucceeds(parser, "b", result: "")
46 | assertParseSucceeds(parser, "a", result: "a")
47 | assertParseSucceeds(parser, "aaa", result: "aaa")
48 | assertParseSucceeds(parser, "aaab", result: "aaa", consumed: 3)
49 | }
50 |
51 | func testStringCountParser () {
52 | let parser = count(3, char("a"))
53 |
54 | assertParseSucceeds(parser, "aaaa", result: "aaa", consumed: 3)
55 | assertParseFails(parser, "aa")
56 | assertParseFails(parser, "axa")
57 | assertParseFails(parser, "")
58 | }
59 |
60 | func testStringCountRangeParser () {
61 | let parser = count(2...4, char("a"))
62 |
63 | assertParseFails(parser, "")
64 | assertParseFails(parser, "ab")
65 | assertParseSucceeds(parser, "aab", result: "aa", consumed: 2)
66 | assertParseSucceeds(parser, "aaaaa", result: "aaaa", consumed: 4)
67 | }
68 |
69 | func testNoneOfStrings() {
70 | let parser = zeroOrMore(noneOf(["foo", "bar"]))
71 |
72 | assertParseSucceeds(parser, "", result: "")
73 | assertParseSucceeds(parser, "a foo", result: "a ")
74 | assertParseSucceeds(parser, "a bar", result: "a ")
75 | assertParseSucceeds(parser, "bar foo", result: "")
76 | }
77 |
78 | func testString() {
79 | let parser = string("foo")
80 | assertParseSucceeds(parser, "foo", result: "foo")
81 | assertParseSucceeds(parser, "foobar", result: "foo")
82 | assertParseFails(parser, "barf")
83 | assertParseFails(parser, "bar")
84 | assertParseFails(parser, "ba")
85 | assertParseFails(parser, "b")
86 | assertParseFails(parser, "")
87 | }
88 | }
89 |
90 | extension StringParser_Tests {
91 | public static var allTests = [
92 | ("testCharParser", testCharParser),
93 | ("testOneOrMoreParserForCharacters", testOneOrMoreParserForCharacters),
94 | ("testZeroOrMoreParserForCharacters", testZeroOrMoreParserForCharacters),
95 | ("testStringCountParser", testStringCountParser),
96 | ("testStringCountRangeParser", testStringCountRangeParser),
97 | ("testNoneOfStrings", testNoneOfStrings),
98 | ("testString", testString),
99 | ("testOffsetForNoneOf", testOffsetForNoneOf)
100 | ]
101 | }
102 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/TestHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestHelpers.swift
3 | // FootlessParser
4 | //
5 | // Created by Kåre Morstøl on 09.04.15.
6 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import FootlessParser
11 | import XCTest
12 |
13 | func == (lhs: AnyCollection, rhs: AnyCollection) -> Bool {
14 | guard lhs.count == rhs.count else { return false }
15 | for (lhs, rhs) in zip(lhs, rhs) {
16 | guard lhs == rhs else { return false }
17 | }
18 | return true
19 | }
20 |
21 | public func ==
22 | (lhs: ((output: R, remainder: AnyCollection)?, E?), rhs: ((output: R, remainder: AnyCollection)?, E?)) -> Bool {
23 | if let lhs=lhs.0, let rhs=rhs.0 {
24 | return lhs.output == rhs.output && lhs.remainder == rhs.remainder
25 | }
26 | if let lhs=lhs.1, let rhs=rhs.1 {
27 | return lhs == rhs
28 | }
29 | return false
30 | }
31 |
32 | public func !=
33 | (lhs: ((output: R, remainder: AnyCollection)?, E?), rhs: ((output: R, remainder: AnyCollection)?, E?)) -> Bool {
34 |
35 | return !(lhs == rhs)
36 | }
37 |
38 | extension XCTestCase {
39 |
40 | /**
41 | Verifies that 2 parsers return the same given the same input, whether it be success or failure.
42 |
43 | - parameter shouldSucceed?: Optionally verifies success or failure.
44 | */
45 | func assertParsesEqually
46 | (_ p1: Parser, _ p2: Parser, input: [T], shouldSucceed: Bool? = nil, file: StaticString = #file, line: UInt = #line) {
47 |
48 | let parse = { (p: Parser) -> ((output: R, remainder: AnyCollection)?, String?) in
49 | do {
50 | return (try p.parse(AnyCollection(input)), nil)
51 | } catch let error as ParseError {
52 | return (nil, error.description)
53 | } catch {
54 | return (nil, nil) // should not happen
55 | }
56 | }
57 | let (r1, r2) = (parse(p1), parse(p2))
58 | if r1 != r2 {
59 | return XCTFail("with input '\(input)': '\(r1)' != '\(r2)", file: file, line: line)
60 | }
61 | if let shouldSucceed = shouldSucceed {
62 | if shouldSucceed && (r1.0 == nil) {
63 | XCTFail("parsing of '\(input)' failed, shoud have succeeded", file: file, line: line)
64 | }
65 | if !shouldSucceed && (r1.1 == nil) {
66 | XCTFail("parsing of '\(input)' succeeded, shoud have failed", file: file, line: line)
67 | }
68 | }
69 | }
70 |
71 | /** Verifies the parse succeeds, and optionally checks the result and how many tokens were consumed. Updates the provided 'input' parameter to the remaining input. */
72 | func assertParseSucceeds
73 | (_ p: Parser, _ input: inout [T], result: R? = nil, consumed: Int? = nil, file: StaticString = #file, line: UInt = #line) {
74 |
75 | do {
76 | let (output, remainder) = try p.parse(AnyCollection(input))
77 | if let result = result {
78 | if output != result {
79 | XCTFail("with input '\(input)': output should be '\(result)', was '\(output)'. ", file: file, line: line)
80 | }
81 | }
82 | if let consumed = consumed {
83 | let actuallyconsumed = input.count - remainder.count
84 | if actuallyconsumed != consumed {
85 | XCTFail("should have consumed \(consumed), took \(actuallyconsumed)", file: file, line: line)
86 | }
87 | }
88 | input = Array(remainder)
89 | } catch let error {
90 | XCTFail("with input \(input): \(error)", file: file, line: line)
91 | }
92 | }
93 |
94 | /** Verifies the parse succeeds, and optionally checks the result and how many tokens were consumed. Updates the provided 'input' parameter to the remaining input. */
95 | func assertParseSucceeds
96 | (_ p: Parser, _ input: inout [T], result: [R]? = nil, consumed: Int? = nil, file: StaticString = #file, line: UInt = #line) {
97 |
98 | do {
99 | let (output, remainder) = try p.parse(AnyCollection(input))
100 | if let result = result {
101 | if output != result {
102 | XCTFail("with input '\(input)': output should be '\(result)', was '\(output)'. ", file: file, line: line)
103 | }
104 | }
105 | if let consumed = consumed {
106 | let actuallyconsumed = input.count - remainder.count
107 | if actuallyconsumed != consumed {
108 | XCTFail("should have consumed \(consumed), took \(actuallyconsumed)", file: file, line: line)
109 | }
110 | }
111 | input = Array(remainder)
112 | } catch let error {
113 | XCTFail("with input \(input): \(error)", file: file, line: line)
114 | }
115 | }
116 |
117 | /** Verifies the parse succeeds, and optionally checks the result and how many tokens were consumed. */
118 | func assertParseSucceeds
119 | (_ p: Parser, _ input: C, result: R? = nil, consumed: Int? = nil, file: StaticString = #file, line: UInt = #line) where C.Iterator.Element == T {
120 |
121 | var parserinput = Array(input)
122 | assertParseSucceeds(p, &parserinput, result: result, consumed: consumed, file: file, line: line)
123 | }
124 |
125 | /** Verifies the parse succeeds, and optionally checks the result and how many tokens were consumed. */
126 | func assertParseSucceeds
127 | (_ p: Parser, _ input: C, result: [R]? = nil, consumed: Int? = nil, file: StaticString = #file, line: UInt = #line) where C.Iterator.Element == T {
128 |
129 | var input = Array(input)
130 | assertParseSucceeds(p, &input, result: result, consumed: consumed, file: file, line: line)
131 | }
132 |
133 |
134 | /** Verifies the parse fails with the given input. */
135 | func assertParseFails
136 | (_ p: Parser, _ input: T, file: StaticString = #file, line: UInt = #line) {
137 |
138 | do {
139 | let (output, _) = try p.parse(AnyCollection([input]))
140 | XCTFail("Parsing succeeded with output '\(output)', should have failed.", file: file, line: line)
141 | } catch {}
142 | }
143 |
144 | /** Verifies the parse fails with the given collection as input. */
145 | func assertParseFails
146 | (_ p: Parser, _ input: C, file: StaticString = #file, line: UInt = #line) where C.Iterator.Element == T {
147 |
148 | do {
149 | let (output, _) = try p.parse(AnyCollection(Array(input)))
150 | XCTFail("Parsing succeeded with output '\(output)', should have failed.", file: file, line: line)
151 | } catch {}
152 | }
153 | }
154 |
155 |
156 | import Foundation
157 |
158 | extension XCTestCase {
159 |
160 | // from https://forums.developer.apple.com/thread/5824
161 | func XCTempAssertThrowsError (message: String = "", file: StaticString = #file, line: UInt = #line, _ block: () throws -> ()) {
162 | do {
163 | try block()
164 |
165 | let msg = (message == "") ? "Tested block did not throw error as expected." : message
166 | XCTFail(msg, file: file, line: line)
167 | } catch {}
168 | }
169 |
170 | func XCTempAssertNoThrowError(message: String = "", file: StaticString = #file, line: UInt = #line, _ block: () throws -> ()) {
171 | do { try block() }
172 | catch {
173 | let msg = (message == "") ? "Tested block threw unexpected error: " : message
174 | XCTFail(msg + String(describing: error), file: file, line: line)
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 |
2 | import XCTest
3 |
4 | @testable import FootlessParserTests
5 |
6 | let tests: [XCTestCaseEntry] = [
7 | testCase(CSV.allTests),
8 | testCase(Examples.allTests),
9 | testCase(Pure_Tests.allTests),
10 | testCase(FlatMap_Tests.allTests),
11 | testCase(Map_Tests.allTests),
12 | testCase(Apply_Tests.allTests),
13 | testCase(Choice_Tests.allTests),
14 | testCase(Parser_Tests.allTests),
15 | testCase(PerformanceTests.allTests),
16 | testCase(StringParser_Tests.allTests),
17 | ]
18 |
19 | XCTMain(tests)
20 |
--------------------------------------------------------------------------------