3

使用MegaParsecparse函数,我可以运行一个解析器,ParseErrorBundle如果它失败了就得到一个。

我知道我可以漂亮地打印ParseErrorBundle,并获得整个解析失败的错误消息,其中包括行号和字符号,使用errorBundlePretty.

我也知道我可以从 a中获取's列表ParseErrorParseErrorBundle,使用bundleErrors. 而且我可以用parseErrorPretty或很好地打印这些parseErrorTextPretty

我希望能够运行一个解析器,如果它失败了,得到一个 列表(SourcePos, Text),这样我就可以知道各个错误消息以及每个错误的位置。我想不出一种优雅的方式来做到这一点。虽然理论上我可以相当多地从源代码errorBundlePretty.reachOffsetPosState

4

2 回答 2

2

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

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每次错误都会调用一次。但是,在实践中,错误列表可能并没有那么大,所以我并不太担心。

于 2021-11-27T16:29:11.043 回答
2

请注意,如果您使用megaparsec >= 7.0.0,我认为您应该attachSourcePos用于遍历。它返回一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从更高版本调整源代码。

于 2021-11-27T22:05:49.653 回答