pointfreeco / swift-parsing

A library for turning nebulous data into well-structured data, with a focus on composition, performance, generality, and ergonomics.
https://www.pointfree.co/collections/parsing
MIT License
864 stars 77 forks source link

Parser of only optionals #346

Closed JacobHearst closed 2 months ago

JacobHearst commented 7 months ago

I'm wondering if it's possible to build a parser of only optional values using the Optionally parser. I've tried the following:

let parser = Parse {
  Optionally { Int.parser() }
  Optionally { Bool.parser() }
}

but the compiler complains about an ambiguous use of 'buildExpression' on the first Optionally statement. If I add a string literal parser inside of the first Optionally block or inside of the top-level Parse block the error goes away.

let parser = Parse {
  Optionally { ""; Int.parser() }
  Optionally { Bool.parser() }
}
// or
let parser = Parse {
  ""
  Optionally { Int.parser() }
  Optionally { Bool.parser() }
}

I get a bit lost when there are lots of generics and lots of type inference is happening but it seems to me like Swift is just having a hard time inferring the type of Input so it doesn't know which overload of buildExpression to use.

The parsers that I'm building are usually pulling values out of the middle of a string, and the string may not contain the value at all, which could also be part of the problem since what's talked about in the early videos of the Parsing collection of episodes is just parsing the start of a Substring so I'm wondering how much that influenced the final API design for this library.

As an example of the parsers I'm building, here's one I wrote to parse video resolution from a string:

private static let threeSixtyParser = ContainsTitleWord(string: "360p")
    .flatMap { if $0 { Always(Resolution.threeSixty) } else { Fail<Substring, Resolution>() } }

private static let fourEightyParser = ContainsTitleWord(pattern: #/480p|640x480|848x480/#)
    .flatMap { if $0 { Always(Resolution.fourEighty) } else { Fail<Substring, Resolution>() } }

private static let fiveFourtyParser = ContainsTitleWord(string: "540p")
    .flatMap { if $0 { Always(Resolution.fiveFourty) } else { Fail<Substring, Resolution>() } }

private static let fiveSeventySixParser = ContainsTitleWord(string: "576p")
    .flatMap { if $0 { Always(Resolution.threeSixty) } else { Fail<Substring, Resolution>() } }

private static let sevenTwentyParser = ContainsTitleWord(pattern: #/720p|1280x720|960p/#)
    .flatMap { if $0 { Always(Resolution.threeSixty) } else { Fail<Substring, Resolution>() } }

private static let tenEightyParser = ContainsTitleWord(pattern: #/1080p|1920x1080|1440p|FHD|1080i|4kto1080p/#)
    .flatMap { if $0 { Always(Resolution.threeSixty) } else { Fail<Substring, Resolution>() } }

private static let twentyOneSixtyParser = ContainsTitleWord(pattern: #/(?:2160p|3840x2160|4k[-_. ](?:UHD|HEVC|BD|H265)|(?:UHD|HEVC|BD|H265)[-_. ]4k)|(?:UHD|\[4K\])/#)
    .flatMap { if $0 { Always(Resolution.threeSixty) } else { Fail<Substring, Resolution>() } }

static let parser = OneOf {
    fourEightyParser
    fiveFourtyParser
    fiveSeventySixParser
    sevenTwentyParser
    tenEightyParser
    twentyOneSixtyParser
}
JacobHearst commented 2 months ago

Closing as stale and I was able to implement a Failable parser that met my needs