我如何强制 Parsec return 出错?
How can I force Parsec to return an error?
我正在使用 Parsec 制作一个解析器,我尝试 return 解析过程中的特定错误。
这是一个暴露我的问题的最小解析器示例:
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
序列中执行一些测试并停止解析和 return 特定的错误消息。
当我不使用 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
函数来停止我的解析器的执行,但我想要一个 "clean" 错误 return 像这样的解析函数:
ghci> parse parseA "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Left "My error message"
您知道如何正确停止解析器和return自定义错误吗?
如果您希望 monad 表现不同,那么也许您应该构建一个不同的 monad。 (N.B。我不是很清楚你想要什么,但无论如何都要前进)。
解决方案:使用 Monad Transformer Stack
例如,要获得不被 Parsec 的 try
捕获和忽略的类似 fail
的函数,您可以使用 Except monad。 Except
允许您像抛出异常一样抛出错误,但它们是单次探测的,而不是使用需要 IO 来捕获它的实际异常机制。
首先,让我们定义我们的 monad:
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
但也运行 except 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
。
我正在使用 Parsec 制作一个解析器,我尝试 return 解析过程中的特定错误。
这是一个暴露我的问题的最小解析器示例:
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
序列中执行一些测试并停止解析和 return 特定的错误消息。
当我不使用 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
函数来停止我的解析器的执行,但我想要一个 "clean" 错误 return 像这样的解析函数:
ghci> parse parseA "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Left "My error message"
您知道如何正确停止解析器和return自定义错误吗?
如果您希望 monad 表现不同,那么也许您应该构建一个不同的 monad。 (N.B。我不是很清楚你想要什么,但无论如何都要前进)。
解决方案:使用 Monad Transformer Stack
例如,要获得不被 Parsec 的 try
捕获和忽略的类似 fail
的函数,您可以使用 Except monad。 Except
允许您像抛出异常一样抛出错误,但它们是单次探测的,而不是使用需要 IO 来捕获它的实际异常机制。
首先,让我们定义我们的 monad:
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
但也运行 except 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
。