Разбор многострочного журнала с помощью attoparsec - PullRequest
0 голосов
/ 31 января 2019

Я пытаюсь разобрать многострочный журнал, как этот

[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)

Любые другие идеи?

РЕДАКТИРОВАТЬ

Боб Далглиш решение по существу работает (большое спасибо !!!), просто нужно несколько настроек, чтобы оно заработало.Это мое окончательное решение:

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)

1 Ответ

0 голосов
/ 31 января 2019

Вам нужен парсер для Block с.Он принимает Entry, просматривает Entry с тем же идентификатором;если не то же самое, он возвращает обратно и возвращает то, что у него есть.

Сначала давайте введем условный 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.)

...