Skip to content

Commit

Permalink
Swift 5.8 Support (#289)
Browse files Browse the repository at this point in the history
* Swift 5.8 Support

Co-authored-by: Jaap Wijnen <[email protected]>

* wip

* wip

* wip

* wip

* wip

* omit some benchmarks from swift 5.7

* wip

* FromSubstring ParseBuilder needs Substring as Input (#292)

* Downstream ParseBuilder should use Downstream.Input as Input (#291)

* Downstream ParseBuilder should use Downstream.Input as Input

The ParseBuilder is for Downstream so it should use it’s input. Or Upstream.Output as pipe runs the downstream parser on the output of the upstream parser. Which is why Upstream.Output == Downstream.Input.

With the 5.8 changes pipe uses the wrong input here. This only causes issues, if Upstream and Downstream have a different Input type.

* Pipe - Use Upstream.Output instead of Downstream.Input

* wip

* wip

* wip

* wip

* wip

* fix

* wip

---------

Co-authored-by: Jaap Wijnen <[email protected]>
Co-authored-by: Brandon Williams <[email protected]>
Co-authored-by: Kai Oelfke <[email protected]>
  • Loading branch information
4 people authored Mar 31, 2023
1 parent 5ac4b09 commit c6e2241
Show file tree
Hide file tree
Showing 77 changed files with 2,089 additions and 12,766 deletions.
21 changes: 6 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ on:

jobs:
macos_tests:
runs-on: macos-11
runs-on: macos-12
strategy:
matrix:
xcode:
- "13.2.1" # Swift 5.5
- "14.2" # Swift 5.7.2
command:
- test
- benchmarks
# - benchmarks
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: System
Expand All @@ -31,22 +31,13 @@ jobs:
ubuntu_tests:
strategy:
matrix:
os: [ubuntu-18.04, ubuntu-20.04]
os: [ubuntu-20.04]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build
run: swift build
- name: Run tests
run: swift test

windows_tests:
runs-on: windows-2019

steps:
- uses: actions/checkout@v2
- uses: MaxDesiatov/swift-windows-action@v1
with:
swift-version: "5.5.1"
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PLATFORM_IOS = iOS Simulator,name=iPhone 11 Pro
PLATFORM_MACOS = macOS
PLATFORM_TVOS = tvOS Simulator,name=Apple TV 4K (at 1080p)
PLATFORM_TVOS = tvOS Simulator,name=Apple TV

default: test

Expand Down
18 changes: 4 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.5
// swift-tools-version:5.7

import PackageDescription

Expand All @@ -18,9 +18,10 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.5.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.8.0"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.2.1"),
.package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.1"),
.package(url: "https://github.com/google/swift-benchmark", from: "0.1.1"),
],
targets: [
.target(
Expand All @@ -37,19 +38,8 @@ let package = Package(
name: "swift-parsing-benchmark",
dependencies: [
"Parsing",
.product(name: "Benchmark", package: "Benchmark"),
.product(name: "Benchmark", package: "swift-benchmark"),
]
),
.executableTarget(
name: "variadics-generator",
dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]
),
]
)

#if swift(>=5.6)
// Add the documentation compiler plugin if possible
package.dependencies.append(
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0")
)
#endif
99 changes: 52 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,26 @@ It would be more straightforward and efficient to instead describe how to consum
We can start by describing what it means to parse a single row, first by parsing an integer off the front of the string, and then parsing a comma. We can do this by using the `Parse` type, which acts as an entry point into describing a list of parsers that you want to run one after the other to consume from an input:

```swift
let user = Parse {
let user = Parse(input: Substring.self) {
Int.parser()
","
}
```

Note that this parsing library is quite general, allowing one to parse _any_ kind of input into
_any_ kind of output. For this reason we sometimes need to specify the exact input type the parser
can process, in this case substrings.

Already this can consume the beginning of the input:

```swift
try user.parse("1,") // 1
try user.parse("1,") // 1
```

Next we want to take everything up until the next comma for the user's name, and then consume the comma:

```swift
let user = Parse {
let user = Parse(input: Substring.self) {
Int.parser()
","
Prefix { $0 != "," }
Expand All @@ -129,7 +133,7 @@ let user = Parse {
And then we want to take the boolean at the end of the row for the user's admin status:

```swift
let user = Parse {
let user = Parse(input: Substring.self) {
Int.parser()
","
Prefix { $0 != "," }
Expand All @@ -141,7 +145,7 @@ let user = Parse {
Currently this will parse a tuple `(Int, Substring, Bool)` from the input, and we can `.map` on that to turn it into a `User`:

```swift
let user = Parse {
let user = Parse(input: Substring.self) {
Int.parser()
","
Prefix { $0 != "," }
Expand All @@ -154,7 +158,7 @@ let user = Parse {
To make the data we are parsing to more prominent, we can instead pass the transform closure as the first argument to `Parse`:

```swift
let user = Parse {
let user = Parse(input: Substring.self) {
User(id: $0, name: String($1), isAdmin: $2)
} with: {
Int.parser()
Expand All @@ -168,7 +172,7 @@ let user = Parse {
Or we can pass the `User` initializer to `Parse` in a point-free style by transforming the `Prefix` parser's output from a `Substring` to ` String` first:

```swift
let user = Parse(User.init(id:name:isAdmin:)) {
let user = Parse(input: Substring.self, User.init(id:name:isAdmin:)) {
Int.parser()
","
Prefix { $0 != "," }.map(String.init)
Expand Down Expand Up @@ -305,46 +309,47 @@ Apple M1 Pro (10 cores, 8 performance and 2 efficiency)
name time std iterations
----------------------------------------------------------------------------------
Arithmetic.Parser 8042.000 ns ± 5.91 % 174657
BinaryData.Parser 42.000 ns ± 56.81 % 1000000
Bool.Bool.init 41.000 ns ± 60.69 % 1000000
Bool.Bool.parser 42.000 ns ± 57.28 % 1000000
Bool.Scanner.scanBool 1041.000 ns ± 25.98 % 1000000
Color.Parser 209.000 ns ± 13.68 % 1000000
CSV.Parser 4047750.000 ns ± 1.18 % 349
CSV.Ad hoc mutating methods 898604.000 ns ± 1.49 % 1596
Date.Parser 6416.000 ns ± 2.56 % 219218
Date.DateFormatter 25625.000 ns ± 2.19 % 54110
Date.ISO8601DateFormatter 35125.000 ns ± 1.71 % 39758
HTTP.HTTP 9709.000 ns ± 3.81 % 138868
JSON.Parser 32292.000 ns ± 3.18 % 41890
JSON.JSONSerialization 1833.000 ns ± 8.58 % 764057
Numerics.Int.init 41.000 ns ± 84.54 % 1000000
Numerics.Int.parser 42.000 ns ± 72.17 % 1000000
Numerics.Scanner.scanInt 125.000 ns ± 20.26 % 1000000
Numerics.Comma separated: Int.parser 8096459.000 ns ± 0.44 % 173
Numerics.Comma separated: Scanner.scanInt 49178770.500 ns ± 0.24 % 28
Numerics.Comma separated: String.split 14922583.500 ns ± 0.67 % 94
Numerics.Double.init 42.000 ns ± 72.61 % 1000000
Numerics.Double.parser 125.000 ns ± 58.57 % 1000000
Numerics.Scanner.scanDouble 167.000 ns ± 18.84 % 1000000
Numerics.Comma separated: Double.parser 11313395.500 ns ± 0.96 % 124
Numerics.Comma separated: Scanner.scanDouble 50431521.000 ns ± 0.19 % 28
Numerics.Comma separated: String.split 18744125.000 ns ± 0.46 % 75
PrefixUpTo.Parser: Substring 249958.000 ns ± 0.88 % 5595
PrefixUpTo.Parser: UTF8 13250.000 ns ± 2.96 % 105812
PrefixUpTo.String.range(of:) 43084.000 ns ± 1.57 % 32439
PrefixUpTo.Scanner.scanUpToString 47500.000 ns ± 1.27 % 29444
Race.Parser 34417.000 ns ± 2.73 % 40502
README Example.Parser: Substring 4000.000 ns ± 3.79 % 347868
README Example.Parser: UTF8 1125.000 ns ± 7.92 % 1000000
README Example.Ad hoc 3542.000 ns ± 4.13 % 394248
README Example.Scanner 14292.000 ns ± 2.82 % 97922
String Abstractions.Substring 934167.000 ns ± 0.60 % 1505
String Abstractions.UTF8 158750.000 ns ± 1.36 % 8816
UUID.UUID.init 209.000 ns ± 15.02 % 1000000
UUID.UUID.parser 208.000 ns ± 24.17 % 1000000
Xcode Logs.Parser 3768437.500 ns ± 0.56 % 372
Arithmetic.Parser 6166.000 ns ± 10.73 % 228888
BinaryData.Parser 208.000 ns ± 39.64 % 1000000
Bool.Bool.init 41.000 ns ± 84.71 % 1000000
Bool.Bool.parser 42.000 ns ± 87.86 % 1000000
Bool.Scanner.scanBool 916.000 ns ± 30.55 % 1000000
Color.Parser 208.000 ns ± 28.34 % 1000000
CSV.Parser 3675250.000 ns ± 1.16 % 380
CSV.Ad hoc mutating methods 651333.000 ns ± 1.00 % 2143
Date.Parser 5833.000 ns ± 5.65 % 238924
Date.DateFormatter 23542.000 ns ± 5.50 % 58766
Date.ISO8601DateFormatter 29041.000 ns ± 3.31 % 48028
HTTP.HTTP 10250.000 ns ± 6.24 % 135657
JSON.Parser 38167.000 ns ± 3.26 % 36423
JSON.JSONSerialization 1792.000 ns ± 54.14 % 753770
Numerics.Int.init 0.000 ns ± inf % 1000000
Numerics.Int.parser 83.000 ns ± 67.28 % 1000000
Numerics.Scanner.scanInt 125.000 ns ± 38.65 % 1000000
Numerics.Digits 83.000 ns ± 65.03 % 1000000
Numerics.Comma separated: Int.parser 15364583.000 ns ± 0.63 % 91
Numerics.Comma separated: Scanner.scanInt 50654458.500 ns ± 0.30 % 28
Numerics.Comma separated: String.split 15452542.000 ns ± 1.30 % 90
Numerics.Double.init 42.000 ns ± 152.57 % 1000000
Numerics.Double.parser 166.000 ns ± 45.23 % 1000000
Numerics.Scanner.scanDouble 167.000 ns ± 42.36 % 1000000
Numerics.Comma separated: Double.parser 18539833.000 ns ± 0.57 % 75
Numerics.Comma separated: Scanner.scanDouble 55239167.000 ns ± 0.46 % 25
Numerics.Comma separated: String.split 17636000.000 ns ± 1.34 % 78
PrefixUpTo.Parser: Substring 182041.000 ns ± 1.78 % 7643
PrefixUpTo.Parser: UTF8 40417.000 ns ± 2.71 % 34379
PrefixUpTo.String.range(of:) 49792.000 ns ± 2.70 % 27891
PrefixUpTo.Scanner.scanUpToString 53959.000 ns ± 3.87 % 25745
Race.Parser 59583.000 ns ± 2.78 % 23333
README Example.Parser: Substring 2834.000 ns ± 12.87 % 488264
README Example.Parser: UTF8 1291.000 ns ± 22.65 % 1000000
README Example.Ad hoc 2459.000 ns ± 20.61 % 561930
README Example.Scanner 12084.000 ns ± 5.53 % 115388
String Abstractions.Substring 472083.500 ns ± 1.38 % 2962
String Abstractions.UTF8 196041.000 ns ± 3.38 % 7059
UUID.UUID.init 208.000 ns ± 43.60 % 1000000
UUID.UUID.parser 167.000 ns ± 42.00 % 1000000
Xcode Logs.Parser 4511625.500 ns ± 0.58 % 226
```

## Documentation
Expand Down
72 changes: 56 additions & 16 deletions Sources/Parsing/Builders/OneOfBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/// try currency.parse("$100") // (.usd, 100)
/// ```
@resultBuilder
public enum OneOfBuilder {
public enum OneOfBuilder<Input, Output> {
/// Provides support for `for`-`in` loops in ``OneOfBuilder`` blocks.
///
/// Useful for building up a parser from a dynamic source, like for a case-iterable enum:
Expand All @@ -36,23 +36,23 @@ public enum OneOfBuilder {
/// }
/// ```
@inlinable
public static func buildArray<P>(_ parsers: [P]) -> Parsers.OneOfMany<P> {
public static func buildArray<P>(_ parsers: [P]) -> Parsers.OneOfMany<P>
where P.Input == Input, P.Output == Output {
.init(parsers)
}

@inlinable
static public func buildBlock() -> Fail<Input, Output> {
Fail()
}

/// Provides support for specifying a parser in ``OneOfBuilder`` blocks.
@inlinable
static public func buildBlock<P: Parser>(_ parser: P) -> P {
static public func buildBlock<P: Parser>(_ parser: P) -> P
where P.Input == Input, P.Output == Output {
parser
}

#if swift(<5.7)
@inlinable
static public func buildBlock<P0: Parser, P1: Parser>(_ p0: P0, _ p1: P1) -> OneOf2<P0, P1> {
OneOf2(p0, p1)
}
#endif

/// Provides support for `if`-`else` statements in ``OneOfBuilder`` blocks, producing a
/// conditional parser for the `if` branch.
///
Expand All @@ -68,7 +68,13 @@ public enum OneOfBuilder {
@inlinable
public static func buildEither<TrueParser, FalseParser>(
first parser: TrueParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
) -> Parsers.Conditional<TrueParser, FalseParser>
where
TrueParser.Input == Input,
TrueParser.Output == Output,
FalseParser.Input == Input,
FalseParser.Output == Output
{
.first(parser)
}

Expand All @@ -87,10 +93,22 @@ public enum OneOfBuilder {
@inlinable
public static func buildEither<TrueParser, FalseParser>(
second parser: FalseParser
) -> Parsers.Conditional<TrueParser, FalseParser> {
) -> Parsers.Conditional<TrueParser, FalseParser>
where
TrueParser.Input == Input,
TrueParser.Output == Output,
FalseParser.Input == Input,
FalseParser.Output == Output
{
.second(parser)
}

@inlinable
public static func buildExpression<P: Parser>(_ parser: P) -> P
where P.Input == Input, P.Output == Output {
parser
}

/// Provides support for `if` statements in ``OneOfBuilder`` blocks, producing an optional parser.
///
/// ```swift
Expand All @@ -106,24 +124,27 @@ public enum OneOfBuilder {
/// }
/// ```
@inlinable
public static func buildIf<P>(_ parser: P?) -> OptionalOneOf<P> {
public static func buildIf<P>(_ parser: P?) -> OptionalOneOf<P> where P.Input == Input {
.init(wrapped: parser)
}

/// Provides support for `if #available` statements in ``OneOfBuilder`` blocks, producing an
/// optional parser.
@inlinable
public static func buildLimitedAvailability<P>(_ parser: P?) -> OptionalOneOf<P> {
public static func buildLimitedAvailability<P>(_ parser: P?) -> OptionalOneOf<P>
where P.Input == Input, P.Output == Output {
.init(wrapped: parser)
}

@inlinable
public static func buildPartialBlock<P0: Parser>(first: P0) -> P0 {
public static func buildPartialBlock<P: Parser>(first: P) -> P
where P.Input == Input, P.Output == Output {
first
}

@inlinable
public static func buildPartialBlock<P0, P1>(accumulated: P0, next: P1) -> OneOf2<P0, P1> {
public static func buildPartialBlock<P0, P1>(accumulated: P0, next: P1) -> OneOf2<P0, P1>
where P0.Input == Input, P0.Output == Output, P1.Input == Input, P1.Output == Output {
.init(accumulated, next)
}

Expand Down Expand Up @@ -211,3 +232,22 @@ extension OneOfBuilder.OptionalOneOf: ParserPrinter where Wrapped: ParserPrinter
try wrapped.print(output, into: &input)
}
}

extension OneOfBuilder where Input == Substring {
@_disfavoredOverload
public static func buildExpression<P: Parser>(_ parser: P)
-> From<Conversions.SubstringToUTF8View, Substring.UTF8View, P>
where P.Input == Substring.UTF8View {
From(.utf8) {
parser
}
}
}

extension OneOfBuilder where Input == Substring.UTF8View {
@_disfavoredOverload
public static func buildExpression<P: Parser>(_ parser: P) -> P
where P.Input == Substring.UTF8View {
parser
}
}
Loading

0 comments on commit c6e2241

Please sign in to comment.