使用 attoparsec 解析多行日志
Parsing multi line log with attoparsec
我正在尝试像这样解析多行日志
[xxx] This is 1
[xxx] This is also 1
[yyy] This is 2
我定义了这些类型
{-# LANGUAGE OverloadedStrings #-}
module Parser where
import Prelude hiding(takeWhile)
import Data.Text
import Data.Word
import Data.Attoparsec.Text as T
import Data.Char
import Data.String
data ID = ID String deriving (Eq, Show)
data Entry = Entry ID String deriving (Eq, Show)
data Block = Block ID [String]
data Log = Log [Block]
并定义了这些解析器:
parseID :: Parser ID
parseID = do
char '['
id <- takeTill ( == ']' )
char ']'
return $ ID $ unpack id
parseEntry :: Parser Entry
parseEntry = do
id <- parseID
char ' '
content <- takeTill isEndOfLine
return $ Entry id (unpack content)
当我做类似 parseOnly parseEntry entryString
的事情时,这工作正常,我得到一个 Entry
。
问题是当我尝试解析诸如我在开始时添加的日志之类的内容时。
我会得到 [Entry]
但我想得到 [Block]
.
我还希望当 2 条或更多条连续的行具有相同的 ID(如 xxx
)时,它们应该存储在同一个块中,因此为了解析上述日志,我想返回
[block1, block2]
-- block1 == Block "xxx" ["This is 1", "This is also 1"]
-- block2 == Block "yyy" ["This is 2"]
如何根据 ID
的变化让解析器创建新块或添加到最后生成的块中?
一个明显的解决方案是简单地生成一个 [Entry]
,然后使用折叠函数以适当的逻辑将其转换为 [Block]
,但我会进行 2 遍,1 遍日志和 [Entry]
上的另一个日志,这似乎不仅对大型日志来说性能不佳,而且感觉是错误的方式(根据我非常有限的 attoparsec 知识)
还有其他想法吗?
编辑
Bob Dalgleish 解决方案基本上有效(非常感谢!!!),只需要进行一些调整即可使其有效。
这是我的最终解决方案:
data ID = ID String deriving (Eq, Show)
data Entry = Entry ID String deriving (Eq, Show)
data Block = Block ID [String] deriving (Eq, Show)
data Log = Log [Block] deriving (Eq, Show)
parseID :: Parser ID
parseID = do
char '['
id <- takeTill ( == ']' )
char ']'
return $ ID $ unpack id
parseEntry :: Parser Entry
parseEntry = do
id <- parseID
char ' '
content <- takeTill isEndOfLine
return $ Entry id (unpack content)
parseEntryFor :: ID -> Parser Entry
parseEntryFor blockId = do
id <- parseID
if blockId == id
then do
char ' '
content <- takeTill isEndOfLine
endOfLine <|> endOfInput
return $ Entry id (unpack content)
else fail "nonmatching id"
parseBlock :: Parser Block
parseBlock = do
(Entry entryId s) <- parseEntry
let newBlock = Block entryId [s]
endOfLine <|> endOfInput
entries <- many' (parseEntryFor entryId)
return $ Block entryId (s : Prelude.map (\(Entry _ s') -> s') entries)
您需要一个 Block
的解析器。它接受一个 Entry
,对具有相同 id 的 Entry
进行前瞻;如果不一样,它会回溯 returns 目前为止的内容。
首先,让我们介绍一个条件Entry
解析器:
parseEntryFor :: ID -> Parser Entry
parseEntryFor blockId = do
id <- parseEntry
if blockId == id
then do
char ' '
content <- takeTill isEndOfLine
endOfLine
return $ Entry id (unpack content)
else fail "nonmatching id"
-- |A Block consists of one or more Entry's with the same ID
parseBlock :: Parser Block
parseBlock = do
(Entry entryId s) <- parseEntry
let newBlock = Block entryId [s]
endOfLine
entries <- many' (parseEntryFor entryId)
return $ Block entryId s: (map (\(Entry _ s') -> x') entries)
(此代码未经测试,因为我只使用过 Parsec。)
我正在尝试像这样解析多行日志
[xxx] This is 1
[xxx] This is also 1
[yyy] This is 2
我定义了这些类型
{-# LANGUAGE OverloadedStrings #-}
module Parser where
import Prelude hiding(takeWhile)
import Data.Text
import Data.Word
import Data.Attoparsec.Text as T
import Data.Char
import Data.String
data ID = ID String deriving (Eq, Show)
data Entry = Entry ID String deriving (Eq, Show)
data Block = Block ID [String]
data Log = Log [Block]
并定义了这些解析器:
parseID :: Parser ID
parseID = do
char '['
id <- takeTill ( == ']' )
char ']'
return $ ID $ unpack id
parseEntry :: Parser Entry
parseEntry = do
id <- parseID
char ' '
content <- takeTill isEndOfLine
return $ Entry id (unpack content)
当我做类似 parseOnly parseEntry entryString
的事情时,这工作正常,我得到一个 Entry
。
问题是当我尝试解析诸如我在开始时添加的日志之类的内容时。
我会得到 [Entry]
但我想得到 [Block]
.
我还希望当 2 条或更多条连续的行具有相同的 ID(如 xxx
)时,它们应该存储在同一个块中,因此为了解析上述日志,我想返回
[block1, block2]
-- block1 == Block "xxx" ["This is 1", "This is also 1"]
-- block2 == Block "yyy" ["This is 2"]
如何根据 ID
的变化让解析器创建新块或添加到最后生成的块中?
一个明显的解决方案是简单地生成一个 [Entry]
,然后使用折叠函数以适当的逻辑将其转换为 [Block]
,但我会进行 2 遍,1 遍日志和 [Entry]
上的另一个日志,这似乎不仅对大型日志来说性能不佳,而且感觉是错误的方式(根据我非常有限的 attoparsec 知识)
还有其他想法吗?
编辑
Bob Dalgleish 解决方案基本上有效(非常感谢!!!),只需要进行一些调整即可使其有效。 这是我的最终解决方案:
data ID = ID String deriving (Eq, Show)
data Entry = Entry ID String deriving (Eq, Show)
data Block = Block ID [String] deriving (Eq, Show)
data Log = Log [Block] deriving (Eq, Show)
parseID :: Parser ID
parseID = do
char '['
id <- takeTill ( == ']' )
char ']'
return $ ID $ unpack id
parseEntry :: Parser Entry
parseEntry = do
id <- parseID
char ' '
content <- takeTill isEndOfLine
return $ Entry id (unpack content)
parseEntryFor :: ID -> Parser Entry
parseEntryFor blockId = do
id <- parseID
if blockId == id
then do
char ' '
content <- takeTill isEndOfLine
endOfLine <|> endOfInput
return $ Entry id (unpack content)
else fail "nonmatching id"
parseBlock :: Parser Block
parseBlock = do
(Entry entryId s) <- parseEntry
let newBlock = Block entryId [s]
endOfLine <|> endOfInput
entries <- many' (parseEntryFor entryId)
return $ Block entryId (s : Prelude.map (\(Entry _ s') -> s') entries)
您需要一个 Block
的解析器。它接受一个 Entry
,对具有相同 id 的 Entry
进行前瞻;如果不一样,它会回溯 returns 目前为止的内容。
首先,让我们介绍一个条件Entry
解析器:
parseEntryFor :: ID -> Parser Entry
parseEntryFor blockId = do
id <- parseEntry
if blockId == id
then do
char ' '
content <- takeTill isEndOfLine
endOfLine
return $ Entry id (unpack content)
else fail "nonmatching id"
-- |A Block consists of one or more Entry's with the same ID
parseBlock :: Parser Block
parseBlock = do
(Entry entryId s) <- parseEntry
let newBlock = Block entryId [s]
endOfLine
entries <- many' (parseEntryFor entryId)
return $ Block entryId s: (map (\(Entry _ s') -> x') entries)
(此代码未经测试,因为我只使用过 Parsec。)