├── .editorconfig
├── .github
├── CODE_OF_CONDUCT.md
└── workflows
│ ├── ci.yml
│ ├── documentation.yml
│ ├── format.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── Parsing.playground
├── Contents.swift
└── contents.xcplayground
├── Parsing.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── swiftpm
│ └── Package.resolved
│ └── xcschemes
│ ├── Parsing.xcscheme
│ ├── swift-parsing-benchmark.xcscheme
│ └── variadics-generator.xcscheme
├── README.md
├── Sources
├── Parsing
│ ├── Builders
│ │ ├── OneOfBuilder.swift
│ │ └── ParserBuilder.swift
│ ├── Conversion.swift
│ ├── Conversions
│ │ ├── AnyConversion.swift
│ │ ├── BinaryFloatingPoint.swift
│ │ ├── ConversionMap.swift
│ │ ├── Conversions.swift
│ │ ├── Data.swift
│ │ ├── Enum.swift
│ │ ├── FixedWidthInteger.swift
│ │ ├── Identity.swift
│ │ ├── JSON.swift
│ │ ├── LosslessStringConvertible.swift
│ │ ├── Memberwise.swift
│ │ ├── ParseableFormatStyleConversion.swift
│ │ ├── RawRepresentable.swift
│ │ ├── String.swift
│ │ ├── Substring.swift
│ │ └── UTF8View.swift
│ ├── ConvertingError.swift
│ ├── CountingRange.swift
│ ├── Documentation.docc
│ │ ├── Articles
│ │ │ ├── Backtracking.md
│ │ │ ├── Design.md
│ │ │ ├── ErrorMessages.md
│ │ │ ├── GettingStarted.md
│ │ │ ├── Parsers
│ │ │ │ ├── Bool.md
│ │ │ │ ├── CaseIterable.md
│ │ │ │ ├── CharacterSet.md
│ │ │ │ ├── Float.md
│ │ │ │ ├── Int.md
│ │ │ │ ├── String.md
│ │ │ │ └── UUID.md
│ │ │ ├── Roundtripping.md
│ │ │ └── StringAbstractions.md
│ │ ├── Extensions
│ │ │ ├── Conversions.md
│ │ │ ├── OneOf.md
│ │ │ ├── Parse.md
│ │ │ └── Parser.md
│ │ └── Parsing.md
│ ├── EmptyInitializable.swift
│ ├── Internal
│ │ ├── AnyEquatable.swift
│ │ └── Deprecations.swift
│ ├── Parser.swift
│ ├── ParserPrinter.swift
│ ├── ParserPrinters
│ │ ├── Always.swift
│ │ ├── AnyParserPrinter.swift
│ │ ├── Backtracking.swift
│ │ ├── Bool.swift
│ │ ├── CaseIterableRawRepresentable.swift
│ │ ├── CharacterSet.swift
│ │ ├── Conditional.swift
│ │ ├── Consumed.swift
│ │ ├── Digits.swift
│ │ ├── End.swift
│ │ ├── Fail.swift
│ │ ├── Filter.swift
│ │ ├── First.swift
│ │ ├── Float.swift
│ │ ├── From.swift
│ │ ├── Int.swift
│ │ ├── Lazy.swift
│ │ ├── Literal.swift
│ │ ├── Many.swift
│ │ ├── Map.swift
│ │ ├── Newline.swift
│ │ ├── Not.swift
│ │ ├── OneOf.swift
│ │ ├── OneOfMany.swift
│ │ ├── Optional.swift
│ │ ├── Optionally.swift
│ │ ├── Parse.swift
│ │ ├── ParseableFormatStyle.swift
│ │ ├── Peek.swift
│ │ ├── Pipe.swift
│ │ ├── Prefix.swift
│ │ ├── PrefixThrough.swift
│ │ ├── PrefixUpTo.swift
│ │ ├── Printing.swift
│ │ ├── Pullback.swift
│ │ ├── ReplaceError.swift
│ │ ├── Rest.swift
│ │ ├── Skip.swift
│ │ ├── StartsWith.swift
│ │ ├── UUID.swift
│ │ └── Whitespace.swift
│ ├── Parsers
│ │ ├── AnyParser.swift
│ │ ├── CompactMap.swift
│ │ ├── FlatMap.swift
│ │ ├── Parsers.swift
│ │ └── Stream.swift
│ ├── ParsingError.swift
│ ├── PrependableCollection.swift
│ └── PrintingError.swift
└── swift-parsing-benchmark
│ ├── Arithmetic.swift
│ ├── BinaryData.swift
│ ├── Bool.swift
│ ├── CSV.swift
│ ├── Color.swift
│ ├── Common
│ ├── Benchmarking.swift
│ └── ParsingError.swift
│ ├── Date.swift
│ ├── HTTP.swift
│ ├── JSON.swift
│ ├── Numerics.swift
│ ├── PrefixUpTo.swift
│ ├── Race.swift
│ ├── ReadmeExample.swift
│ ├── Samples
│ ├── CSVSample.swift
│ └── XCTestLogsSample.swift
│ ├── StringAbstractions.swift
│ ├── UUID.swift
│ ├── XCTestLogs.swift
│ └── main.swift
└── Tests
└── ParsingTests
├── AlwaysTests.swift
├── AnyParserTests.swift
├── BacktrackTests.swift
├── BoolTests.swift
├── CaseIterableRawRepresentableTests.swift
├── CaseIterableTests.swift
├── CharacterSetTests.swift
├── CompactMapTests.swift
├── ConditionalTests.swift
├── ConsumedTests.swift
├── DigitsTests.swift
├── DoubleTests.swift
├── EndTests.swift
├── FailTests.swift
├── FilterTests.swift
├── FirstTests.swift
├── FlatMapTests.swift
├── FromSubstringTests.swift
├── IntTests.swift
├── LazyTests.swift
├── ManyTests.swift
├── MapTests.swift
├── NotTests.swift
├── OneOfBuilderTests.swift
├── OneOfTests.swift
├── OptionallyTests.swift
├── ParseableFormatTests.swift
├── ParserBuilderTests.swift
├── ParserTests.swift
├── ParsingErrorTests.swift
├── PeekTests.swift
├── PipeEndTests.swift
├── PipeTests.swift
├── PrefixTests.swift
├── PrefixThroughTests.swift
├── PrefixUpToTests.swift
├── RegressionTests.swift
├── ReplaceErrorTests.swift
├── RestTests.swift
├── SkipTests.swift
├── StartsWithTests.swift
├── StreamTests.swift
├── UTF8Tests.swift
├── UUIDTests.swift
├── VariadicTests.swift
└── WhitespaceTests.swift
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - '*'
10 | workflow_dispatch:
11 |
12 | jobs:
13 | macos15:
14 | name: macOS (Swift 6)
15 | runs-on: macos-15
16 | strategy:
17 | matrix:
18 | xcode:
19 | - '16.2'
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Select Xcode ${{ matrix.xcode }}
23 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
24 | - name: System
25 | run: system_profiler SPHardwareDataType
26 | - name: Build
27 | run: swift build
28 | - name: Run tests
29 | run: swift test
30 |
31 | macos14:
32 | name: macOS (Swift 5.9)
33 | runs-on: macos-14
34 | strategy:
35 | matrix:
36 | xcode:
37 | - '15.2'
38 | steps:
39 | - uses: actions/checkout@v4
40 | - name: Select Xcode ${{ matrix.xcode }}
41 | run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
42 | - name: System
43 | run: system_profiler SPHardwareDataType
44 | - name: Build
45 | run: swift build
46 | - name: Run tests
47 | run: swift test
48 |
49 | linux:
50 | name: Linux
51 | strategy:
52 | matrix:
53 | os: [ubuntu-latest]
54 | swift:
55 | - '6.0'
56 | runs-on: ${{ matrix.os }}
57 | container: swift:${{ matrix.swift }}
58 | steps:
59 | - uses: actions/checkout@v4
60 | - name: Build
61 | run: swift build
62 | - name: Run tests
63 | run: swift test
64 |
--------------------------------------------------------------------------------
/.github/workflows/format.yml:
--------------------------------------------------------------------------------
1 | name: Format
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | workflow_dispatch:
8 |
9 | concurrency:
10 | group: format-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | swift_format:
15 | name: swift-format
16 | runs-on: macos-14
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Xcode Select
20 | run: sudo xcode-select -s /Applications/Xcode_15.4.app
21 | - name: Install
22 | run: brew install swift-format
23 | - name: Format
24 | run: make format
25 | - uses: stefanzweifel/git-auto-commit-action@v4
26 | with:
27 | commit_message: Run swift-format
28 | branch: 'main'
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | release:
4 | types: [published]
5 | workflow_dispatch:
6 | jobs:
7 | project-channel:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Dump Github context
11 | env:
12 | GITHUB_CONTEXT: ${{ toJSON(github) }}
13 | run: echo "$GITHUB_CONTEXT"
14 | - name: Slack Notification on SUCCESS
15 | if: success()
16 | uses: tokorom/action-slack-incoming-webhook@main
17 | env:
18 | INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_PROJECT_CHANNEL_WEBHOOK_URL }}
19 | with:
20 | text: swift-parsing ${{ github.event.release.tag_name }} has been released.
21 | blocks: |
22 | [
23 | {
24 | "type": "header",
25 | "text": {
26 | "type": "plain_text",
27 | "text": "swift-parsing ${{ github.event.release.tag_name}}"
28 | }
29 | },
30 | {
31 | "type": "section",
32 | "text": {
33 | "type": "mrkdwn",
34 | "text": ${{ toJSON(github.event.release.body) }}
35 | }
36 | },
37 | {
38 | "type": "section",
39 | "text": {
40 | "type": "mrkdwn",
41 | "text": "${{ github.event.release.html_url }}"
42 | }
43 | }
44 | ]
45 |
46 | releases-channel:
47 | runs-on: ubuntu-latest
48 | steps:
49 | - name: Dump Github context
50 | env:
51 | GITHUB_CONTEXT: ${{ toJSON(github) }}
52 | run: echo "$GITHUB_CONTEXT"
53 | - name: Slack Notification on SUCCESS
54 | if: success()
55 | uses: tokorom/action-slack-incoming-webhook@main
56 | env:
57 | INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_RELEASES_WEBHOOK_URL }}
58 | with:
59 | text: swift-parsing ${{ github.event.release.tag_name }} has been released.
60 | blocks: |
61 | [
62 | {
63 | "type": "header",
64 | "text": {
65 | "type": "plain_text",
66 | "text": "swift-parsing ${{ github.event.release.tag_name}}"
67 | }
68 | },
69 | {
70 | "type": "section",
71 | "text": {
72 | "type": "mrkdwn",
73 | "text": ${{ toJSON(github.event.release.body) }}
74 | }
75 | },
76 | {
77 | "type": "section",
78 | "text": {
79 | "type": "mrkdwn",
80 | "text": "${{ github.event.release.html_url }}"
81 | }
82 | }
83 | ]
84 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /.swiftpm
4 | /Packages
5 | /*.xcodeproj
6 | xcuserdata/
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Point-Free, Inc.
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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PLATFORM_IOS = iOS Simulator,name=iPhone 11 Pro
2 | PLATFORM_MACOS = macOS
3 | PLATFORM_TVOS = tvOS Simulator,name=Apple TV
4 |
5 | default: test
6 |
7 | benchmarks:
8 | swift run -c release swift-parsing-benchmark
9 |
10 | test:
11 | xcodebuild test \
12 | -workspace Parsing.xcworkspace \
13 | -scheme Parsing \
14 | -destination platform="$(PLATFORM_IOS)"
15 | xcodebuild test \
16 | -workspace Parsing.xcworkspace \
17 | -scheme Parsing \
18 | -destination platform="$(PLATFORM_MACOS)"
19 | xcodebuild test \
20 | -workspace Parsing.xcworkspace \
21 | -scheme Parsing \
22 | -destination platform="$(PLATFORM_TVOS)"
23 |
24 | test-linux:
25 | docker run \
26 | --rm \
27 | -v "$(PWD):$(PWD)" \
28 | -w "$(PWD)" \
29 | swift:5.3 \
30 | bash -c 'make test-swift'
31 |
32 | test-swift:
33 | swift test \
34 | --enable-test-discovery \
35 | --parallel
36 |
37 | format:
38 | swift format --in-place --recursive \
39 | ./Package.swift ./Sources ./Tests
40 | find . -type f -name '*.md' -print0 | xargs -0 perl -pi -e 's/ +$$//'
41 |
42 | generate-variadics:
43 | swift run variadics-generator \
44 | --generate-zips \
45 | --generate-one-ofs \
46 | > Sources/Parsing/Builders/Variadics.swift
47 |
48 | .PHONY: benchmarks format generate-variadics test
49 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-argument-parser",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-argument-parser",
7 | "state" : {
8 | "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca",
9 | "version" : "0.5.0"
10 | }
11 | },
12 | {
13 | "identity" : "swift-benchmark",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/google/swift-benchmark",
16 | "state" : {
17 | "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80",
18 | "version" : "0.1.1"
19 | }
20 | },
21 | {
22 | "identity" : "swift-case-paths",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/pointfreeco/swift-case-paths",
25 | "state" : {
26 | "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81",
27 | "version" : "1.0.0"
28 | }
29 | },
30 | {
31 | "identity" : "swift-docc-plugin",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-docc-plugin",
34 | "state" : {
35 | "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
36 | "version" : "1.0.0"
37 | }
38 | },
39 | {
40 | "identity" : "xctest-dynamic-overlay",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
43 | "state" : {
44 | "revision" : "302891700c7fa3b92ebde9fe7b42933f8349f3c7",
45 | "version" : "1.0.0"
46 | }
47 | }
48 | ],
49 | "version" : 2
50 | }
51 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.9
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "swift-parsing",
7 | platforms: [
8 | .iOS(.v13),
9 | .macOS(.v10_15),
10 | .tvOS(.v13),
11 | .watchOS(.v6),
12 | ],
13 | products: [
14 | .library(
15 | name: "Parsing",
16 | targets: ["Parsing"]
17 | )
18 | ],
19 | dependencies: [
20 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
21 | .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.0.0"),
22 | .package(url: "https://github.com/google/swift-benchmark", from: "0.1.1"),
23 | ],
24 | targets: [
25 | .target(
26 | name: "Parsing",
27 | dependencies: [.product(name: "CasePaths", package: "swift-case-paths")]
28 | ),
29 | .testTarget(
30 | name: "ParsingTests",
31 | dependencies: [
32 | "Parsing"
33 | ]
34 | ),
35 | .executableTarget(
36 | name: "swift-parsing-benchmark",
37 | dependencies: [
38 | "Parsing",
39 | .product(name: "Benchmark", package: "swift-benchmark"),
40 | ]
41 | ),
42 | ]
43 | )
44 |
--------------------------------------------------------------------------------
/Parsing.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | import Parsing
2 |
3 | /*
4 | This playground builds a parser for [day 13][AoC13] of the Advent of Code 2021 challenge.
5 |
6 | [AoC13]: https://adventofcode.com/2021/day/13
7 | */
8 |
9 | /// Sample input to be parsed.
10 | let input = """
11 | 6,10
12 | 0,14
13 | 9,10
14 | 0,3
15 | 10,4
16 | 4,11
17 | 6,0
18 | 6,12
19 | 4,1
20 | 0,13
21 | 10,12
22 | 3,4
23 | 3,0
24 | 8,4
25 | 1,10
26 | 2,14
27 | 8,10
28 | 9,0
29 |
30 | fold along y=7
31 | fold along x=5
32 | """
33 |
34 | // MARK: - Models
35 |
36 | struct Dot {
37 | let x, y: Int
38 | }
39 |
40 | enum Direction: String, CaseIterable {
41 | case x, y
42 | }
43 |
44 | struct Fold {
45 | let direction: Direction
46 | let position: Int
47 | }
48 |
49 | struct Instructions {
50 | let dots: [Dot]
51 | let folds: [Fold]
52 | }
53 |
54 | // MARK: - Parsers
55 |
56 | let dot = ParsePrint(input: Substring.self, .memberwise(Dot.init)) {
57 | Digits()
58 | ","
59 | Digits()
60 | }
61 |
62 | let fold = ParsePrint(input: Substring.self, .memberwise(Fold.init)) {
63 | "fold along "
64 | Direction.parser()
65 | "="
66 | Digits()
67 | }
68 |
69 | let instructions = ParsePrint(input: Substring.self, .memberwise(Instructions.init)) {
70 | Many {
71 | dot
72 | } separator: {
73 | "\n"
74 | } terminator: {
75 | "\n\n"
76 | }
77 | Many {
78 | fold
79 | } separator: {
80 | "\n"
81 | }
82 | }
83 |
84 | // MARK: Round-trip
85 |
86 | let parsed = try instructions.parse(input)
87 | let printed = try instructions.print(parsed)
88 |
89 | input == printed
90 |
--------------------------------------------------------------------------------
/Parsing.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-argument-parser",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-argument-parser",
7 | "state" : {
8 | "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca",
9 | "version" : "0.5.0"
10 | }
11 | },
12 | {
13 | "identity" : "swift-benchmark",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/google/swift-benchmark",
16 | "state" : {
17 | "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80",
18 | "version" : "0.1.1"
19 | }
20 | },
21 | {
22 | "identity" : "swift-case-paths",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/pointfreeco/swift-case-paths",
25 | "state" : {
26 | "revision" : "642e6aab8e03e5f992d9c83e38c5be98cfad5078",
27 | "version" : "1.5.5"
28 | }
29 | },
30 | {
31 | "identity" : "swift-docc-plugin",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-docc-plugin",
34 | "state" : {
35 | "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
36 | "version" : "1.0.0"
37 | }
38 | },
39 | {
40 | "identity" : "swift-syntax",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/swiftlang/swift-syntax",
43 | "state" : {
44 | "revision" : "cb53fa1bd3219b0b23ded7dfdd3b2baff266fd25",
45 | "version" : "600.0.0"
46 | }
47 | },
48 | {
49 | "identity" : "xctest-dynamic-overlay",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
52 | "state" : {
53 | "revision" : "bc2a151366f2cd0e347274544933bc2acb00c9fe",
54 | "version" : "1.4.0"
55 | }
56 | }
57 | ],
58 | "version" : 2
59 | }
60 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/xcshareddata/xcschemes/Parsing.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/xcshareddata/xcschemes/swift-parsing-benchmark.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Parsing.xcworkspace/xcshareddata/xcschemes/variadics-generator.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversion.swift:
--------------------------------------------------------------------------------
1 | /// Declares a type that can transform an `Input` value into an `Output` value *and* transform an
2 | /// `Output` value back into an `Input` value.
3 | ///
4 | /// Useful in transforming the output of a parser-printer into some new type while preserving
5 | /// printability via ``Parser/map(_:)-18m9d``.
6 | @rethrows public protocol Conversion {
7 | // The type of values this conversion converts from.
8 | associatedtype Input
9 |
10 | // The type of values this conversion converts to.
11 | associatedtype Output
12 |
13 | /// Attempts to transform an input into an output.
14 | ///
15 | /// See ``Conversion/apply(_:)`` for the reverse process.
16 | ///
17 | /// - Parameter input: An input value.
18 | /// - Returns: A transformed output value.
19 | func apply(_ input: Input) throws -> Output
20 |
21 | /// Attempts to transform an output back into an input.
22 | ///
23 | /// The reverse process of ``Conversion/apply(_:)``.
24 | ///
25 | /// - Parameter output: An output value.
26 | /// - Returns: An "un"-transformed input value.
27 | func unapply(_ output: Output) throws -> Input
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/BinaryFloatingPoint.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Conversion where Self == Conversions.FixedWidthIntegerToBinaryFloatingPoint {
4 | /// A conversion from an `Int` to a `Double`.
5 | ///
6 | /// This conversion can be used to transform a ``ParserPrinter``'s integer output into a double
7 | /// output:
8 | ///
9 | /// ```swift
10 | /// Digits().map(.double).parse("123") // ✅ 123.0
11 | ///
12 | /// Digits().map(.double).parse("123.0") // ❌
13 | /// // error: unexpected input
14 | /// // --> input:1:4
15 | /// // 1 | 123.0
16 | /// // | ^ expected end of input
17 | /// ```
18 | @inlinable
19 | public static var double: Self { .init() }
20 | }
21 |
22 | extension Conversion where Output == Int {
23 | /// Transforms this conversion to `Int` into a conversion to `Double`.
24 | ///
25 | /// A fluent version of ``Conversion/double-swift.type.property``. Equivalent to calling
26 | /// ``map(_:)`` with ``Conversion/double-swift.type.property``:
27 | ///
28 | /// ```swift
29 | /// intConversion.double
30 | /// // =
31 | /// intConversion.map(.double)
32 | /// ```
33 | @inlinable
34 | public var double:
35 | Conversions.Map<
36 | Self, Conversions.FixedWidthIntegerToBinaryFloatingPoint
37 | >
38 | { self.map(.double) }
39 | }
40 |
41 | extension Conversions {
42 | /// A conversion from an `Int` to a `Double`.
43 | ///
44 | /// You will not typically need to interact with this type directly. Instead you will usually use
45 | /// the ``Conversion/double-swift.type.property`` operation, which constructs this type.
46 | public struct FixedWidthIntegerToBinaryFloatingPoint<
47 | Input: FixedWidthInteger, Output: BinaryFloatingPoint
48 | >: Conversion {
49 | @usableFromInline
50 | init() {}
51 |
52 | @inlinable
53 | public func apply(_ input: Input) -> Output {
54 | .init(input)
55 | }
56 |
57 | @inlinable
58 | public func unapply(_ output: Output) -> Input {
59 | .init(output)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/ConversionMap.swift:
--------------------------------------------------------------------------------
1 | extension Conversion {
2 | /// Returns a conversion that transforms the output of this conversion with a given downstream
3 | /// conversion.
4 | ///
5 | /// When provided with a conversion from this conversion's output type to some new output type,
6 | /// this method can return a new conversion from this conversion's input type to the given
7 | /// conversion's output type by calling their ``apply(_:)`` functions and ``unapply(_:)``
8 | /// functions one after the other.
9 | ///
10 | /// This method is similar to `Sequence.map`, `Optional.map`, and `Result.map` in the Swift
11 | /// standard library, as well as `Publisher.map` in the Combine framework. This method is also
12 | /// similar to the `map` functions on ``Parser`` and ``ParserPrinter``, especially
13 | /// ``Parser/map(_:)-18m9d``, which takes a conversion.
14 | ///
15 | /// - Parameter downstream: A conversion that transforms the output of this conversion into some
16 | /// new output.
17 | /// - Returns: A conversion that transforms the input of this conversion into the output of the
18 | /// given conversion.
19 | @inlinable
20 | public func map(_ downstream: C) -> Conversions.Map {
21 | .init(upstream: self, downstream: downstream)
22 | }
23 | }
24 |
25 | extension Conversions {
26 | /// A conversion that composes two conversions together by composing their
27 | /// ``Conversion/apply(_:)`` functions and ``Conversion/unapply(_:)`` functions together.
28 | ///
29 | /// You will not typically need to interact with this type directly. Instead you will usually use
30 | /// the ``Conversion/map(_:)`` operation, which constructs this type.
31 | public struct Map: Conversion
32 | where Upstream.Output == Downstream.Input {
33 | public let upstream: Upstream
34 | public let downstream: Downstream
35 |
36 | @usableFromInline
37 | init(upstream: Upstream, downstream: Downstream) {
38 | self.upstream = upstream
39 | self.downstream = downstream
40 | }
41 |
42 | @inlinable
43 | @inline(__always)
44 | public func apply(_ input: Upstream.Input) rethrows -> Downstream.Output {
45 | try self.downstream.apply(self.upstream.apply(input))
46 | }
47 |
48 | @inlinable
49 | @inline(__always)
50 | public func unapply(_ output: Downstream.Output) rethrows -> Upstream.Input {
51 | try self.upstream.unapply(self.downstream.unapply(output))
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/Conversions.swift:
--------------------------------------------------------------------------------
1 | /// A namespace for types that serve as conversions.
2 | ///
3 | /// The various operators defined as extensions on ``Conversion`` implement their functionality as
4 | /// classes or structures that extend this enumeration. For example, the ``Conversion/map(_:)``
5 | /// operator returns a ``Map`` conversion.
6 | public enum Conversions {}
7 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/Data.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Conversion where Self == Conversions.BytesToData {
4 | /// A conversion from `Substring.UTF8View` to `Data`.
5 | @inlinable
6 | public static var data: Self { .init() }
7 | }
8 |
9 | extension Conversion where Output == Substring.UTF8View {
10 | /// Transforms this conversion to `Substring.UTF8View` into a conversion to `Data`.
11 | ///
12 | /// A fluent version of ``Conversion/data-swift.type.property-8z7qz``.
13 | @inlinable
14 | public var data: Conversions.Map> {
15 | self.map(.data)
16 | }
17 | }
18 |
19 | extension Conversion where Self == Conversions.BytesToData> {
20 | /// A conversion from `ArraySlice` to `Data`.
21 | @inlinable
22 | public static var data: Self { .init() }
23 | }
24 |
25 | extension Conversion where Output == ArraySlice {
26 | /// Transforms this conversion to `ArraySlice` into a conversion to `Data`.
27 | ///
28 | /// A fluent version of ``Conversion/data-swift.type.property-7g9sj``.
29 | @inlinable
30 | public var data: Conversions.Map> {
31 | self.map(.data)
32 | }
33 | }
34 |
35 | extension Conversions {
36 | /// A conversion from a ``PrependableCollection`` of UTF-8 bytes to `Data`.
37 | ///
38 | /// You will not typically need to interact with this type directly. Instead you will usually use
39 | /// the ``Conversion/data-swift.type.property-8z7qz`` and
40 | /// ``Conversion/data-swift.type.property-7g9sj`` operations, which constructs this type.
41 | public struct BytesToData: Conversion where Input.Element == UInt8 {
42 | @usableFromInline
43 | init() {}
44 |
45 | @inlinable
46 | public func apply(_ input: Input) -> Data {
47 | .init(input)
48 | }
49 |
50 | @inlinable
51 | public func unapply(_ output: Data) -> Input {
52 | .init(output)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/Enum.swift:
--------------------------------------------------------------------------------
1 | import CasePaths
2 |
3 | extension Conversion {
4 | /// Converts the associated values of an enum case into the case, and an enum case into its
5 | /// associated values.
6 | ///
7 | /// Useful for transforming the output of a ``ParserPrinter`` into an enum:
8 | ///
9 | /// ```swift
10 | /// enum Expression {
11 | /// case add(Int, Int)
12 | /// ...
13 | /// }
14 | ///
15 | /// let add = ParsePrint(.case(Expression.add)) {
16 | /// Int.parser()
17 | /// "+"
18 | /// Int.parser()
19 | /// }
20 | /// try add.parse("1+2") // Expression.add(1, 2)
21 | /// ```
22 | ///
23 | /// To transform the output of a ``ParserPrinter`` into a struct, see ``memberwise(_:)``.
24 | ///
25 | /// - Parameter embed: An embed function where `Values` directly maps to the memory
26 | /// layout of `Enum`, for example the internal, default initializer that is automatically
27 | /// synthesized for structs.
28 | /// - Returns: A conversion that can embed the associated values of an enum case into the case,
29 | /// and extract the associated values from the case.
30 | @inlinable
31 | public static func `case`(
32 | _ initializer: @escaping (Values) -> Enum
33 | ) -> Self where Self == CasePath {
34 | /initializer
35 | }
36 |
37 | @inlinable
38 | public static func `case`(
39 | _ initializer: Enum
40 | ) -> Self where Self == CasePath {
41 | /initializer
42 | }
43 | }
44 |
45 | extension CasePath: Conversion {
46 | @inlinable
47 | public func apply(_ input: Value) -> Root {
48 | self.embed(input)
49 | }
50 |
51 | @inlinable
52 | public func unapply(_ output: Root) throws -> Value {
53 | guard let value = self.extract(from: output)
54 | else {
55 | throw ConvertingError(
56 | """
57 | case: Failed to extract \(Value.self) from \(output).
58 | """
59 | )
60 | }
61 | return value
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/FixedWidthInteger.swift:
--------------------------------------------------------------------------------
1 | extension Conversion where Self == Conversions.BinaryFloatingPointToFixedWidthInteger {
2 | /// A conversion from a `Double` to an `Int`.
3 | ///
4 | /// This conversion can be used to transform a ``ParserPrinter``'s double output into an integer
5 | /// output, rounding toward zero.
6 | ///
7 | /// ```swift
8 | /// Double.parser().map(.int).parse("123.45") // 123
9 | /// ```
10 | @inlinable
11 | public static var int: Self { .init() }
12 | }
13 |
14 | extension Conversion where Output == Double {
15 | /// Transforms this conversion to `Double` into a conversion to `Int`.
16 | ///
17 | /// A fluent version of ``Conversion/int-swift.type.property``. Equivalent to calling ``map(_:)``
18 | /// with ``Conversion/int-swift.type.property``:
19 | ///
20 | /// ```swift
21 | /// doubleConversion.int
22 | /// // =
23 | /// doubleConversion.map(.int)
24 | /// ```
25 | @inlinable
26 | public var int:
27 | Conversions.Map<
28 | Self, Conversions.BinaryFloatingPointToFixedWidthInteger
29 | >
30 | { self.map(.int) }
31 | }
32 |
33 | extension Conversions {
34 | /// A conversion from a `Double` to an `Int`.
35 | ///
36 | /// You will not typically need to interact with this type directly. Instead you will usually use
37 | /// the ``Conversion/int-swift.type.property`` operation, which constructs this type.
38 | public struct BinaryFloatingPointToFixedWidthInteger<
39 | Input: BinaryFloatingPoint, Output: FixedWidthInteger
40 | >: Conversion {
41 | @usableFromInline
42 | init() {}
43 |
44 | @inlinable
45 | public func apply(_ input: Input) -> Output {
46 | .init(input)
47 | }
48 |
49 | @inlinable
50 | public func unapply(_ output: Output) -> Input {
51 | .init(output)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/Identity.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Conversions {
4 | public struct Identity: Conversion {
5 | @inlinable
6 | public init() {}
7 |
8 | @inlinable
9 | @inline(__always)
10 | public func apply(_ input: Value) -> Value {
11 | input
12 | }
13 |
14 | @inlinable
15 | @inline(__always)
16 | public func unapply(_ output: Value) -> Value {
17 | output
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/JSON.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Conversion {
4 | /// A conversion from `Data` to the given codable type.
5 | ///
6 | /// See ``json(_:decoder:encoder:)-swift.method`` for a fluent version of this interface that
7 | /// transforms an existing conversion.
8 | ///
9 | /// - Parameters:
10 | /// - type: A type that conforms to `Codable`.
11 | /// - decoder: An optional JSON decoder that handles decoding.
12 | /// - encoder: An optional JSON encoder that handles encoding.
13 | /// - Returns: A conversion from `Data` to the given codable type.
14 | @inlinable
15 | public static func json(
16 | _ type: Value.Type,
17 | decoder: JSONDecoder = .init(),
18 | encoder: JSONEncoder = .init()
19 | ) -> Self where Self == Conversions.JSON {
20 | .init(type, decoder: decoder, encoder: encoder)
21 | }
22 |
23 | /// Transforms this conversion to `Data` into a conversion to the given codable type.
24 | ///
25 | /// A fluent version of ``Conversion/json(_:decoder:encoder:)-swift.type.method``. Equivalent to
26 | /// calling ``map(_:)`` with ``Conversion/json(_:decoder:encoder:)-swift.type.method``:
27 | ///
28 | /// ```swift
29 | /// Parse(.data.json(User.self))
30 | /// // =
31 | /// Parse(.data.map(.json(User.self)))
32 | /// ```
33 | ///
34 | /// - Parameters:
35 | /// - type: A type that conforms to `Codable`.
36 | /// - decoder: An optional JSON decoder that handles decoding.
37 | /// - encoder: An optional JSON encoder that handles encoding.
38 | /// - Returns: A conversion from this conversion's input to the given codable type.
39 | @inlinable
40 | public func json(
41 | _ type: Value.Type,
42 | decoder: JSONDecoder = .init(),
43 | encoder: JSONEncoder = .init()
44 | ) -> Conversions.Map> {
45 | self.map(.json(type, decoder: decoder, encoder: encoder))
46 | }
47 | }
48 |
49 | extension Conversions {
50 | /// A conversion from `Data` to some codable type.
51 | ///
52 | /// You will not typically need to interact with this type directly. Instead you will usually use
53 | /// the ``Conversion/json(_:decoder:encoder:)-swift.type.method`` operation, which constructs this
54 | /// type.
55 | public struct JSON: Conversion {
56 | @usableFromInline
57 | let decoder: JSONDecoder
58 |
59 | @usableFromInline
60 | let encoder: JSONEncoder
61 |
62 | @inlinable
63 | public init(
64 | _ type: Value.Type,
65 | decoder: JSONDecoder = .init(),
66 | encoder: JSONEncoder = .init()
67 | ) {
68 | self.decoder = decoder
69 | self.encoder = encoder
70 | }
71 |
72 | @inlinable
73 | public func apply(_ input: Data) throws -> Value {
74 | try self.decoder.decode(Value.self, from: input)
75 | }
76 |
77 | @inlinable
78 | public func unapply(_ output: Value) throws -> Data {
79 | try self.encoder.encode(output)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/LosslessStringConvertible.swift:
--------------------------------------------------------------------------------
1 | extension Conversion {
2 | /// A conversion from a string to a lossless string-convertible type.
3 | ///
4 | /// See ``lossless(_:)-swift.method`` for a fluent version of this interface that transforms an
5 | /// existing conversion.
6 | ///
7 | /// - Parameter type: A type that conforms to `LosslessStringConvertible`.
8 | /// - Returns: A conversion from a string to the given type.
9 | @inlinable
10 | public static func lossless(
11 | _ type: NewOutput.Type
12 | ) -> Self where Self == Conversions.FromLosslessString {
13 | .init()
14 | }
15 |
16 | /// Transforms this conversion to a string into a conversion to the given lossless
17 | /// string-convertible type.
18 | ///
19 | /// A fluent version of ``Conversion/lossless(_:)-swift.type.method``. Equivalent to calling
20 | /// ``map(_:)`` with ``Conversion/lossless(_:)-swift.type.method``:
21 | ///
22 | /// ```swift
23 | /// stringConversion.lossless(NewOutput.self)
24 | /// // =
25 | /// stringConversion.map(.lossless(NewOutput.self))
26 | /// ```
27 | ///
28 | /// - Parameter type: A type that conforms to `LosslessStringConvertible`.
29 | /// - Returns: A conversion from a string to the given type.
30 | @inlinable
31 | public func lossless(
32 | _ type: NewOutput.Type
33 | ) -> Conversions.Map> {
34 | self.map(.lossless(NewOutput.self))
35 | }
36 | }
37 |
38 | extension Conversions {
39 | /// A conversion from a string to a lossless string-convertible type.
40 | ///
41 | /// You will not typically need to interact with this type directly. Instead you will usually use
42 | /// the ``Conversion/lossless(_:)-swift.type.method`` operation, which constructs this type.
43 | public struct FromLosslessString: Conversion {
44 | @usableFromInline
45 | init() {}
46 |
47 | @inlinable
48 | public func apply(_ input: String) throws -> Output {
49 | guard let output = Output(input)
50 | else {
51 | throw ConvertingError(
52 | """
53 | lossless: Failed to convert \(input.debugDescription) to \(Output.self).
54 | """
55 | )
56 | }
57 |
58 | return output
59 | }
60 |
61 | @inlinable
62 | public func unapply(_ output: Output) -> String {
63 | output.description
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/Parsing/Conversions/ParseableFormatStyleConversion.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
2 | import Foundation
3 |
4 | @available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
5 | extension Conversion {
6 | /// A conversion that wraps a parseable format style.
7 | ///
8 | /// This conversion forwards its ``apply(_:)`` and ``unapply(_:)`` methods to the underlying
9 | /// `ParseableFormatStyle` by invoking its parse strategy's `parse` method and its `format`
10 | /// method.
11 | ///
12 | /// See ``formatted(_:)-swift.method`` for a fluent version of this interface that transforms an
13 | /// existing conversion.
14 | ///
15 | /// - Parameter style: A parseable format style.
16 | /// - Returns: A conversion from a string to the given type.
17 | public static func formatted