如何将 MegaParsec "ParseErrorBundle" 转换为 "SourcePos" 列表和错误消息

How do I convert a MegaParsec "ParseErrorBundle" into a list of "SourcePos" and error messages

使用 MegaParsec parse function, I'm able to run a parser, and get a ParseErrorBundle if it fails.

我知道我能够漂亮地打印 ParseErrorBundle,并得到一条针对整个解析失败的错误消息,其中将包括行号和字符号,使用 errorBundlePretty

我也知道我可以使用 bundleErrorsParseErrorBundle 得到 list of ParseError's。而且我可以用 parseErrorPrettyparseErrorTextPretty.

漂亮地打印这些

我希望能够 运行 一个解析器,如果它失败了,得到一个 (SourcePos, Text) 的列表,这样我就可以知道各个错误消息以及每个错误的位置. 我想不出一个优雅的方法来做到这一点。虽然理论上我可以从 source code to errorBundlePretty 中大量抄袭,但我觉得要克服错误并使用 reachOffset 推进 PosState 不是解决这个问题的最简单方法吗? .

我能够按如下方式让它工作:

import qualified Text.Megaparsec as MP
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Text as T

annotateErrorBundle :: MP.ParseErrorBundle Text Void -> NonEmpty (MP.SourcePos, Text)
annotateErrorBundle bundle = (\e -> (errorSrcPos e, T.pack $ MP.parseErrorTextPretty e)) <$> MP.bundleErrors bundle
  where 
    initialPosState = MP.bundlePosState bundle
    errors = MP.bundleErrors bundle
    errorSrcPos e = MP.pstateSourcePos . snd $ MP.reachOffset (MP.errorOffset e) initialPosState 

我怀疑这可能效率不高,因为我每个错误都会调用一次 reachOffset。但是,实际上,错误列表可能并没有那么多,所以我不太担心。

请注意,如果您使用 megaparsec >= 7.0.0,我认为您应该使用 attachSourcePos 进行遍历。它 returns NonEmpty(ParseError, SourcePos) 对。我认为它看起来像:

import qualified Text.Megaparsec as MP
import qualified Data.Text as T
import Data.List.NonEmpty (NonEmpty (..))
import Data.Void

annotateErrorBundle :: MP.ParseErrorBundle T.Text Void -> NonEmpty (MP.SourcePos, T.Text)
annotateErrorBundle bundle
  = fmap (\(err, pos) -> (pos, T.pack . MP.parseErrorTextPretty $ err)) . fst $
    MP.attachSourcePos MP.errorOffset
                       (MP.bundleErrors bundle)
                       (MP.bundlePosState bundle)

请注意,与您建议的答案不同,attachSourcePos 通过错误包的遍历正确地将 PosState 线程化,而不是在每次 reachOffset 调用后丢弃更新的状态。因此,我相信对于大量的错误,它会更有效率。 (它还使用 reachOffsetNoLine 而不是 reachOffset,这对于某些流类型可能更有效。

如果您使用的是 megaparsec < 7.0.0,您可能想尝试从更高版本中调整 attachSourcePos 的源代码。