代码之家  ›  专栏  ›  技术社区  ›  JeanJouX

如何强制Parsec返回错误?

  •  1
  • JeanJouX  · 技术社区  · 6 年前

    我正在用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"
    

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

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

    这不是我想要的!

    我考虑过使用 error 函数来停止解析器的执行,但我希望解析函数返回“clean”错误,如下所示:

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

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

    1 回复  |  直到 6 年前
        1
  •  1
  •   Thomas M. DuBuisson    6 年前

    如果您希望Monad的行为有所不同,那么也许您应该构建一个不同的Monad。(注意,我不完全清楚你想要什么,但还是要继续前进)。

    解决方案:使用单端变压器组

    例如,要获得 fail -类似于未被parsec捕获和忽略的函数 try 你可以用一个 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
    

    所以这个单子是 EscParse 并结合了Parsec的特性(通过变压器 ParsecT ) 除了 .

    其次,让我们定义一些帮助者:

    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之外的。你可能想做点什么来避免嵌套的,但这是一个简单的表面变化。 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
    

    除了空格和类型签名之外,上面的内容与您拥有的内容匹配,但使用 埃斯科尔 而不是 失败 .