Я пытаюсь разобрать многострочный журнал, подобный этому
[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 или более последовательных строк имели одинаковый идентификатор (например, 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
. Он не будет излучатьBlock
, пока не будет уверен, чтоBlock
нельзя вырастить. Другой подход заключается в использовании способности attoparsec выполнять неограниченный просмотр вперед, чтобы определить, имеет ли следующийEntry
тот же идентификатор. - person Bob Dalgleish   schedule 31.01.2019