├── .swift-version
├── FootlessParser.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── FootlessParser.xcscheme
├── FootlessParserTests_Info.plist
├── FootlessParser_Info.plist
└── project.pbxproj
├── .gitignore
├── Package.swift
├── Sources
└── FootlessParser
│ ├── Error.swift
│ ├── Parsers.swift
│ ├── Converters.swift
│ ├── Parser+Operators.swift
│ ├── Parser.swift
│ └── StringParser.swift
├── Tests
├── LinuxMain.swift
└── FootlessParserTests
│ ├── Grammar
│ ├── Examples.swift
│ ├── CSV.swift
│ └── CSV-quotes.csv
│ ├── StringParser_Tests.swift
│ ├── PerformanceTests.swift
│ ├── Parser+Operators_Tests.swift
│ ├── TestHelpers.swift
│ └── Parser_Tests.swift
├── FootlessParser.podspec
├── LICENSE.txt
├── .travis.yml
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FootlessParser.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SchemeUserState
5 |
6 | FootlessParser.xcscheme
7 |
8 |
9 | SuppressBuildableAutocreation
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/Grammar/CSV.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CSV.swift
3 | // FootlessParser
4 | //
5 | // Created by Kåre Morstøl on 05.06.15.
6 | // Copyright (c) 2015 NotTooBad Software. All rights reserved.
7 | //
8 |
9 | import FootlessParser
10 | import XCTest
11 | import Foundation
12 |
13 | let delimiter = "," as Character
14 | let quote = "\"" as Character
15 | let newline = "\n" as Character
16 |
17 | let cell = char(quote) *> 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 |
--------------------------------------------------------------------------------
/.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.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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.. = 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Tests/FootlessParserTests/Grammar/CSV-quotes.csv:
--------------------------------------------------------------------------------
1 | "position","const","created","modified","description","Title","Title type","Directors","You rated","IMDb Rating","Runtime (mins)","Year","Genres","Num. Votes","Release Date (month/day/year)","URL"
2 | "1","tt2562232","Tue Jun 2 00:00:00 2015","","","Birdman: Or (The Unexpected Virtue of Ignorance)","Feature Film","Alejandro G. Iñárritu","5","7.9","119","2014","comedy,
3 |
4 | drama","266756","2014-08-27","http://www.imdb.com/title/tt2562232/"
5 | "2",tt0837562,"Sat May 30 00:00:00 2015",,"","Hotel Transylvania","Feature Film","Genndy Tartakovsky","7","7.1","91","2012","animation, comedy, family, fantasy","127594","2012-09-08","http://www.imdb.com/title/tt0837562/"
6 |
7 | 3,"tt0082869","Tue May 26 00:00:00 2015","","","Outland","Feature Film","Peter Hyams","5","6.6","112","1981","action, crime, sci_fi, thriller, western","19727","1981-05-22","http://www.imdb.com/title/tt0082869/"
8 | "4","tt1792647","Mon May 25 00:00:00 2015","","","Cirque du Soleil: Worlds Away","Feature Film","Andrew Adamson","6","6.7","91","2012","fantasy","6742","2012-10-20","http://www.imdb.com/title/tt1792647/"
9 | "5","tt2960930","Mon May 25 00:00:00 2015","","","Extraterrestrial","Feature Film","Colin Minihan","6","5.0","101","2014","horror, sci_fi","8513","2014-04-18","http://www.imdb.com/title/tt2960930/"
10 | "6","tt0787474","Sat May 23 00:00:00 2015","","","The Boxtrolls","Feature Film","Graham Annable, Anthony Stacchi","7","6.8","96","2014","animation, adventure, comedy, family, fantasy","29781","2014-08-31","http://www.imdb.com/title/tt0787474/"
11 | "7","tt1799508","Thu May 21 00:00:00 2015","","","After","Feature Film","Ryan Smith","4","5.4","90","2012","mystery, thriller","2124","2012-09-14","http://www.imdb.com/title/tt1799508/"
12 | "8","tt1562899","Mon May 18 00:00:00 2015","","","Jinn","Feature Film","Ajmal Zaheer Ahmad","2","4.2","97","2014","thriller","3145","2014-04-04","http://www.imdb.com/title/tt1562899/"
13 | "9","tt2395427","Wed May 13 00:00:00 2015","","","Avengers: Age of Ultron","Feature Film","Joss Whedon","7","8.0","141","2015","action, adventure, sci_fi","209535","2015-04-13","http://www.imdb.com/title/tt2395427/"
14 | "10","tt2265171","Tue May 12 00:00:00 2015","","","The Raid 2","Feature Film","Gareth Evans","4","8.1","150","2014","action, crime, thriller","69063","2014-01-21","http://www.imdb.com/title/tt2265171/"
15 | "11","tt1959490","Sun Apr 26 00:00:00 2015","","","Noah","Feature Film","Darren Aronofsky","8","5.9","138","2014","action, adventure, drama","174004","2014-03-10","http://www.imdb.com/title/tt1959490/"
16 | "12","tt0825232","Sat Apr 25 00:00:00 2015","","","The Bucket List","Feature Film","Rob Reiner","7","7.4","97","2007","adventure, comedy, drama","166523","2007-12-16","http://www.imdb.com/title/tt0825232/"
17 | "13","tt1059786","Sat Apr 25 00:00:00 2015","","","Eagle Eye","Feature Film","D.J. Caruso","6","6.6","118","2008","action, mystery, thriller","139219","2008-09-16","http://www.imdb.com/title/tt1059786/"
18 | "14","tt1981115","Fri Apr 24 00:00:00 2015","","","Thor: The Dark World","Feature Film","Alan Taylor","5","7.1","112","2013","action, adventure, fantasy","335463","2013-10-30","http://www.imdb.com/title/tt1981115/"
19 | "15","tt2802144","Sat Apr 18 00:00:00 2015","","","Kingsman: The Secret Service","Feature Film","Matthew Vaughn","6","8.0","129","2014","action, adventure, comedy, crime","183232","2014-12-13","http://www.imdb.com/title/tt2802144/"
20 | "16","tt0816692","Wed Apr 15 00:00:00 2015","","","Interstellar","Feature Film","Christopher Nolan","7","8.7","169","2014","adventure, sci_fi","632869","2014-10-26","http://www.imdb.com/title/tt0816692/"
21 | "17","tt1584943","Tue Mar 24 00:00:00 2015","","","Dark Space","Feature Film","Emmett Callinan","2","3.8","91","2013","sci_fi","697","2013-07-15","http://www.imdb.com/title/tt1584943/"
22 | "18","tt1622547","Sun Mar 22 00:00:00 2015","","","30 Minutes or Less","Feature Film","Ruben Fleischer","6","6.2","83","2011","action, comedy, crime","72195","2011-08-11","http://www.imdb.com/title/tt1622547/"
23 | "19","tt0480239","Sun Mar 22 00:00:00 2015","","","Atlas Shrugged: Part I","Feature Film","Paul Johansson","6","5.8","97","2011","drama, mystery, sci_fi","10841","2011-04-15","http://www.imdb.com/title/tt0480239/"
24 | "20","tt0268397","Sun Mar 22 00:00:00 2015","","","Jimmy Neutron: Boy Genius","Feature Film","John A. Davis","5","6.0","82","2001","animation, action, adventure, comedy, family, sci_fi","21502","2001-12-09","http://www.imdb.com/title/tt0268397/"
25 | "21","tt0072251","Sat Mar 21 00:00:00 2015","","","The Taking of Pelham One Two Three","Feature Film","Joseph Sargent","6","7.7","104","1974","action, crime, thriller","18262","1974-09","http://www.imdb.com/title/tt0072251/"
26 | "22","tt1267297","Sat Mar 21 00:00:00 2015","","","Hercules","Feature Film","Brett Ratner","4","6.1","98","2014","action, adventure","91937","2014-07-23","http://www.imdb.com/title/tt1267297/"
27 | "23","tt1646971","Fri Mar 20 00:00:00 2015","","","How to Train Your Dragon 2","Feature Film","Dean DeBlois","3","7.9","102","2014","animation, action, adventure, comedy, family, fantasy","171934","2014-05-16","http://www.imdb.com/title/tt1646971/"
28 | "24","tt0098309","Thu Mar 19 00:00:00 2015","","","She-Devil","Feature Film","Susan Seidelman","6","5.3","99","1989","comedy","11292","1989-12-06","http://www.imdb.com/title/tt0098309/"
29 | "25","tt0086873","Thu Mar 19 00:00:00 2015","","","All of Me","Feature Film","Carl Reiner","7","6.7","93","1984","comedy, fantasy, romance","12125","1984-09-07","http://www.imdb.com/title/tt0086873/"
30 | "26","tt0085959","Thu Mar 19 00:00:00 2015","","","The Meaning of Life","Feature Film","Terry Jones, Terry Gilliam","7","7.6","107","1983","comedy, musical","74011","1983-03-31","http://www.imdb.com/title/tt0085959/"
31 | "27","tt1254207","Thu Mar 19 00:00:00 2015","","","Big Buck Bunny","Short Film","Sacha Goedegebure","3","6.7","10","2008","animation, comedy","1444","2008-04-10","http://www.imdb.com/title/tt1254207/"
32 | "28","tt0268380","Thu Mar 19 00:00:00 2015","","","Ice Age","Feature Film","Chris Wedge, Carlos Saldanha","6","7.6","81","2002","animation, adventure, comedy, family","282766","2002-03-12","http://www.imdb.com/title/tt0268380/"
33 | "29","tt0852863","Thu Mar 19 00:00:00 2015","","","Phineas and Ferb","TV Series","","9","8.0","30","2007","animation, comedy, family","17397","2007-08-17","http://www.imdb.com/title/tt0852863/"
34 | "30","tt1294970","Wed Mar 18 00:00:00 2015","","","The Angriest Man in Brooklyn","Feature Film","Phil Alden Robinson","6","5.7","83","2014","comedy, drama","12677","2014-05-22","http://www.imdb.com/title/tt1294970/"
35 | "31","tt0458413","Wed Mar 11 00:00:00 2015","","","A Long Way Down","Feature Film","Pascal Chaumeil","5","6.4","96","2014","comedy, drama","19143","2014-02-10","http://www.imdb.com/title/tt0458413/"
36 | "32","tt1987028","Sun Mar 8 00:00:00 2015","","","Werewolf: The Beast Among Us","Video","Louis Morneau","4","5.1","93","2012","horror, thriller","4386","2012-10-09","http://www.imdb.com/title/tt1987028/"
37 | "33","tt1418377","Sun Mar 8 00:00:00 2015","","","I, Frankenstein","Feature Film","Stuart Beattie","5","5.2","92","2014","action, fantasy, sci_fi","56182","2014-01-20","http://www.imdb.com/title/tt1418377/"
38 | "34","tt0088979","Sat Mar 7 00:00:00 2015","","","D.A.R.Y.L.","Feature Film","Simon Wincer","6","6.1","99","1985","family, sci_fi","8741","1985-06-14","http://www.imdb.com/title/tt0088979/"
39 | "35","tt0111512","Sun Mar 1 00:00:00 2015","","","The Legend of Drunken Master","Feature Film","Chia-Liang Liu","3","7.6","102","1994","action, comedy","31458","1994-02-03","http://www.imdb.com/title/tt0111512/"
40 | "36","tt3479316","Sun Mar 1 00:00:00 2015","","","Parallels","Feature Film","Christopher Leone","6","6.4","83","2015","action, sci_fi","4296","2015-03-01","http://www.imdb.com/title/tt3479316/"
41 | "37","tt0227445","Sun Mar 1 00:00:00 2015","","","The Score","Feature Film","Frank Oz","4","6.8","124","2001","action, crime, romance, thriller","94960","2001-07-09","http://www.imdb.com/title/tt0227445/"
42 | "38","tt0120177","Fri Feb 27 00:00:00 2015","","","Spawn","Feature Film","Mark A.Z. Dippé","3","5.2","96","1997","action, horror","48845","1997-07","http://www.imdb.com/title/tt0120177/"
43 | "39","tt0120738","Wed Feb 25 00:00:00 2015","","","Lost in Space","Feature Film","Stephen Hopkins","2","5.0","130","1998","action, adventure, family, sci_fi, thriller","55107","1998-04-03","http://www.imdb.com/title/tt0120738/"
44 | "40","tt0893402","Tue Feb 24 00:00:00 2015","","","Franklyn","Feature Film","Gerald McMorrow","7","6.2","98","2008","drama, fantasy, sci_fi, thriller","13920","2008-10-16","http://www.imdb.com/title/tt0893402/"
45 | "41","tt2368672","Wed Feb 18 00:00:00 2015","","","Minuscule: Valley of the Lost Ants","Feature Film","Hélène Giraud, Thomas Szabo","6","7.1","89","2013","animation, adventure, family","3414","2013-11-17","http://www.imdb.com/title/tt2368672/"
46 | "42","tt1205537","Sun Feb 15 00:00:00 2015","","","Jack Ryan: Shadow Recruit","Feature Film","Kenneth Branagh","4","6.2","105","2014","action, thriller","85935","2014-01-15","http://www.imdb.com/title/tt1205537/"
47 | "43","tt0864835","Sat Feb 14 00:00:00 2015","","","Mr. Peabody & Sherman","Feature Film","Rob Minkoff","5","6.9","92","2014","animation, adventure, comedy, family, sci_fi","39146","2014-02-07","http://www.imdb.com/title/tt0864835/"
48 | "44","tt0100802","Wed Feb 11 00:00:00 2015","","","Total Recall","Feature Film","Paul Verhoeven","5","7.5","113","1990","action, sci_fi","215737","1990-06-01","http://www.imdb.com/title/tt0100802/"
49 | "45","tt1709143","Sun Feb 8 00:00:00 2015","","","The Last Days on Mars","Feature Film","Ruairi Robinson","3","5.5","98","2013","horror, sci_fi, thriller","24674","2013-05-20","http://www.imdb.com/title/tt1709143/"
50 | "46","tt1706620","Fri Feb 6 00:00:00 2015","","","Snowpiercer","Feature Film","Bong Joon Ho","6","7.0","126","2013","action, sci_fi, thriller","138531","2013-08-01","http://www.imdb.com/title/tt1706620/"
51 | "47","tt1517489","Fri Feb 6 00:00:00 2015","","","Spy Kids: All the Time in the World in 4D","Feature Film","Robert Rodriguez","3","3.6","89","2011","action, adventure, comedy, family, sci_fi","13602","2011-08-18","http://www.imdb.com/title/tt1517489/"
52 | "48","tt1634122","Fri Feb 6 00:00:00 2015","","","Johnny English Reborn","Feature Film","Oliver Parker","4","6.3","101","2011","action, adventure, comedy, crime, drama","80654","2011-09-15","http://www.imdb.com/title/tt1634122/"
53 | "49","tt0120611","Sun Feb 1 00:00:00 2015","","","Blade","Feature Film","Stephen Norrington","4","7.1","120","1998","action, horror","169748","1998-08-19","http://www.imdb.com/title/tt0120611/"
54 | "50","tt0381601","Sat Jan 31 00:00:00 2015","","","Slipstream","Feature Film","David van Eyssen","3","4.6","89","2005","sci_fi","2776","2005-02-03","http://www.imdb.com/title/tt0381601/"
55 | "51","tt2872732","Thu Jan 22 00:00:00 2015","","","Lucy","Feature Film","Luc Besson","6","6.4","89","2014","action, sci_fi, thriller","255571","2014-07-25","http://www.imdb.com/title/tt2872732/"
56 | "52","tt2310332","Fri Jan 16 00:00:00 2015","","","The Hobbit: The Battle of the Five Armies","Feature Film","Peter Jackson","7","7.5","144","2014","adventure, fantasy","264311","2014-12-01","http://www.imdb.com/title/tt2310332/"
57 | "53","tt1170358","Wed Jan 14 00:00:00 2015","","","The Hobbit: The Desolation of Smaug","Feature Film","Peter Jackson","7","8.0","161","2013","adventure, fantasy","411655","2013-12-02","http://www.imdb.com/title/tt1170358/"
58 | "54","tt0113568","Sun Jan 11 00:00:00 2015","","","Ghost in the Shell","Feature Film","Mamoru Oshii","6","8.0","83","1995","animation, action, mystery, sci_fi, thriller","67815","1995-11-18","http://www.imdb.com/title/tt0113568/"
59 | "55","tt1907668","Fri Jan 9 00:00:00 2015","","","Flight","Feature Film","Robert Zemeckis","7","7.3","138","2012","drama, thriller","238527","2012-10-14","http://www.imdb.com/title/tt1907668/"
60 | "56","tt0338526","Thu Jan 8 00:00:00 2015","","","Van Helsing","Feature Film","Stephen Sommers","4","6.0","131","2004","action, adventure, fantasy, mystery, thriller","171734","2004-05-03","http://www.imdb.com/title/tt0338526/"
61 | "57","tt1742334","Mon Jan 5 00:00:00 2015","","","Sabotage","Feature Film","David Ayer","4","5.7","109","2014","action, crime, drama, thriller","39946","2014-03-19","http://www.imdb.com/title/tt1742334/"
62 | "58","tt1043726","Sun Jan 4 00:00:00 2015","","","The Legend of Hercules","Feature Film","Renny Harlin","5","4.2","99","2014","action, adventure, fantasy","40106","2014-01-08","http://www.imdb.com/title/tt1043726/"
63 | "59","tt0125439","Wed Dec 24 00:00:00 2014","","","Notting Hill","Feature Film","Roger Michell","6","7.0","124","1999","comedy, drama, romance","179754","1999-05-13","http://www.imdb.com/title/tt0125439/"
64 | "60","tt1470827","Mon Dec 22 00:00:00 2014","","","Monsters","Feature Film","Gareth Edwards","7","6.4","94","2010","drama, sci_fi, thriller","70186","2010-03-13","http://www.imdb.com/title/tt1470827/"
65 | "61","tt0448011","Mon Dec 22 00:00:00 2014","","","Knowing","Feature Film","Alex Proyas","5","6.2","121","2009","action, drama, mystery, sci_fi, thriller","172822","2009-03-09","http://www.imdb.com/title/tt0448011/"
66 | "62","tt1564585","Sat Dec 20 00:00:00 2014","","","Skyline","Feature Film","The Brothers Strause, The Brothers Strause","4","4.4","97","2010","action, sci_fi, thriller","72286","2010-11-11","http://www.imdb.com/title/tt1564585/"
67 | "63","tt1397280","Sat Dec 20 00:00:00 2014","","","Taken 2","Feature Film","Olivier Megaton","4","6.3","92","2012","action, thriller","205871","2012-09-07","http://www.imdb.com/title/tt1397280/"
68 | "64","tt0119116","Wed Dec 17 00:00:00 2014","","","The Fifth Element","Feature Film","Luc Besson","9","7.6","126","1997","action, adventure, sci_fi","306094","1997-05-07","http://www.imdb.com/title/tt0119116/"
69 | "65","tt1545660","Wed Dec 17 00:00:00 2014","","","Knights of Badassdom","Feature Film","Joe Lynch","2","5.7","86","2013","adventure, comedy, fantasy, horror","14022","2013-09-25","http://www.imdb.com/title/tt1545660/"
70 | "66","tt2006753","Tue Dec 16 00:00:00 2014","","","Heavenly Sword","Feature Film","Gun Ho Jang","3","5.2","85","2014","animation, action, adventure, fantasy","1082","2014-09-04","http://www.imdb.com/title/tt2006753/"
71 | "67","tt0990407","Sun Dec 14 00:00:00 2014","","","The Green Hornet","Feature Film","Michel Gondry","6","5.9","119","2011","action, comedy, crime, sci_fi, thriller","126145","2011-01-12","http://www.imdb.com/title/tt0990407/"
72 | "68","tt2015381","Fri Dec 12 00:00:00 2014","","","Guardians of the Galaxy","Feature Film","James Gunn","7","8.1","121","2014","action, adventure, sci_fi","489898","2014-07-21","http://www.imdb.com/title/tt2015381/"
73 | "69","tt0177789","Thu Dec 4 00:00:00 2014","","","Galaxy Quest","Feature Film","Dean Parisot","8","7.3","102","1999","action, adventure, comedy, sci_fi","112321","1999-12-23","http://www.imdb.com/title/tt0177789/"
74 | "70","tt1646926","Tue Dec 2 00:00:00 2014","","","Open Season 3","Feature Film","Cody Cameron","4","5.1","75","2010","animation, adventure, comedy, family","5301","2010-10-21","http://www.imdb.com/title/tt1646926/"
75 | "71","tt0090056","Mon Dec 1 00:00:00 2014","","","Spies Like Us","Feature Film","John Landis","3","6.3","102","1985","adventure, comedy","31047","1985-12-06","http://www.imdb.com/title/tt0090056/"
76 | "72","tt0287978","Mon Dec 1 00:00:00 2014","","","Daredevil","Feature Film","Mark Steven Johnson","7","5.3","103","2003","action, crime, fantasy, sci_fi, thriller","158136","2003-02-09","http://www.imdb.com/title/tt0287978/"
77 | "73","tt0110475","Thu Nov 27 00:00:00 2014","","","The Mask","Feature Film","Charles Russell","6","6.8","101","1994","comedy, crime, fantasy","232889","1994-07-29","http://www.imdb.com/title/tt0110475/"
78 | "74","tt0120762","Tue Nov 25 00:00:00 2014","","","Mulan","Feature Film","Tony Bancroft, Barry Cook","5","7.5","88","1998","animation, adventure, family, musical, war","141626","1998-06-05","http://www.imdb.com/title/tt0120762/"
79 | "75","tt1631867","Sun Nov 23 00:00:00 2014","","","Edge of Tomorrow","Feature Film","Doug Liman","8","7.9","113","2014","action, sci_fi","331078","2014-05-28","http://www.imdb.com/title/tt1631867/"
80 | "76","tt1046173","Wed Nov 19 00:00:00 2014","","","G.I. Joe: The Rise of Cobra","Feature Film","Stephen Sommers","5","5.8","118","2009","action, adventure, sci_fi, thriller","160743","2009-07-27","http://www.imdb.com/title/tt1046173/"
81 | "77","tt0328107","Mon Nov 17 00:00:00 2014","","","Man on Fire","Feature Film","Tony Scott","3","7.8","146","2004","action, drama, thriller","240961","2004-04-21","http://www.imdb.com/title/tt0328107/"
82 | "78","tt1160996","Mon Nov 17 00:00:00 2014","","","The Colony","Feature Film","Jeff Renfroe","2","5.3","95","2013","action, horror, sci_fi, thriller","31025","2013-04-26","http://www.imdb.com/title/tt1160996/"
83 | "79","tt3139072","Sun Nov 16 00:00:00 2014","","","Son of Batman","Video","Ethan Spaulding","3","6.6","74","2014","animation, action, adventure, crime, drama, fantasy, mystery","7774","2014-05-06","http://www.imdb.com/title/tt3139072/"
84 | "80","tt0093260","Sun Nov 16 00:00:00 2014","","","Innerspace","Feature Film","Joe Dante","6","6.7","120","1987","action, adventure, comedy, fantasy, sci_fi, thriller","37779","1987-07-01","http://www.imdb.com/title/tt0093260/"
85 | "81","tt2103281","Sat Nov 15 00:00:00 2014","","","Dawn of the Planet of the Apes","Feature Film","Matt Reeves","9","7.7","130","2014","action, drama, sci_fi, thriller","256589","2014-06-26","http://www.imdb.com/title/tt2103281/"
86 | "82","tt1234721","Sat Nov 15 00:00:00 2014","","","RoboCop","Feature Film","José Padilha","8","6.3","117","2014","action, sci_fi, thriller","159686","2014-01-30","http://www.imdb.com/title/tt1234721/"
87 | "83","tt0161083","Thu Nov 13 00:00:00 2014","","","What's the Worst That Could Happen?","Feature Film","Sam Weisman","5","5.4","94","2001","comedy, crime","11574","2001-06-01","http://www.imdb.com/title/tt0161083/"
88 | "84","tt0120483","Wed Nov 12 00:00:00 2014","","","The Man Who Knew Too Little","Feature Film","Jon Amiel","6","6.6","94","1997","action, comedy, crime, thriller","22083","1997-11-14","http://www.imdb.com/title/tt0120483/"
89 | "85","tt0356910","Wed Nov 12 00:00:00 2014","","","Mr. & Mrs. Smith","Feature Film","Doug Liman","9","6.5","120","2005","action, comedy, crime, romance, thriller","298836","2005-06-07","http://www.imdb.com/title/tt0356910/"
90 | "86","tt0406375","Wed Nov 12 00:00:00 2014","","","Zathura: A Space Adventure","Feature Film","Jon Favreau","5","6.1","101","2005","action, adventure, comedy, family, fantasy, sci_fi","58534","2005-11-06","http://www.imdb.com/title/tt0406375/"
91 | "87","tt1531901","Thu Nov 6 00:00:00 2014","","","Byzantium","Feature Film","Neil Jordan","6","6.5","118","2012","drama, fantasy, horror, thriller","27085","2012-09-09","http://www.imdb.com/title/tt1531901/"
92 | "88","tt1772341","Sun Nov 2 00:00:00 2014","","","Wreck-It Ralph","Feature Film","Rich Moore","6","7.8","101","2012","animation, adventure, comedy, family","228213","2012-11-01","http://www.imdb.com/title/tt1772341/"
93 | "89","tt0420238","Sat Nov 1 00:00:00 2014","","","The Tale of Despereaux","Feature Film","Sam Fell, Robert Stevenhagen","3","6.1","93","2008","adventure, animation, comedy, family, fantasy","29422","2008-12-17","http://www.imdb.com/title/tt0420238/"
94 | "90","tt1587310","Sat Nov 1 00:00:00 2014","","","Maleficent","Feature Film","Robert Stromberg","7","7.1","97","2014","action, adventure, family, fantasy, romance","208367","2014-05-28","http://www.imdb.com/title/tt1587310/"
95 | "91","tt1038686","Fri Oct 31 00:00:00 2014","","","Legion","Feature Film","Scott Stewart","3","5.2","100","2010","action, fantasy, horror","74074","2010-01-21","http://www.imdb.com/title/tt1038686/"
96 | "92","tt0401855","Wed Oct 29 00:00:00 2014","","","Underworld: Evolution","Feature Film","Len Wiseman","7","6.8","106","2006","action, fantasy, sci_fi, thriller","141157","2006-01-12","http://www.imdb.com/title/tt0401855/"
97 | "93","tt1525366","Mon Oct 27 00:00:00 2014","","","Grabbers","Feature Film","Jon Wright","8","6.2","94","2012","comedy, horror, sci_fi, thriller","11158","2012-01-23","http://www.imdb.com/title/tt1525366/"
98 | "94","tt1716777","Sun Oct 26 00:00:00 2014","","","People Like Us","Feature Film","Alex Kurtzman","4","7.1","114","2012","drama","31536","2012-06-15","http://www.imdb.com/title/tt1716777/"
99 | "95","tt0119094","Sun Oct 26 00:00:00 2014","","","Face/Off","Feature Film","John Woo","7","7.3","138","1997","action, crime, sci_fi, thriller","257723","1997-06-27","http://www.imdb.com/title/tt0119094/"
100 | "96","tt0118880","Sun Oct 26 00:00:00 2014","","","Con Air","Feature Film","Simon West","3","6.8","115","1997","action, crime, thriller","204687","1997-06-02","http://www.imdb.com/title/tt0118880/"
101 | "97","tt1840417","Sun Oct 26 00:00:00 2014","","","The Words","Feature Film","Brian Klugman, Lee Sternthal","7","7.1","102","2012","drama, mystery, romance","53357","2012-01-27","http://www.imdb.com/title/tt1840417/"
102 | "98","tt0107120","Sun Oct 26 00:00:00 2014","","","Hocus Pocus","Feature Film","Kenny Ortega","6","6.6","96","1993","comedy, family, fantasy","52428","1993-07-16","http://www.imdb.com/title/tt0107120/"
103 | "99","tt0113627","Sun Oct 26 00:00:00 2014","","","Leaving Las Vegas","Feature Film","Mike Figgis","8","7.6","111","1995","drama, romance","86548","1995-09-15","http://www.imdb.com/title/tt0113627/"
104 |
--------------------------------------------------------------------------------