Передача состояния от производителя к синтаксическому анализатору

Я использую каналы, attoparsec и pipe-attoparsec, чтобы написать конвертер файла дампа базы данных. Общий формат файла состоит в том, чтобы иметь команду создания таблицы, за которой следует необязательная команда вставки. В дополнение к преобразованию операторов на месте определения таблиц должны храниться в памяти до самого конца для дополнительной обработки (индексы, ограничения и т. д.).

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

Я пробовал что-то вроде этого:

-- IO
import qualified Data.ByteString.Char8 as BS (putStrLn)
import System.Exit (ExitCode (..), exitSuccess, exitFailure)
import System.IO (hPutStrLn, stderr)

-- Pipes
import Pipes (runEffect, for, liftIO, Producer, Effect)
import Pipes.Attoparsec (parsed, ParsingError)
import Pipes.Lift (runStateP)
import Pipes.Safe (runSafeT)
import qualified Pipes.ByteString as PBS (stdin)

-- State
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Strict

dump' :: StateT ParserState Parser Command
dump' = fmap Create createStatements' <|> fmap Insert justData'

doStuff :: MonadIO m => Effect m (Either (ParsingError, Producer ByteString (StateT ParserState m) ()) (), ParserState)
doStuff = runStateP defaultParserState theStuff

theStuff :: MonadIO m => Effect (StateT ParserState m) (Either (ParsingError, Producer ByteString (StateT ParserState m) ()) ())
theStuff = for runParser (liftIO . BS.putStrLn <=< lift . processCommand)

runParser :: MonadIO m => Producer Command (StateT ParserState m) (Either (ParsingError, Producer ByteString (StateT ParserState m) ()) ())
runParser = do
    s <- lift get
    liftIO $ putStrLn "runParser"
    liftIO $ putStrLn $ show s
    parsed (evalStateT dump' s) PBS.stdin

processCommand :: MonadIO m => Command -> StateT ParserState m ByteString
processCommand (Create xs) = do
    currentState <- get
    liftIO $ putStrLn "processCommand"
    liftIO $ putStrLn $ show currentState
    _ <- put (currentState { constructs = xs ++ (constructs currentState)})
    return $ P.firstPass $ P.transformConstructs xs
processCommand (Insert x) = return x

Полный исходный код (включая парсеры): https://github.com/cimmanon/mysqlnothx/blob/parser-state/src/Main.hs

Когда я запускаю его, я получаю результат, который выглядит примерно так:

runParser
ParserState {constructs = []}
processCommand
ParserState {constructs = []}
processCommand
ParserState {constructs = [ ... ]}
processCommand
ParserState {constructs = [ ..... ]}

Я ожидал, что runParser (который будет получать последнее содержимое из State) будет запускаться каждый раз, когда запускается processCommand, но это явно не так, судя по выходным данным. Когда я проверяю содержимое State в синтаксическом анализаторе, оно всегда пусто, независимо от того, сколько команд анализируется.

Как я могу расширить состояние от моих производителей до моего парсера (дампа), чтобы они использовали одно и то же состояние? Если у моего производителя есть 4 значения в состоянии, синтаксический анализатор также должен увидеть те же самые 4 значения.


person cimmanon    schedule 01.05.2017    source источник


Ответы (1)


Я ожидал, что runParser (который будет получать последнее содержимое из состояния) будет запускаться каждый раз, когда запускается processCommand, но это явно не так.

Ваш основной эффект for runParser (liftIO . BS.putStrLn <=< lift . processCommand). Чтобы понять, что это за эффект, вам нужно понять, что такое for делает:

(for p body) зацикливается на p, заменяя каждый yield на body

«Зацикливается over p» верно, хотя и немного сбивает с толку. Он не запускается p один раз для каждого значения, созданного p; что бы взорваться! Вместо этого for заменяет все yield в p на body. Заменив yield на body, он запустит body один раз для каждого значения yielded. Запуск тела один раз для каждого полученного значения аналогичен тому, как в других языках цикл for над списком запускает тело один раз для каждого значения в списке.

Ваш runParser

runParser = do
    s <- lift get
    liftIO $ putStrLn "runParser"
    liftIO $ putStrLn $ show s
    parsed (evalStateT dump' s) PBS.stdin

Он считывает состояние, выводит его и создает Commands parsed из stdin. parsed анализирует источник и yields один раз для каждого полностью успешно проанализированного значения. Затем ваш for заменяет все yield parsed на liftIO . BS.putStrLn <=< lift . processCommand. Полный эффект выполняется runParser один раз и processCommand один раз для каждого yield, что вы и видите на выходе.

person Cirdec    schedule 01.05.2017
comment
Это объясняет поведение, которое я вижу, но возможно ли добиться поведения, которое я ищу? Я готов переписать свои синтаксические анализаторы, чтобы они работали по-другому, если потребуется, но я надеюсь сэкономить себе несколько часов работы. - person cimmanon; 01.05.2017
comment
@cimmanon Из вашего вопроса я не уверен, какое поведение вы ищете. Возможно, было бы проще задать два вопроса: почему мой код делает это?, сосредоточившись на описании вашего существующего кода, как этот, и как я могу заставить его делать X? сосредоточившись на описании X, которого вы хотите достичь. - person Cirdec; 01.05.2017
comment
@cimmanon Но вот подсказка: если вы хотите сделать что-то еще в конце после того, как все Command были проанализированы, попробуйте добавить несколько строк в конец runParser (например, s' <- lift get; liftIO $ putStrLn "runParser end"; liftIO $ putStrLn $ show s') и посмотрите, хотите ли вы этого. Или в конце theStuff или doStuff, если это имеет смысл. - person Cirdec; 01.05.2017
comment
Это синтаксический анализатор ниже по ветке Insert, которому требуется доступ к проанализированным до сих пор определениям таблиц, чтобы я мог запустить правильный синтаксический анализатор для каждого типа столбца (в значительной степени гарантировано создание таблицы, а затем вставка для этой таблицы, но другие операторы создания которые я фиксирую, могут появляться между такими же индексами). БД, которая генерирует дампы, которые я разбираю, выдает недопустимые даты, а двоичные типы имеют неправильный формат... и они выглядят неотличимыми от обычной строки в кавычках. - person cimmanon; 01.05.2017
comment
Если бы вы не могли сказать, спрашивал ли я, почему X это делает? или Как мне сделать X?, вы должны были попросить разъяснений, а не публиковать ответ. Поскольку мой вопрос никогда не был «Почему X это делает?», я уже мог сделать вывод о поведении на основе вывода. - person cimmanon; 01.05.2017