将状态从生产者传递到解析器



我正在使用pipes,attoparsec和pipes-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 = [ ..... ]}

我期望每次 processCommand 运行时都会运行 runParser(它会从状态中获取最新内容),但根据输出,情况显然并非如此。 当我在解析器中检查 State 的内容时,无论解析多少命令,它始终为空。

如何将状态从我的生产者扩展到我的解析器(转储),以便它们共享相同的状态? 如果我的生产者在状态中有 4 个值,解析器也应该看到这 4 个相同的值。

我期望每次运行时都会运行runParser(它会从状态中获取最新内容processCommand但显然并非如此。

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

(for p body)循环pbody替换每个yield

"循环p"是准确的,如果有点混乱。它不会为p产生的每个值运行一次p;那会爆炸的!相反,forbody替换p中的每个yield。通过将yield替换为body,它bodyyielded 值运行一次。为每个生成的值运行一次主体类似于在其他语言中,列表上的for 循环如何为列表中的每个值运行一次主体。

您的runParser

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

它读取状态,输出状态,并从stdin生成Commandparsed。Pipes-autoparsec 的parsed解析源,并为每个完全成功解析的值yield一次。然后,您的forparsed的每个yield替换为liftIO . BS.putStrLn <=< lift . processCommand。完整的效果runParser运行一次processCommand每个yield运行一次,这就是您在输出中观察到的内容。

最新更新