将状态从制作者传递给解析器

时间:2017-05-01 13:56:43

标签: haskell haskell-pipes attoparsec

我使用管道,attoparsec和pipes-attoparsec来编写数据库转储文件转换器。该文件的一般格式是使用create table命令,后跟可选的insert命令。除了转换语句之外,表定义必须保存在内存中,直到最后进行额外的处理(索引,约束等)。

这很好用,但现在我需要允许我的一些内部解析器访问我的Producer的状态,以确定在处理insert命令中的值时需要运行哪个解析器。

我试过这样的事情:

-- 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 = [ ..... ]}

我希望每次processCommand运行时都会运行runParser(它会从State获取最新内容),但显然不是基于输出的情况。当我在解析器中检查State的内容时,无论解析了多少命令,它总是为空。

如何将State从我的制作人扩展到我的Parser(转储&#39;),以便他们共享同一个州?如果我的Producer在State中有4个值,则Parser也应该看到相同的4个值。

1 个答案:

答案 0 :(得分:0)

  

我希望每次runParser运行时都会processCommand(可以从州获取最新内容),但情况显然并非如此。

您的主要影响是for runParser (liftIO . BS.putStrLn <=< lift . processCommand)。要了解这种影响,您需要了解for的作用:

  

(for p body)循环p,用yield

替换每个body

&#34;循环 over p&#34;如果有点混乱,是准确的。对于p生成的每个值,它不会运行p次;那会爆炸!相反,for会将yield中的每个p替换为body。将yield替换为body,每body次ed值运行一次yield。为每个生成的值运行一次主体类似于在其他语言中for循环 over 一个列表为列表中的每个值运行一次主体。

您的runParser

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

它读取状态,输出它,并从Command生成parsed s stdin。 Pipes-autoparsec parsed为每个完全成功解析的值解析源和yield一次。然后,您for会用parsed替换yieldliftIO . BS.putStrLn <=< lift . processCommand中的每一个runParser。对于每个processCommand ,完整效果会{/ 1}} 一次yield 一次,这就是您在“{1}} 中观察到的内容输出