pointfreeco/swift-parsing

Easy to introduce parsing bug

JaapWijnen opened this issue · 1 comments

Was packaging a parser implemented as a closure into a struct type and somehow my parser stopped working. Found the issue which is very easy to step into, maybe there's an opportunity for a better warning somehow? Situation:

Two exactly the same parsers, once as a closure, once as a struct:

struct XMLParser: ParserPrinter {
    let parser: AnyParserPrinter<Substring.UTF8View, XML>
    
    public init(indenting: Bool = true) {
        self.parser = ParsePrint {
            Optionally {
                xmlPrologParser
                Whitespace(.vertical).printing(indenting ? "\n".utf8 : "".utf8)
            }.map(Conversions.OptionalEmptyDictionary())
            containerTagParser(indenting ? 0 : nil)
            End()
        }
        .map(.memberwise(XML.init(prolog:root:)))
        .eraseToAnyParserPrinter()
    }
    
    func print(_ output: XML, into input: inout Substring.UTF8View) throws {
        try parser.print(output, into: &input)
    }
    
    func parse(_ input: inout Substring.UTF8View) throws -> XML {
        try parser.parse(input) // missing &
    }
}

public let xmlParser: (Bool) -> AnyParserPrinter<Substring.UTF8View, XML> = { (indenting: Bool) in
    ParsePrint {
        Optionally {
            xmlPrologParser
            Whitespace(.vertical).printing(indenting ? "\n".utf8 : "".utf8)
        }.map(Conversions.OptionalEmptyDictionary())
        containerTagParser(indenting ? 0 : nil)
        End()
    }
    .map(.memberwise(XML.init(prolog:root:)))
    .eraseToAnyParserPrinter()
}

When running a test, the closure works, but the struct fails with an error

testXMLProlog(): failed: caught error: "error: unexpected input
 --> input:1:1
1 | <?xml version="1.0" encoding="utf-8"?><root></root>
  | ^ expected end of input"

This is due to not properly passing along the parse input in the struct's parse(..) implementation where I forgot the ampersand & (marked by a comment in the code above). Took me a while to find the error 😅
I couldn't think of a way too catch passing these values erroneously but thought I'd share for when others run into a similar issue or for when someone does think of a potential fix!

Hi @JaapWijnen! This is more a behavior of Swift, and we do document when to call the inout vs non-inout, though I'm sure this can be improved.

Another idea would be for the Rest() parser that gets baked into parse(nonInoutValue) to have better error messaging in this case.

Because it's not technically a bug with the library, though, I'm going to convert to a discussion, where any improvements to documentation, error messaging, or the library design can be discussed.