Here's my toy file:
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Void (Void)
type Parser = Parsec Void String
myParser :: Parser String
myParser = do
d <- digitChar
c <- letterChar
return $ replicate (read [d]) c
Now from ghci, if I type parseTest (myParser <?> "foo") "3a" I get "aaa" as expected, but if I type parseTest (myParser <?> "foo") "33a" then I get:
1:2:
|
1 | 33a
| ^
unexpected '3'
expecting letter
The error message makes sense in this simple case (I had to enter a letter instead of another digit) but when writing more complicated parsers, letterChar may appear in any number of composite parsers, so it's not clear which letterChar was the one that failed. Since I passed in a label foo for my parser, I would like it if the error message instead said something like the following:
1:2:
|
1 | 33a
| ^
error while parsing foo:
unexpected '3'
expecting letter
And more generally, as long as I give my parsers labels using <?>, I would like the whole traceback of errors to be shown, like:
error while parsing grandparent:
error while parsing parent:
unexpected '3'
expecting letter
Is there a way to do this in Megaparsec?
Megaparsec has no built in support for doing this, but you can use its custom error machinery.
We can define a custom error type that adds a context label to an existing
ParseError, together with aShowErrorComponentinstance to display it within the error message. (The weird orphanOrdinstance forParseErrorhere satisfies a technical requirement. Custom errors need anOrdinstance, butParseErrordoesn't have one, so we have to derive one if we want to include a nested ParseError in our custom error.)By itself, this doesn't do anything, but we can modify the definitions of
<?>and its non-operator equivalentlabelto make use of this custom error. Specifically, we can modify them so they call the original Megaparsec definition oflabelwhich properly handles the case where a parser fails without consuming input (by presenting the label as the "lowermost" error) and then also handle the case where a parser fails after consuming input (by wrapping the error with anErrorWithLabelcontext):This works fine for your example:
A full code example with some slightly more complicated labelling: