使用parsec解析子字符串(通过忽略不匹配的前缀)

时间:2017-11-09 18:01:36

标签: parsing haskell parsec

我想从git remote -v的第一行提取存储库名称,通常是以下形式:

origin git@github.com:some-user/some-repo.git (fetch)

我使用parsec快速制作了以下解析器:

-- | Parse the repository name from the output given by the first line of `git remote -v`.
repoNameFromRemoteP :: Parser String
repoNameFromRemoteP = do
    _ <- originPart >> hostPart
    _ <- char ':'
    firstPart <- many1 alphaNum
    _ <- char '/'
    secondPart <- many1 alphaNum
    _ <- string ".git"
    return $ firstPart ++ "/" ++ secondPart
    where
      originPart = many1 alphaNum >> space
      hostPart =  many1 alphaNum
               >> (string "@" <|> string "://")
               >> many1 alphaNum `sepBy` char '.'

但是这个解析器看起来有点尴尬。实际上我只对冒号(":")后面的内容感兴趣,如果我能为它编写解析器会更容易。

有没有办法让parsec在失败的比赛中跳过一个角色,并从下一个位置重新尝试?

2 个答案:

答案 0 :(得分:3)

如果我理解了这个问题,请尝试many (noneOf ":")。这将消耗任何字符,直到它看到':',然后停止。

编辑:似乎我没有理解这个问题。您可以使用try组合器来转换可能会消耗某些字符的解析器,然后再将其转换为在失败时不消耗任何字符的解析器。所以:

skipUntil p = try p <|> (anyChar >> skipUntil p)

请注意,这在运行时(因为它会尝试在每个位置匹配p)和内存(因为try阻止p消耗字符,所以这可能非常昂贵,因此在p完成之前,输入不能被垃圾收集。您可以通过参数化anyChar位来缓解这两个问题中的第一个,以便调用者可以选择一些廉价的解析器来查找候选位置; e.g。

skipUntil p skipper = try p <|> (skipper >> skipUntil p skipper)

然后,您可以使用上述many (noneOf ":")构造仅对以p开头的位置尝试:

答案 1 :(得分:0)

sepCap 来自的组合器 replace-megaparsec 可以在失败的比赛中跳过角色,然后从下一个位置重试。

对于您的特定情况,这也许算是过大了,但是它确实解决了 普遍的问题。

import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Maybe
import Data.Either

username :: Parsec Void String String
username = do
    void $ single ':'
    some $ alphaNumChar <|> single '-'

listToMaybe . rights =<< parseMaybe (sepCap username)
    "origin git@github.com:some-user/some-repo.git (fetch)"
Just "some-user"