我正在使用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"
当我使用fail
或unexpected
时,解析器不会停止(由于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"
您知道如何正确停止解析器并返回自定义错误吗?
答案 0 :(得分:1)
如果您希望monad表现不同,那么也许您应该构建其他monad。 (N.B。我还不清楚您想要什么,但还是要继续前进。)
解决方案:使用Monad变压器堆栈
例如,要获得Parsec的fail
不会捕获和忽略的类似try
的函数,可以使用Except monad。 Except
允许您像抛出异常一样抛出错误,但是它们会被单子检测,而不是使用需要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
我们的run
与runParse
类似,但也运行了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
。