在Parsec中保留解析器的失败模式

时间:2013-09-03 02:55:41

标签: parsing haskell parsec

问题陈述

假设我有两个解析器pq,我将它们连接起来:

r = try p *> q

在Parsec中,其行为是:

  • 如果p在没有消耗输入的情况下失败,则r会在不消耗输入的情况下失败。
  • 如果p在消费输入后失败,则r会在不消耗输入的情况下失败。
  • 如果q在没有消费输入的情况下失败,则r在使用p后失败。
  • 如果q在使用输入后失败,则r在使用p和部分q后失败。

但是,我正在寻找的行为有点不寻常:

  • 如果p在没有消费输入的情况下失败,那么r会在不消耗输入的情况下失败。
  • 如果p在消费输入后失败,那么r应该会在不消耗输入的情况下失败。
  • 如果q在没有消费输入的情况下失败,则r 会在不消耗输入的情况下失败。
  • 如果消费输入后q失败,那么在消耗了一些输入后r会失败。

我似乎无法想到一个干净的方法来做到这一点。

原理

原因是我有一个这样的解析器:

s = (:) <$> q <*> many r

嵌入在q解析器中的r解析器需要一种方式来发出信号:无效输入(在q消耗输入但失败时发生)或者{ {1}}循环(当many不消耗任何内容并且失败时发生)。如果输入无效,则应该完全失败解析器并将问题报告给用户。如果没有更多要使用的输入,那么它应该结束q循环(不向用户报告解析器错误)。问题是输入可能以many结尾但没有更多有效p消耗,在这种情况下q将失败但不消耗任何输入。

所以我想知道是否有人有一个优雅的方法来解决这个问题?感谢。

附录:示例

q

在(假设的)解析器p = string "P" q = (++) <$> try (string "xy") <*> string "z" 上测试输入,让它按照我想要的方式工作:

  1. s(接受)
  2. xyz(接受; xyzP仍未解析)
  3. P(接受; xyzPx仍未解析; Px失败,但未消费任何输入)
  4. q(拒绝;解析器xyzPxy消耗q但失败了)
  5. xy(接受)
  6. xyzPxyz格式中,r = try p *> q实际上会拒绝上述案例#2和案例#3。当然,通过写:

    可以实现上述行为
    s

    但这不是适用于任何解析器r = (++) <$> try (string "P" *> string "xy") <*> string "z" p的通用解决方案。 (也许一般解决方案不存在?)

1 个答案:

答案 0 :(得分:1)

我相信我找到了解决方案。这不是特别好,但似乎有效。至少要开始:

{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative hiding (many, (<|>))
import Control.Monad (void)
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe
import Text.Parsec hiding (optional)
import Text.Parsec.Char
import Text.Parsec.String

rcomb ::  (Stream s m t) => ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m b
rcomb p q = ((test $ opt p *> opt q) <|> pure (Just ()))
                >>= maybe empty (\_ -> p *> q)
  where
    -- | Converts failure to @MaybeT Nothing@:
    opt = MaybeT . optional -- optional from Control.Applicative!
    -- | Tests running a parser, returns Nothing if parsers failed consuming no
    -- input, Just () otherwise.
    test = lookAhead . try . runMaybeT . void

这是你要求的r组合子。我们的想法是,我们首先在“测试”运行中执行解析器(使用lookAhead . try),如果其中任何一个在没有消耗输入的情况下失败,我们会在Nothing内将其记录为MaybeT。这是由opt完成的,它将失败转换为Nothing并将其包装到MaybeT。感谢MaybeT,如果opt p返回Nothing,则会跳过opt q

如果pq都成功,则test ..部分会返回Just ()。如果其中一个消耗输入,则整个test ..失败。这样,我们就可以区分出三种可能性:

  1. pq消耗了一些输入失败。
  2. 失败以致失败的部分不消耗输入。
  3. 成功。
  4. <|> pure (Just ())之后1.和3.结果为Just (),而2.结果为Nothing。最后,maybe部分将Nothing转换为非消耗性故障,并Just ()再次运行解析器,现在没有任何保护。这意味着1.消耗一些输入再次失败,并且3.成功。

    测试:

    samples =
        [ "xyz" -- (accept)
        , "xyzP" -- (accept; P remains unparsed)
        , "xyzPz" -- (accept; Pz remains unparsed)
        , "xyzPx" -- (accept; Px remains unparsed; q failed but did not consume any input)
        , "xyzPxy" -- (reject; parser q consumed xy but failed)
        , "xyzPxyz" -- (accept)
        ]
    
    main = do
        -- Runs a parser and then accept anything, which shows what's left in the
        -- input buffer:
        let run p x = runP ((,) <$> p <*> many anyChar) () x x
    
        let p, q :: Parser String
            p = string "P"
            q = (++) <$> try (string "xy") <*> string "z"
    
        let parser = show <$> ((:) <$> q <*> many (rcomb p q))
        mapM_ (print . run parser) samples