aboutsummaryrefslogtreecommitdiffhomepage
path: root/Data/Text/Pipes/Parse.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Data/Text/Pipes/Parse.hs')
-rw-r--r--Data/Text/Pipes/Parse.hs139
1 files changed, 139 insertions, 0 deletions
diff --git a/Data/Text/Pipes/Parse.hs b/Data/Text/Pipes/Parse.hs
new file mode 100644
index 0000000..675c7aa
--- /dev/null
+++ b/Data/Text/Pipes/Parse.hs
@@ -0,0 +1,139 @@
1-- | Parsing utilities for characterstrings, in the style of @pipes-parse@
2
3module Data.Text.Pipes.Parse (
4 -- * Parsers
5 nextChar,
6 drawChar,
7 unDrawChar,
8 peekChar,
9 isEndOfChars,
10 take,
11 takeWhile
12 ) where
13
14import Control.Monad.Trans.State.Strict (StateT, modify)
15import qualified Data.Text as T
16import Data.Text (Text)
17
18import Pipes
19import qualified Pipes.Parse as PP
20
21import Prelude hiding (take, takeWhile)
22
23{-| Consume the first character from a 'Text' stream
24
25 'next' either fails with a 'Left' if the 'Producer' has no more characters or
26 succeeds with a 'Right' providing the next byte and the remainder of the
27 'Producer'.
28-}
29nextChar
30 :: (Monad m)
31 => Producer Text m r
32 -> m (Either r (Char, Producer Text m r))
33nextChar = go
34 where
35 go p = do
36 x <- next p
37 case x of
38 Left r -> return (Left r)
39 Right (txt, p') -> case (T.uncons txt) of
40 Nothing -> go p'
41 Just (c, txt') -> return (Right (c, yield txt' >> p'))
42{-# INLINABLE nextChar #-}
43
44{-| Draw one 'Char' from the underlying 'Producer', returning 'Left' if the
45 'Producer' is empty
46-}
47drawChar :: (Monad m) => StateT (Producer Text m r) m (Either r Char)
48drawChar = do
49 x <- PP.draw
50 case x of
51 Left r -> return (Left r)
52 Right txt -> case (T.uncons txt) of
53 Nothing -> drawChar
54 Just (c, txt') -> do
55 PP.unDraw txt'
56 return (Right c)
57{-# INLINABLE drawChar #-}
58
59-- | Push back a 'Char' onto the underlying 'Producer'
60unDrawChar :: (Monad m) => Char -> StateT (Producer Text m r) m ()
61unDrawChar c = modify (yield (T.singleton c) >>)
62{-# INLINABLE unDrawChar #-}
63
64{-| 'peekChar' checks the first 'Char' in the stream, but uses 'unDrawChar' to
65 push the 'Char' back
66
67> peekChar = do
68> x <- drawChar
69> case x of
70> Left _ -> return ()
71> Right c -> unDrawChar c
72> return x
73-}
74peekChar :: (Monad m) => StateT (Producer Text m r) m (Either r Char)
75peekChar = do
76 x <- drawChar
77 case x of
78 Left _ -> return ()
79 Right c -> unDrawChar c
80 return x
81{-# INLINABLE peekChar #-}
82
83{-| Check if the underlying 'Producer' has no more characters
84
85 Note that this will skip over empty 'Text' chunks, unlike
86 'PP.isEndOfInput' from @pipes-parse@.
87
88> isEndOfChars = liftM isLeft peekChar
89-}
90isEndOfChars :: (Monad m) => StateT (Producer Text m r) m Bool
91isEndOfChars = do
92 x <- peekChar
93 return (case x of
94 Left _ -> True
95 Right _ -> False )
96{-# INLINABLE isEndOfChars #-}
97
98{-| @(take n)@ only allows @n@ characters to pass
99
100 Unlike 'take', this 'PP.unDraw's unused characters
101-}
102take :: (Monad m, Integral a) => a -> Pipe Text Text (StateT (Producer Text m r) m) ()
103take n0 = go n0 where
104 go n
105 | n <= 0 = return ()
106 | otherwise = do
107 txt <- await
108 let len = fromIntegral (T.length txt)
109 if (len > n)
110 then do
111 let n' = fromIntegral n
112 lift . PP.unDraw $ T.drop n' txt
113 yield $ T.take n' txt
114 else do
115 yield txt
116 go (n - len)
117{-# INLINABLE take #-}
118
119{-| Take characters until they fail the predicate
120
121 Unlike 'takeWhile', this 'PP.unDraw's unused characters
122-}
123takeWhile
124 :: (Monad m)
125 => (Char -> Bool)
126 -> Pipe Text Text (StateT (Producer Text m r) m) ()
127takeWhile predicate = go
128 where
129 go = do
130 txt <- await
131 let (prefix, suffix) = T.span predicate txt
132 if (T.null suffix)
133 then do
134 yield txt
135 go
136 else do
137 lift $ PP.unDraw suffix
138 yield prefix
139{-# INLINABLE takeWhile #-}