2013-03-15 2 views
5

Я пытаюсь разбора двоичных данных с использованием pipe-attoparsec в Haskell. Причина, по которой используются трубы (прокси), - это чередование чтения с помощью синтаксического анализа, чтобы избежать использования большой памяти для больших файлов. Многие двоичные форматы основаны на блоках (или кусках), а их размеры часто описываются полем в файле. Я не уверен, что называется парсер для такого блока, но это то, что я подразумеваю под «подпарасером» в названии. Проблема заключается в том, чтобы реализовать их в сжатом виде без потенциально большого объема памяти. Я придумал две альтернативы, каждая из которых терпит неудачу.«Sub-parsers» in pipes-attoparsec

Альтернатива 1 заключается в том, чтобы прочитать блок в отдельную строку и запустить для нее отдельный синтаксический анализатор. Хотя краткий, большой блок вызовет высокую память.

Альтернатива 2 заключается в том, чтобы продолжать разбор в одном контексте и отслеживать количество потребляемых байтов. Это отслеживание подвержено ошибкам и, похоже, заражает все синтаксические анализаторы, которые формируются в последнем блочном процессоре. Для искаженного входного файла он также может тратить время, анализируя дальше, чем указано полем размера, прежде чем можно будет сравнить отслеживаемый размер.

import Control.Proxy.Attoparsec 
import Control.Proxy.Trans.Either 
import Data.Attoparsec as P 
import Data.Attoparsec.Binary 
import qualified Data.ByteString as BS 

parser = do 
    size <- fromIntegral <$> anyWord32le 

    -- alternative 1 (ignore the Either for simplicity): 
    Right result <- parseOnly blockParser <$> P.take size 
    return result 

    -- alternative 2 
    (result, trackedSize) <- blockparser 
    when (size /= trackedSize) $ fail "size mismatch" 
    return result 

blockParser = undefined 

main = withBinaryFile "bin" ReadMode go where 
    go h = fmap print . runProxy . runEitherK $ session h 
    session h = printD <-< parserD parser <-< throwParsingErrors <-< parserInputD <-< readChunk h 128 
    readChunk h n() = runIdentityP go where 
     go = do 
      c <- lift $ BS.hGet h n 
      unless (BS.null c) $ respond c *> go 

ответ

2

Мне нравится называть это парсером с фиксированным входом.

Я могу вам сказать, как это будет делать pipes-parse. Вы можете увидеть предварительный просмотр того, что я собираюсь описать в pipes-parse в функциях библиотеки parseN и parseWhile. Это на самом деле общие входы, но я написал аналогичные, например, String парсеров, а также here и here.

Трюк очень прост, вы вставляете поддельный конец входного маркера, где вы хотите, чтобы синтаксический анализатор остановился, запустите анализатор (который не удастся, если он попадет под фальшивый конец входного маркера), а затем удалите конец ввода маркер.

Очевидно, что это не так просто, как я делаю это звук, но это общий принцип. Трудные составляющие:

  • Выполнение этого действия таким образом, чтобы оно продолжало течь. Тот, который я связал, пока не делает этого, но способ, которым вы это делаете потоковым способом, - это вставить канал вверх по потоку, который подсчитывает байты, проходящие через него, а затем вставляет маркер конца ввода в нужное место.

  • Не мешая существующим конца ввода маркеров

Этот трюк может быть адаптирован для pipes-attoparsec, но я думаю, что лучшим решением было бы для attoparsec непосредственно включить эту функцию. Однако, если это решение недоступно, мы можем ограничить ввод, который подается на парсер attoparsec.

+0

Вставка трубы, которая подсчитывает восходящий поток, звучит интересно, но как она будет знать, сколько байтов считать? Это значение обнаруживается только парсером вниз по течению, который не может напрямую вызвать запрос со значением как параметром, поскольку он выполняется парсером. – absence

+0

@absence Ну, теперь игнорируйте интерфейс pipe-attoparsec, потому что мы с Ренцо скоро исправим его. Фиксированный входной парсер внутренне использует канал, который ограничивает количество байтов. Подумайте об этом как: 'parser1 >> (ограничивайте n> -> parser2) >> parser3'. Комбинатор с фиксированной шириной вставляет что-то вроде 'ограничивает 'выше указанного парсера. Это сложнее, чем это, но довольно похоже на дух. –

+0

Ссылки мертвы – SwiftsNamesake

2

Итак, я, наконец, выяснил, как это сделать, и я кодифицировал этот шаблон в библиотеке pipes-parse. pipes-parse tutorial объясняет, как это сделать, особенно в разделе «Вложенность».

В этом учебном пособии объясняется это только для синтаксического анализа типа данных (агрегированного элемента), но вы можете расширить его для работы с ByteString s для работы.

двух основных трюков, которые делают эту работу являются:

  • Крепление StateP быть глобальным (в pipes-3.3.0)

  • Встраивание суб-анализатор в переходном StateP слое так, что он использует свежий контекст остатков

pipes-attoparsec скоро опубликует обновление buil ds на pipes-parse, чтобы вы могли использовать эти трюки в своем собственном коде.

+0

Могу ли я вызвать passUpTo внутри Data.Attoparsec.Parser, как функция парсера в моем примере? Или лучше комбинировать несколько небольших прокси-серверов parseD вместо использования одного огромного Parser, который, будучи составлен из небольших Parsers, является черным ящиком для pipe-attoparsec? – absence

+0

Вы хотите, чтобы 'parseD' перебирал маленькие' Parser', потому что он не может освободить память до тех пор, пока не завершится каждый 'Parser'. 'attoparsec' никогда не освобождает ввод до тех пор, пока' Parser' не завершится, потому что он всегда оставляет за собой право отступать в 'Parser'. Единственный способ разобрать что-то в постоянной памяти - это определить границы в потоке, где безопасно сбросить предыдущий ввод. Например, если вы разбираете огромный файл CSV, тогда вы можете определить 'Parser' для каждой строки файла CSV, называемого' parseLine', а затем просто запустить 'parseD' для генерации потока анализируемых строк. –

+0

@ отсутствие @ Кроме того, 'pipe-bytestring' будет предоставлять примитив' passBytesUpTo', который позволит вам разграничить весь парсер 'attoparsec' на фиксированный вход, все еще показывая потоки в постоянной памяти. Скорее всего, это ближе к тому, что вы хотите. Идея заключается в том, что вы отправляете 'passBytesUpTo' * вверх по течению * вызов' Control.Proxy.Attoparsec.parse' и запускаете этот анализатор в фиксированном количестве байтов. –

Смежные вопросы