使用 attoparsec 进行多行后续行的匹配值

Matching values that carry onto multiple following lines with attoparsec

我正在尝试解析以下内容:

message: 123 test
abc xys
messageA: hmm
messageA: testing
messageB: aueo
qkhwueoaz

变成这样的东西:

[
  ("message", "123 test\nabcxyz"),
, ("messageA", "hmm")
, ("messageA","testing")
, ("messageB","aueo\nqkhwueoaz")
]

但是我似乎无法弄清楚这一点,我发现了一些困难,因为我不是 100% 熟悉 attoparsecs 功能(而且我真的看不到每个函数是否被记录在案它向前移动光标...)。

我已通读: 并且我得到以下代码:

isChrisNext :: Parser ()
isChrisNext = lookAhead (parseChris) *> pure()

notFollowedBy :: Monad m => m a -> m b
notFollowedBy p = p >> fail "not followed by"

restOfLine :: Parser Text
restOfLine = do
    rest <- takeTill (== '\n')
    isEOF <- atEnd
    if isEOF then
        return rest
    else
        (char '\n') >> return rest

parseChris :: Parser [Text]
parseChris = do
  x <- takeWhile1 (notInClass ":")
  _ <- string ":"
  x' <- manyTill restOfLine (endOfInput <|> isChrisNext)
  () <- return $ unsafePerformIO $! do
    print "?????????????"
    print x
    print x'
  return $ x : x'

然而,试图用 parseChris 解析数据只是 returns: [ "message" ] 而我期待 ("message", "123 test\nabcxyz")

如果我将前瞻函数更改为:

isChrisNext :: Parser ()
isChrisNext = lookAhead (string "message:") *> pure()

我得到了更符合预期的输出:

[ "message"
, "123 test"
, "abc xys"         
] 

另外,前面提到的问题还有一个评论建议的做法是:

Just parse the log times apart by matching on time stamps, and only within each time-entry parse the sub-entries.

我也知道第二行可能包含 : 的潜在问题,但这不是我需要考虑的问题,谢天谢地...

我发现在使用解析器组合器时真正有用的方法是将整个问题分解成更小的部分。所以,我只是先组成解析器 bottom-up: a keyValuePair,然后整个解析器只包含 many keyValuePairkeyValuePair 消耗了 : 之前的部分,然后尽可能多地吃掉没有 : 的行。

在代码中:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString.Char8 as BS
import qualified Data.Attoparsec.ByteString.Char8 as AT
import Control.Applicative
import Data.Functor

valuePart :: AT.Parser BS.ByteString
valuePart = AT.takeTill (`BS.elem` ":\n") <* AT.endOfLine

keyValuePair :: AT.Parser (BS.ByteString, BS.ByteString)
keyValuePair = do
    key <- AT.takeTill (== ':')
    void ": "
    valLines <- AT.many1 valuePart
    pure (key, BS.intercalate "\n" valLines)

parser :: AT.Parser [(BS.ByteString, BS.ByteString)]
parser = many keyValuePair

运行 根据您的输入数据生成

*Main> AT.parseOnly parser test
Right [("message","123 test\nabc xys"),("messageA","hmm"),("messageA","testing"),("messageB","aueo")]

请注意,没有前瞻,因为不需要它:一旦 valuePart 遇到 :,它就会失败,这会导致 keyValuePair 停止并且接下来 keyValuePair 通过 parser.

中的 top-level many 得到 运行

顺便说一句,您可以使用 Debug.Trace 中的 tracetraceShow 而不是 unsafePerformIO 来生成调试输出。