在Parsec中,如果第一个解析器消耗了一些输入,我该如何运行第二个解析器?

时间:2017-05-30 05:09:32

标签: haskell parsec

我需要像p1 << p2这样的组合器,但如果p2成功并且消耗了一些输入p1应该只运行

如果p1成功而没有消费输入,则p2不应该运行。

如果p1失败,那么p2也会被忽略?

总体结果是r1的结果

3 个答案:

答案 0 :(得分:2)

Parsec原语在消耗了一些输入之后成功的解析器和在没有输入之后成功的解析器之间进行了内部区分,你应该能够利用它。特别是,以下内容应该用于解析p然后 - 以p成功消费输入为条件 - 解析q并丢弃其结果:

ifConsumed :: Monad m => ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m a
ifConsumed p q = mkPT k
  where -- k :: State s u -> m (Consumed (m (Reply s u a)))
        k s = do cons <- runParsecT p s
                 case cons of
                   Consumed mrep -> do
                     rep <- mrep
                     case rep of
                       Ok x s' err -> runParsecT (fmap (const x) q) s'
                       Error err -> return . Consumed . return $ Error err
                   Empty mrep -> do
                     rep <- mrep
                     case rep of
                       Ok x s' err -> return . Empty . return $ Ok x s' err
                       Error err -> return . Empty . return $ Error err

它很难看,因为Parsec没有直接暴露ParsecT构造函数,所以你必须使用mkPtrunParsecT中介,它们会增加很多样板

简而言之,它运行p解析器。如果成功消耗了输入(Consumed -> Ok分支),它将运行通过q修改的fmap解析器以返回由p解析的值。另一方面,如果p成功而没有消耗输入(Empty -> Ok分支),它只返回成功而不运行q解析器。

唯一需要注意的是,我不能100%确定在Parsec库本身中如何保留不变量,只有在输入消耗时才会调用Consumed -> Ok分支,所以我不会&# 39;不知道这是否真的可靠。您希望在特定用例中仔细测试它。

对于下面的解析器---解析一个或多个元素的列表,用逗号分隔每个元素由零个或多个数字组成的符号,然后只有前一个解析器消耗了一些输入,然后是分号---两个惊叹号---它似乎有效:

p :: Parser [String]
p = ifConsumed (sepBy1 (many digit) (char ',')) (char '!' >> char '!') <* char ';'

runp :: String -> Either ParseError [String]
runp = parse p ""

一些测试:

runp ""         -- fails, expecting semicolon
runp ";"        -- returns [""]
runp "!!;"      -- fails, "!!" w/ no preceding content
runp ",;"       -- fails, missing "!!"
runp ",!!;"     -- returns ["",""]
runp ",!;"      -- fails, expecting second "!"
runp ",1,23;"   -- fails, missing "!!"
runp ",1,23!!;"  -- returns ["","1","23"]

答案 1 :(得分:1)

使用天真的解析器实现,您应该能够这样做:

(<<) p1 p2 = P $ \inp -> case parse p1 inp of
  ErrorResult e -> ErrorResult e
  SuccessResult (rem, res) -> if rem == inp
    then SuccessResult (rem, res)
    else parse p2 rem

虽然Parsec更先进,但您也可以在那里推出自己的产品。

答案 2 :(得分:0)

我认为你不能为任意解析器p1p2做到这一点:你需要他们以某种方式进行交流。如果你能做到这一点,在我看来你会破坏参照透明度。

例如,考虑解析输入字符串repeat 'x'p1是否消费某个字符,p2会将字符串视为x个字符的无尽海洋。如果它没有以某种方式与p1进行通信(例如通过修改解析器状态中的某些内容),那么您就无法知道某个角色是否已被消耗;如果你的组合者以某种方式能够以不同的方式处理这两种情况,那就违反了规则。