benmachine/

Progress reports with ParsecT

The ParsecT monad transformer introduced in parsec 3 has some interesting applications. Here's a little snippet that uses it with the Writer monad to allow slow parses to let you know how far they've got:

import Control.Applicative
import Control.Monad.Writer
import Text.Parsec

type ReportingParser a = ParsecT String () (Writer (Action IO)) a

newtype Action f = Action { runAction :: f () }

instance (Applicative f) => Monoid (Action f) where
mempty = Action $ pure ()
Action a `mappend` Action b = Action (a *> b)

report :: IO () -> ReportingParser ()
report = lift . tell . Action

-- Example parser
reportingParse :: ReportingParser ()
reportingParse = forM_ (zip [1 .. 10] ['a' ..]) $ \(i, c) -> do
spaces
char c
report $ print i

testInput = ' ' : 'a' : concatMap extend testInput
where
extend ' ' = replicate 5 ' '
extend c = [succ c]

test = runAction . execWriter $ runParserT reportingParse () "" testInput

And the result:

ghci> test
Loading package bytestring-0.9.1.7 ... linking ... done.
Loading package syb-0.1.0.2 ... linking ... done.
Loading package base-3.0.3.2 ... linking ... done.
Loading package mtl-1.1.0.2 ... linking ... done.
Loading package parsec-3.1.0 ... linking ... done.
1
2
3
4
5
6
7
8
9
10