我需要像p1 << p2
这样的组合器,但如果p2
成功并且消耗了一些输入,p1
应该只运行 。
如果p1
成功而没有消费输入,则p2
不应该运行。
如果p1
失败,那么p2
也会被忽略?
总体结果是r1
的结果
答案 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
构造函数,所以你必须使用mkPt
和runParsecT
中介,它们会增加很多样板
简而言之,它运行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)
我认为你不能为任意解析器p1
和p2
做到这一点:你需要他们以某种方式进行交流。如果你能做到这一点,在我看来你会破坏参照透明度。
例如,考虑解析输入字符串repeat 'x'
:p1
是否消费某个字符,p2
会将字符串视为x
个字符的无尽海洋。如果它没有以某种方式与p1
进行通信(例如通过修改解析器状态中的某些内容),那么您就无法知道某个角色是否已被消耗;如果你的组合者以某种方式能够以不同的方式处理这两种情况,那就违反了规则。