Pipe Transformers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Control.Monad.Trans.Either
import Control.Monad.Trans.State
import Control.Pipe
import Control.Pipe.Category
import Control.Pipe.Trans
import Data.Monoid
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Text (Text)

newtype ParseP p a b r = ParseP {
    unParseP :: StateP Text (EitherP String p) a b r }
    deriving (Monad, MonadPipe)

instance PipeTrans ParseP where
    liftP = ParseP . liftP . liftP

runParseP = runEitherP . (`runStateP` T.empty) . unParseP

-- This can be made more efficient by hand-writing the state passing instead of
-- using calls to to 'put' and 'get'
take' :: Monad m => Int -> ParseP (Pipe m) Text b Text
take' n = ParseP go  where
    go = do
        s <- P get
        if (T.length s < n)
        then do
            s' <- liftP $ liftP await
            P $ put (s <> s')
            go
        else do
            let (h, t) = T.splitAt n s
            P $ put t
            return h

parseFail str = ParseP $ liftP $ P $ left str

string :: (Monad m) => Text -> ParseP (Pipe m) Text b Text
string str = do
    str' <- take' (T.length str)
    if (str' == str)
    then return str
    else parseFail $ concat [
        "Expected: '", T.unpack str, "' -- Found: ", T.unpack str', "'"]

source :: (Monad m) => Pipe m x Text ()
source = do
    yield $ T.pack "Hell"
    yield $ T.pack "o, world!"

sink :: ParseP (Pipe IO) Text x ()
sink = do
    string $ T.pack "Hello"
    str <- take' 5
    liftP $ liftPipe $ T.putStrLn str

{-
>>> runPipe $ runParseP $ sink <-< liftP source
, wor
Right ((),"ld!")
>>> runPipe $ runParseP $ sink <-< liftP (yield $ T.pack "AAAAAAAAA")
Left "Expected: 'Hello' -- Found: AAAAA'"
-}