如何强制Parsec返回错误?

时间:2019-01-07 23:50:49

标签: haskell error-handling text-parsing parsec

我正在使用Parsec进行解析器,并尝试在解析过程中返回特定错误。

这是一个暴露我的问题的最小解析器示例:

parseA = try seq1
      <|>  seq2

seq1 = do
          manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          fail "My error message" 

seq2 = do
          manyTill anyChar (try $ string "\n- ")
          many1 anyChar

我想在第一个try $ do序列中执行一些测试,并停止解析并返回特定的错误消息。 当我不使用fail时,我会得到:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ccccc\n- ddd"

当我使用failunexpected时,解析器不会停止(由于try函数)并执行下一个do序列:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ddd"

这不是我想要的!

我考虑过使用基本的error函数来停止解析器的执行,但是我希望这样的解析函数返回“干净”错误:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Left "My error message"

您知道如何正确停止解析器并返回自定义错误吗?

1 个答案:

答案 0 :(得分:1)

如果您希望monad表现不同,那么也许您应该构建其他monad。 (N.B。我还不清楚您想要什么,但还是要继续前进。)

解决方案:使用Monad变压器堆栈

例如,要获得Parsec的fail不会捕获和忽略的类似try的函数,可以使用Except monadExcept允许您像抛出异常一样抛出错误,但是它们会被单子检测,而不是使用需要IO捕获它的实际异常机制。

首先,让我们定义单子:

import Text.Parsec
import Text.Parsec.Combinator
import Text.Parsec.Char
import Control.Monad.Trans.Except
import Control.Monad.Trans

type EscParse a = ParsecT String () (Except String) a

所以monad是EscParse,并结合了Parsec(通过转换器ParsecT)和Except的特征。

第二,让我们定义一些助手:

run :: EscParse a -> SourceName -> String -> Either String (Either ParseError a)
run op sn input = runExcept (runPT op () sn input)

escFail :: String -> EscParse a
escFail = lift. throwE

我们的runrunParse类似,但也运行了monad除外。您可能需要做一些事情来避免嵌套Either,但这是一个简单的外观更改。如果您不希望忽略该错误,那么将使用escFail

第三,我们需要使用这个新的monad实现您的解析器:

parseA :: EscParse String
parseA = try seq1 <|>  seq2

seq1 :: EscParse String
seq1 = do manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          escFail "My error message"

seq2 :: EscParse String
seq2 = do manyTill anyChar (try $ string "\n- ")
          many1 anyChar

除了间距和类型签名外,上面的内容与您所使用的匹配,只是使用escFail而不是fail