我使用管道,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个值。
答案 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
替换yield
个liftIO . BS.putStrLn <=< lift . processCommand
中的每一个runParser
。对于每个processCommand
,完整效果会{/ 1}} 一次和yield
一次,这就是您在“{1}} 中观察到的内容输出