on master help sign in
Add a CPS style parser implementation

CPS performs much better for parsing operations like "sequence" and "choice" on large containers. Given that applicative "sequence" does not scale, I guess the Monad instance as well won't scale for direct implementation.

commit12fef4390fad52cccd76b49d7f29daac6ff497c3 authorHarendra Kumar <harendra@composewell.com> dateThu 9 Apr 2020, 15:59 (7 years ago) parent30ddc28
9 files changed+698−5
benchmark/Chart.hs+7 benchmark/Streamly/Benchmark/Data/Parser.hs+15−5 benchmark/Streamly/Benchmark/Data/ParserK.hs+157 benchmark/streamly-benchmarks.cabal+12 src/Streamly/Internal/Data/Parser.hs+14 src/Streamly/Internal/Data/ParserK/Types.hs+99 src/Streamly/Internal/Data/Zipper.hs+380 src/Streamly/Internal/Prelude.hs+12 streamly.cabal+2
benchmark/Chart.hs+7
@@ -29,6 +29,7 @@ data BenchType
2929 | LinearRate
3030 | NestedConcurrent
3131 | Parser
32+ | ParserK
3233 | Base
3334 | FileIO
3435 | Array
@@ -82,6 +83,7 @@ parseBench = do
8283 Just "linear-rate" -> setBenchType LinearRate
8384 Just "nested-concurrent" -> setBenchType NestedConcurrent
8485 Just "parser" -> setBenchType Parser
86+ Just "parserK" -> setBenchType ParserK
8587 Just "base" -> setBenchType Base
8688 Just "fileio" -> setBenchType FileIO
8789 Just "array-cmp" -> setBenchType ArrayCmp
@@ -367,6 +369,11 @@ main = do
367369 (makeGraphs "parser")
368370 "charts/parser/results.csv"
369371 "charts/parser"
372+ ParserK -> benchShow opts cfg
373+ { title = Just "ParserK" }
374+ (makeGraphs "parserK")
375+ "charts/parserK/results.csv"
376+ "charts/parserK"
370377 FileIO -> benchShow opts cfg
371378 { title = Just "File IO" }
372379 (makeGraphs "fileIO")
benchmark/Streamly/Benchmark/Data/Parser.hs+15−5
@@ -178,13 +178,9 @@ o_1_space_serial_parse value =
178178 , benchIOSink value "all" $ all value
179179 , benchIOSink value "take" $ take value
180180 , benchIOSink value "takeWhile" $ takeWhile value
181− , benchIOSink value "lookAhead" $ lookAhead value
182181 , benchIOSink value "split (all,any)" $ splitAllAny value
183182 , benchIOSink value "many" many
184183 , benchIOSink value "some" some
185− , benchIOSink value "manyAlt" manyAlt
186− , benchIOSink value "someAlt" someAlt
187− , benchIOSink value "manyTill" $ manyTill value
188184 , benchIOSink value "choice/100" $ choice (value `div` 100)
189185 , benchIOSink value "tee (all,any)" $ teeAllAny value
190186 , benchIOSink value "teeFst (all,any)" $ teeFstAllAny value
@@ -194,6 +190,14 @@ o_1_space_serial_parse value =
194190 , benchIOSink value "sequence/100" $ sequence (value `div` 100)
195191 ]
196192
193+o_1_heap_serial_parse :: Int -> [Benchmark]
194+o_1_heap_serial_parse value =
195+ [ benchIOSink value "lookAhead" $ lookAhead value
196+ , benchIOSink value "manyAlt" manyAlt
197+ , benchIOSink value "someAlt" someAlt
198+ , benchIOSink value "manyTill" $ manyTill value
199+ ]
200+
197201-------------------------------------------------------------------------------
198202-- Driver
199203-------------------------------------------------------------------------------
@@ -206,10 +210,16 @@ main = do
206210 where
207211
208212 allBenchmarks value =
209− [ bgroup "o1"
213+ [ bgroup "o-1-space"
210214 [ bgroup "parser" $ concat
211215 [
212216 o_1_space_serial_parse value
213217 ]
214218 ]
219+ , bgroup "o-n-heap"
220+ [ bgroup "parser" $ concat
221+ [
222+ o_1_heap_serial_parse value
223+ ]
224+ ]
215225 ]
benchmark/Streamly/Benchmark/Data/ParserK.hsnew file+157
@@ -0,0 +1,157 @@
1+-- |
2+-- Module : Streamly.Benchmark.Data.ParserK
3+-- Copyright : (c) 2020 Composewell Technologies
4+--
5+-- License : MIT
6+-- Maintainer : streamly@composewell.com
7+
8+{-# LANGUAGE FlexibleContexts #-}
9+
10+module Main
11+ (
12+ main
13+ ) where
14+
15+import Control.DeepSeq (NFData(..))
16+import Control.Monad.Catch (MonadCatch)
17+import Data.Foldable (asum)
18+import System.Random (randomRIO)
19+import Prelude hiding (any, all, take, sequence, sequenceA, takeWhile)
20+
21+import qualified Data.Traversable as TR
22+import qualified Control.Applicative as AP
23+import qualified Streamly as S hiding (runStream)
24+import qualified Streamly.Prelude as S
25+import qualified Streamly.Internal.Data.ParserK.Types as PR
26+import qualified Streamly.Internal.Data.Parser as PRD
27+import qualified Streamly.Internal.Prelude as IP
28+
29+import Gauge
30+import Streamly hiding (runStream)
31+import Streamly.Benchmark.Common
32+
33+-------------------------------------------------------------------------------
34+-- Utilities
35+-------------------------------------------------------------------------------
36+
37+-- XXX these can be moved to the common module
38+
39+-- We need a monadic bind here to make sure that the function f does not get
40+-- completely optimized out by the compiler in some cases.
41+
42+{-# INLINE sourceUnfoldrM #-}
43+sourceUnfoldrM :: (S.IsStream t, S.MonadAsync m) => Int -> Int -> t m Int
44+sourceUnfoldrM value n = S.unfoldrM step n
45+ where
46+ step cnt =
47+ if cnt > n + value
48+ then return Nothing
49+ else return (Just (cnt, cnt + 1))
50+
51+-- | Takes a fold method, and uses it with a default source.
52+{-# INLINE benchIOSink #-}
53+benchIOSink
54+ :: (IsStream t, NFData b)
55+ => Int -> String -> (t IO Int -> IO b) -> Benchmark
56+benchIOSink value name f =
57+ bench name $ nfIO $ randomRIO (1,1) >>= f . sourceUnfoldrM value
58+
59+-------------------------------------------------------------------------------
60+-- Parsers
61+-------------------------------------------------------------------------------
62+
63+{-# INLINE satisfy #-}
64+satisfy :: MonadCatch m => (a -> Bool) -> PR.Parser m a a
65+satisfy = PRD.toParserK . PRD.satisfy
66+
67+{-# INLINE any #-}
68+any :: MonadCatch m => (a -> Bool) -> PR.Parser m a Bool
69+any = PRD.toParserK . PRD.any
70+
71+{-# INLINE anyK #-}
72+anyK :: (MonadCatch m, Ord a) => a -> SerialT m a -> m Bool
73+anyK value = IP.parseK (any (> value))
74+
75+{-# INLINE splitApp #-}
76+splitApp :: MonadCatch m
77+ => Int -> SerialT m Int -> m (Bool, Bool)
78+splitApp value =
79+ IP.parseK ((,) <$> any (>= (value `div` 2)) <*> any (> value))
80+
81+{-# INLINE sequenceA #-}
82+sequenceA :: MonadCatch m => Int -> SerialT m Int -> m Int
83+sequenceA value xs = do
84+ let parser = satisfy (> 0)
85+ list = Prelude.replicate value parser
86+ x <- IP.parseK (TR.sequenceA list) xs
87+ return $ Prelude.length x
88+
89+{-# INLINE sequence #-}
90+sequence :: MonadCatch m => Int -> SerialT m Int -> m Int
91+sequence value xs = do
92+ let parser = satisfy (> 0)
93+ list = Prelude.replicate value parser
94+ x <- IP.parseK (TR.sequence list) xs
95+ return $ Prelude.length x
96+
97+{-# INLINE manyAlt #-}
98+manyAlt :: MonadCatch m => SerialT m Int -> m Int
99+manyAlt xs = do
100+ x <- IP.parseK (AP.many (satisfy (> 0))) xs
101+ return $ Prelude.length x
102+
103+{-# INLINE someAlt #-}
104+someAlt :: MonadCatch m => SerialT m Int -> m Int
105+someAlt xs = do
106+ x <- IP.parseK (AP.some (satisfy (> 0))) xs
107+ return $ Prelude.length x
108+
109+{-# INLINE choice #-}
110+choice :: MonadCatch m => Int -> SerialT m Int -> m Int
111+choice value = do
112+ IP.parseK (asum (replicate value (satisfy (< 0)))
113+ AP.<|> satisfy (> 0))
114+
115+-------------------------------------------------------------------------------
116+-- Benchmarks
117+-------------------------------------------------------------------------------
118+
119+o_1_space_serial_parse :: Int -> [Benchmark]
120+o_1_space_serial_parse value =
121+ [ benchIOSink value "any" $ anyK value
122+ , benchIOSink value "splitApp" $ splitApp value
123+ ]
124+
125+-- O(n) heap beacuse of accumulation of the list in strict IO monad?
126+o_n_heap_serial_parse :: Int -> [Benchmark]
127+o_n_heap_serial_parse value =
128+ [ benchIOSink value "sequenceA" $ sequenceA value
129+ , benchIOSink value "sequence" $ sequence value
130+ , benchIOSink value "manyAlt" manyAlt
131+ , benchIOSink value "someAlt" someAlt
132+ , benchIOSink value "choice" $ choice value
133+ ]
134+
135+-------------------------------------------------------------------------------
136+-- Driver
137+-------------------------------------------------------------------------------
138+
139+main :: IO ()
140+main = do
141+ (value, cfg, benches) <- parseCLIOpts defaultStreamSize
142+ value `seq` runMode (mode cfg) cfg benches (allBenchmarks value)
143+
144+ where
145+
146+ allBenchmarks value =
147+ [ bgroup "o-1-space"
148+ [ bgroup "parserK" $ concat
149+ [ o_1_space_serial_parse value
150+ ]
151+ ]
152+ , bgroup "o-n-heap"
153+ [ bgroup "parserK" $ concat
154+ [ o_n_heap_serial_parse value
155+ ]
156+ ]
157+ ]
benchmark/streamly-benchmarks.cabal+12
@@ -259,6 +259,18 @@ benchmark parser
259259 buildable: True
260260 build-depends: exceptions >= 0.8 && < 0.11
261261
262+benchmark parserK
263+ import: bench-options
264+ type: exitcode-stdio-1.0
265+ ghc-options: -with-rtsopts "-T -K128K -M32M"
266+ hs-source-dirs: Streamly/Benchmark/Data
267+ main-is: ParserK.hs
268+ if impl(ghcjs)
269+ buildable: False
270+ else
271+ buildable: True
272+ build-depends: exceptions >= 0.8 && < 0.11
273+
262274-------------------------------------------------------------------------------
263275-- Raw Streams
264276-------------------------------------------------------------------------------
src/Streamly/Internal/Data/Parser.hs+14
@@ -43,6 +43,7 @@ module Streamly.Internal.Data.Parser
4343 -- First order parsers
4444 -- * Accumulators
4545 , fromFold
46+ , toParserK
4647 , any
4748 , all
4849 , yield
@@ -182,6 +183,9 @@ import Prelude
182183
183184import Streamly.Internal.Data.Fold.Types (Fold(..))
184185
186+import qualified Streamly.Internal.Data.ParserK.Types as K
187+import qualified Streamly.Internal.Data.Zipper as Z
188+
185189import Streamly.Internal.Data.Parser.Tee
186190import Streamly.Internal.Data.Parser.Types
187191import Streamly.Internal.Data.Strict
@@ -200,6 +204,16 @@ fromFold (Fold fstep finitial fextract) = Parser step finitial fextract
200204
201205 step s a = Yield 0 <$> fstep s a
202206
207+-------------------------------------------------------------------------------
208+-- Convert to CPS style parser representation
209+-------------------------------------------------------------------------------
210+
211+{-# INLINE toParserK #-}
212+toParserK :: MonadCatch m => Parser m a b -> K.Parser m a b
213+toParserK (Parser step initial extract) =
214+ K.MkParser $ \inp yieldk ->
215+ Z.parse step initial extract inp >>= yieldk
216+
203217-------------------------------------------------------------------------------
204218-- Terminating but not failing folds
205219-------------------------------------------------------------------------------
src/Streamly/Internal/Data/ParserK/Types.hsnew file+99
@@ -0,0 +1,99 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE RankNTypes #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+
6+#include "inline.hs"
7+
8+-- |
9+-- Module : Streamly.ParserK.Types
10+-- Copyright : (c) 2020 Composewell Technologies
11+-- License : BSD3
12+-- Maintainer : streamly@composewell.com
13+-- Stability : experimental
14+-- Portability : GHC
15+--
16+-- CPS style implementation of parsers.
17+
18+module Streamly.Internal.Data.ParserK.Types
19+ (
20+ Parser (..)
21+ )
22+where
23+
24+import Control.Monad (MonadPlus(..), ap)
25+import Control.Applicative (Alternative(..))
26+import Streamly.Internal.Data.Zipper (Zipper)
27+import qualified Streamly.Internal.Data.Zipper as Z
28+
29+newtype Parser m a b =
30+ MkParser { runParser :: forall r.
31+ Zipper m a -> ((Zipper m a, Either String b) -> m r) -> m r }
32+
33+-- | Maps a function over the output of the fold.
34+--
35+instance Functor m => Functor (Parser m a) where
36+ {-# INLINE fmap #-}
37+ fmap f parser =
38+ MkParser $ \inp yieldk -> runParser parser inp (yieldk . fmap (fmap f))
39+
40+{-# INLINE yield #-}
41+yield :: b -> Parser m a b
42+yield b = MkParser (\_ yieldk -> yieldk (Z.nil, Right b))
43+
44+-------------------------------------------------------------------------------
45+-- Sequential applicative
46+-------------------------------------------------------------------------------
47+--
48+-- | 'Applicative' form of 'splitWith'.
49+instance Monad m => Applicative (Parser m a) where
50+ {-# INLINE pure #-}
51+ pure = yield
52+
53+ {-# INLINE (<*>) #-}
54+ (<*>) = ap
55+
56+{-# INLINE die #-}
57+die :: String -> Parser m a b
58+die err = MkParser (\z yieldk -> yieldk (z, Left err))
59+
60+instance Monad m => Monad (Parser m a) where
61+ {-# INLINE return #-}
62+ return = pure
63+
64+ {-# INLINE (>>=) #-}
65+ m >>= k = MkParser $ \inp yieldk ->
66+ let yield1 (z, b) = case b of
67+ Right x -> runParser (k x) z yieldk
68+ Left err -> runParser (die err) z yieldk
69+ in runParser m inp yield1
70+
71+-- | 'Alternative' instance using 'alt'.
72+--
73+-- The "some" and "many" operations of alternative collect results in a pure
74+-- list which is not scalable and streaming. Instead use the "splitParse"
75+-- operation to apply a 'Parser' repeatedly on a stream in a scalable manner.
76+--
77+instance Monad m => Alternative (Parser m a) where
78+ {-# INLINE empty #-}
79+ empty = die "empty"
80+
81+ {-# INLINE (<|>) #-}
82+ m1 <|> m2 = MkParser $ \inp yieldk ->
83+ let yield1 (z, b) = case b of
84+ Right _ -> yieldk (Z.release z, b)
85+ Left _ -> runParser m2 (Z.restore z) yieldk
86+ in runParser m1 (Z.checkpoint inp) yield1
87+ -- XXX INLINE some and many?
88+
89+-- | 'mzero' is same as 'empty', it aborts the parser. 'mplus' is same as
90+-- '<|>', it selects the first succeeding parser.
91+--
92+-- /Internal/
93+--
94+instance Monad m => MonadPlus (Parser m a) where
95+ {-# INLINE mzero #-}
96+ mzero = die "mzero"
97+
98+ {-# INLINE mplus #-}
99+ mplus = (<|>)
src/Streamly/Internal/Data/Zipper.hsnew file+380
@@ -0,0 +1,380 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+
7+#include "inline.hs"
8+
9+-- |
10+-- Module : Streamly.Internal.Data.Zipper
11+-- Copyright : (c) 2020 Composewell Technologies
12+-- License : BSD3
13+-- Maintainer : streamly@composewell.com
14+-- Stability : experimental
15+-- Portability : GHC
16+--
17+-- A special zipper derived from a stream to facilitate stream parsing.
18+--
19+-- = Zipper
20+--
21+-- The zipper is designed to enable moving backward and forward in the stream
22+-- efficiently to be able to implement backtracking parsers. The zipper can
23+-- buffer data until the parser indicates that it is not needed, we can move
24+-- back in the buffered data if the parser needs to backtrack.
25+--
26+-- = Checkpointing
27+--
28+-- We have a checkpointing mechanism built into the zipper to handle
29+-- backtracking. Whenever we enter a computation that can fail and has an
30+-- alternative to run then we add a checkpoint to the zipper. The checkpoint
31+-- ensures that we do not release the buffer upto the checkpoint until this
32+-- computation succeeds, if the computation fails we can backtrack to focus the
33+-- cursor back to the checkpoint position.
34+--
35+-- = Future Work
36+--
37+-- We can possibly have a typeclass providing a zipper interface so that we can
38+-- derive a zipper from different data sources efficiently. For example,
39+--
40+-- * stream zipper
41+-- * zippers from different types of arrays
42+-- * zipper from a tree of arrays for dynamic in-memory storage
43+-- * file handle zipper can utilize the seek to go back and forth in the file
44+--
45+-- A tree of zippers where a lower level zipper is dependent on (references
46+-- data in) a higher level zipper could be useful in implementing distributive
47+-- parsers.
48+--
49+-- See https://hackage.haskell.org/package/zipper for an existing type class.
50+-- We need a monadic one.
51+
52+module Streamly.Internal.Data.Zipper
53+ (
54+ Zipper (..)
55+
56+ -- * Construction
57+ , nil
58+ , fromStream
59+ , fromList
60+
61+ -- * Checkpointing
62+ , checkpoint
63+ , release
64+ , restore
65+
66+ -- * Parsing
67+ , parse
68+ )
69+where
70+
71+import Control.Exception (assert, Exception(..))
72+import Control.Monad.Catch (MonadCatch, try, SomeException)
73+-- import GHC.Types (SPEC(..))
74+import Prelude hiding (splitAt)
75+
76+import Streamly.Internal.Data.Stream.StreamK.Type (Stream(..))
77+import Streamly.Internal.Data.SVar (defState)
78+
79+import qualified Streamly.Internal.Data.Parser.Types as PR
80+import qualified Streamly.Internal.Data.Stream.StreamK as K
81+
82+-------------------------------------------------------------------------------
83+-- Zipper type
84+-------------------------------------------------------------------------------
85+
86+-- | @Zipper checkpoints lefts rights tail@. The focus is on the first element
87+-- of @rights@. @lefts@ is buffered data on the right of the cursor. @tail@
88+-- is a stream that is used to generate more data if the cursor moves past
89+-- @rights@.
90+--
91+-- @checkpoints@ is a stack of checkpoints. A new checkpoint is created by a
92+-- @checkpoint@ operation. A checkpoint consists of a count that tracks how
93+-- many elements we have yielded after the checkpoint was taken. We need this
94+-- information to backtrack to the checkpoint. If we enter a nested
95+-- alternative we add another checkpoint in the stack. When we exit an
96+-- alternative we call a @release@ on the checkpoint. The @release@ removes the
97+-- checkpoint from the stack and adds its element count to the previous
98+-- checkpoint in the stack. When the last checkpoint is removed the buffer is
99+-- released.
100+--
101+-- /Internal/
102+--
103+data Zipper m a = Zipper [Int] [a] [a] (Stream m a)
104+
105+-------------------------------------------------------------------------------
106+-- Construction
107+-------------------------------------------------------------------------------
108+
109+-- | Empty zipper.
110+--
111+-- /Internal/
112+--
113+{-# INLINE nil #-}
114+nil :: Zipper m a
115+nil = Zipper [] [] [] K.nil
116+
117+-- | Create a zipper from a stream.
118+--
119+-- /Internal/
120+--
121+{-# INLINE fromStream #-}
122+fromStream :: Stream m a -> Zipper m a
123+fromStream = Zipper [] [] []
124+
125+-- | Create a zipper from a list.
126+--
127+-- /Internal/
128+--
129+{-# INLINE fromList #-}
130+fromList :: [a] -> Zipper m a
131+fromList xs = Zipper [] [] xs K.nil
132+
133+-- | Add a checkpoint to the Zipper so that we do not release the buffer beyond
134+-- the checkpoint.
135+--
136+-- /Internal/
137+--
138+{-# INLINE checkpoint #-}
139+checkpoint :: Zipper m a -> Zipper m a
140+checkpoint (Zipper cps xs ys stream) = Zipper (0:cps) xs ys stream
141+
142+-- | Release the latest checkpoint, releases any values held by the checkpoint.
143+--
144+-- /Internal/
145+--
146+{-# INLINE release #-}
147+release :: Zipper m a -> Zipper m a
148+release (Zipper [] _ _ _) = error "Bug: release, no checkpoint exists!"
149+release (Zipper (n:cps) xs ys stream) =
150+ assert (n <= length xs) $
151+ case cps of
152+ [] -> assert (n == length xs) $ Zipper [] [] ys stream
153+ cp:rest -> Zipper ((cp + n) : rest) xs ys stream
154+
155+-- | Rewind to restore the cursor to the latest checkpoint.
156+--
157+-- /Internal/
158+--
159+{-# INLINE restore #-}
160+restore :: Zipper m a -> Zipper m a
161+restore (Zipper [] _ _ _) = error "Bug: restore, no checkpoint exists!"
162+restore (Zipper (n:cps) xs ys stream) =
163+ assert (n <= length xs) $
164+ let (src0, buf1) = splitAt n xs
165+ src = Prelude.reverse src0
166+ in Zipper cps buf1 (src ++ ys) stream
167+
168+-------------------------------------------------------------------------------
169+-- Parse driver for a Zipper
170+-------------------------------------------------------------------------------
171+
172+-- Inlined definition. Without the inline "serially/parser/take" benchmark
173+-- degrades and splitParse does not fuse. Even using "inline" at the callsite
174+-- does not help.
175+{-# INLINE splitAt #-}
176+splitAt :: Int -> [a] -> ([a],[a])
177+splitAt n ls
178+ | n <= 0 = ([], ls)
179+ | otherwise = splitAt' n ls
180+ where
181+ splitAt' :: Int -> [a] -> ([a], [a])
182+ splitAt' _ [] = ([], [])
183+ splitAt' 1 (x:xs) = ([x], xs)
184+ splitAt' m (x:xs) = (x:xs', xs'')
185+ where
186+ (xs', xs'') = splitAt' (m - 1) xs
187+
188+-- | Parse a stream zipper using a parser's @step@, @initial@ and @extract@
189+-- functions.
190+--
191+{-# INLINE_NORMAL parse #-}
192+parse
193+ :: MonadCatch m
194+ => (s -> a -> m (PR.Step s b))
195+ -> m s
196+ -> (s -> m b)
197+ -> Zipper m a
198+ -> m (Zipper m a, Either String b)
199+
200+-- The case when no checkpoints exist
201+parse pstep initial extract (Zipper [] ls rs stream) =
202+ case rs of
203+ [] -> go stream ls initial
204+ _ -> gobuf stream ls rs initial
205+
206+ where
207+
208+ {-# INLINE go #-}
209+ go st buf !acc =
210+ let stop = do
211+ pst <- acc
212+ r <- try $ extract pst
213+ return $ case r of
214+ Left (e :: SomeException) ->
215+ (nil, Left (displayException e))
216+ Right b -> (nil, Right b)
217+ single x = do
218+ acc1 <- acc >>= \b -> pstep b x
219+ case acc1 of
220+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
221+ PR.Yield n pst1 -> do
222+ assert (n <= length (x:buf)) (return ())
223+ go K.nil (Prelude.take n (x:buf)) (return pst1)
224+ PR.Skip 0 pst1 -> go K.nil (x:buf) (return pst1)
225+ PR.Skip n pst1 -> do
226+ assert (n <= length (x:buf)) (return ())
227+ let (src0, buf1) = splitAt n (x:buf)
228+ src = Prelude.reverse src0
229+ gobuf K.nil buf1 src (return pst1)
230+ PR.Stop n b -> do
231+ assert (n <= length (x:buf)) (return ())
232+ let (src0, buf1) = splitAt n (x:buf)
233+ src = Prelude.reverse src0
234+ return (Zipper [] buf1 src K.nil, Right b)
235+ PR.Error err ->
236+ return (Zipper [] (x:buf) [] K.nil, Left err)
237+ yieldk x r = do
238+ acc1 <- acc >>= \b -> pstep b x
239+ case acc1 of
240+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
241+ PR.Yield n pst1 -> do
242+ assert (n <= length (x:buf)) (return ())
243+ go r (Prelude.take n (x:buf)) (return pst1)
244+ PR.Skip 0 pst1 -> go r (x:buf) (return pst1)
245+ PR.Skip n pst1 -> do
246+ assert (n <= length (x:buf)) (return ())
247+ let (src0, buf1) = splitAt n (x:buf)
248+ src = Prelude.reverse src0
249+ gobuf r buf1 src (return pst1)
250+ PR.Stop n b -> do
251+ assert (n <= length (x:buf)) (return ())
252+ let (src0, buf1) = splitAt n (x:buf)
253+ src = Prelude.reverse src0
254+ return (Zipper [] buf1 src r, Right b)
255+ PR.Error err ->
256+ return (Zipper [] (x:buf) [] r, Left err)
257+ in K.foldStream defState yieldk single stop st
258+
259+ gobuf s buf [] !pst = go s buf pst
260+ gobuf s buf (x:xs) !pst = do
261+ r <- pst
262+ pRes <- pstep r x
263+ case pRes of
264+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
265+ PR.Yield n pst1 -> do
266+ assert (n <= length (x:buf)) (return ())
267+ gobuf s (Prelude.take n (x:buf)) xs (return pst1)
268+ PR.Skip 0 pst1 -> gobuf s (x:buf) xs (return pst1)
269+ PR.Skip n pst1 -> do
270+ assert (n <= length (x:buf)) (return ())
271+ let (src0, buf1) = splitAt n (x:buf)
272+ src = Prelude.reverse src0 ++ xs
273+ gobuf s buf1 src (return pst1)
274+ PR.Stop n b -> do
275+ assert (n <= length (x:buf)) (return ())
276+ let (src0, buf1) = splitAt n (x:buf)
277+ src = Prelude.reverse src0 ++ xs
278+ return (Zipper [] buf1 src s, Right b)
279+ PR.Error err ->
280+ return (Zipper [] (x:buf) xs s, Left err)
281+
282+-- The case when checkpoints exist
283+-- XXX code duplication alert!
284+parse pstep initial extract (Zipper (cp:cps) ls rs stream) =
285+ case rs of
286+ [] -> go 0 stream ls initial
287+ _ -> gobuf 0 stream ls rs initial
288+
289+ where
290+
291+ {-# INLINE go #-}
292+ go cnt st buf !acc =
293+ let stop = do
294+ pst <- acc
295+ r <- try $ extract pst
296+ return $ case r of
297+ Left (e :: SomeException) ->
298+ (nil, Left (displayException e))
299+ Right b -> (nil, Right b)
300+ single x = do
301+ acc1 <- acc >>= \b -> pstep b x
302+ let cnt1 = cnt + 1
303+ case acc1 of
304+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
305+ PR.Yield n pst1 -> do
306+ assert (n <= length (x:buf)) (return ())
307+ go cnt1 K.nil (x:buf) (return pst1)
308+ PR.Skip 0 pst1 -> go cnt1 K.nil (x:buf) (return pst1)
309+ PR.Skip n pst1 -> do
310+ assert (n <= length (x:buf)) (return ())
311+ let (src0, buf1) = splitAt n (x:buf)
312+ src = Prelude.reverse src0
313+ assert (cnt1 - n >= 0) (return ())
314+ gobuf (cnt1 - n) K.nil buf1 src (return pst1)
315+ PR.Stop n b -> do
316+ assert (n <= length (x:buf)) (return ())
317+ let (src0, buf1) = splitAt n (x:buf)
318+ src = Prelude.reverse src0
319+ assert (cp + cnt1 - n >= 0) (return ())
320+ return ( Zipper (cp + cnt1 - n : cps) buf1 src K.nil
321+ , Right b
322+ )
323+ PR.Error err ->
324+ return ( Zipper (cp + cnt1 : cps) (x:buf) [] K.nil
325+ , Left err
326+ )
327+ yieldk x r = do
328+ acc1 <- acc >>= \b -> pstep b x
329+ let cnt1 = cnt + 1
330+ case acc1 of
331+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
332+ PR.Yield n pst1 -> do
333+ assert (n <= length (x:buf)) (return ())
334+ go cnt1 r (x:buf) (return pst1)
335+ PR.Skip 0 pst1 -> go cnt1 r (x:buf) (return pst1)
336+ PR.Skip n pst1 -> do
337+ assert (n <= length (x:buf)) (return ())
338+ let (src0, buf1) = splitAt n (x:buf)
339+ src = Prelude.reverse src0
340+ assert (cnt1 - n >= 0) (return ())
341+ gobuf (cnt1 - n) r buf1 src (return pst1)
342+ PR.Stop n b -> do
343+ assert (n <= length (x:buf)) (return ())
344+ let (src0, buf1) = splitAt n (x:buf)
345+ src = Prelude.reverse src0
346+ assert (cp + cnt1 - n >= 0) (return ())
347+ return ( Zipper (cp + cnt1 - n : cps) buf1 src r
348+ , Right b
349+ )
350+ PR.Error err ->
351+ return ( Zipper (cp + cnt1 : cps) (x:buf) [] r
352+ , Left err
353+ )
354+ in K.foldStream defState yieldk single stop st
355+
356+ gobuf cnt s buf [] !pst = go cnt s buf pst
357+ gobuf cnt s buf (x:xs) !pst = do
358+ r <- pst
359+ pRes <- pstep r x
360+ let cnt1 = cnt + 1
361+ case pRes of
362+ -- PR.Yield 0 pst1 -> go SPEC s [] pst1
363+ PR.Yield n pst1 -> do
364+ assert (n <= length (x:buf)) (return ())
365+ gobuf cnt1 s (x:buf) xs (return pst1)
366+ PR.Skip 0 pst1 -> gobuf cnt1 s (x:buf) xs (return pst1)
367+ PR.Skip n pst1 -> do
368+ assert (n <= length (x:buf)) (return ())
369+ let (src0, buf1) = splitAt n (x:buf)
370+ src = Prelude.reverse src0 ++ xs
371+ assert (cnt1 - n >= 0) (return ())
372+ gobuf (cnt1 - n) s buf1 src (return pst1)
373+ PR.Stop n b -> do
374+ assert (n <= length (x:buf)) (return ())
375+ let (src0, buf1) = splitAt n (x:buf)
376+ src = Prelude.reverse src0 ++ xs
377+ assert (cp + cnt1 - n >= 0) (return ())
378+ return (Zipper (cp + cnt1 - n : cps) buf1 src s, Right b)
379+ PR.Error err ->
380+ return (Zipper (cp + cnt1 : cps) (x:buf) xs s, Left err)
src/Streamly/Internal/Prelude.hs+12
@@ -93,6 +93,7 @@ module Streamly.Internal.Prelude
9393 -- ** Composable Left Folds
9494 , fold
9595 , parse
96+ , parseK
9697
9798 -- ** Concurrent Folds
9899 , foldAsync
@@ -576,6 +577,8 @@ import qualified Streamly.Internal.Data.Stream.StreamD as S
576577import qualified Streamly.Internal.Data.Stream.Serial as Serial
577578import qualified Streamly.Internal.Data.Stream.Parallel as Par
578579import qualified Streamly.Internal.Data.Stream.Zip as Z
580+import qualified Streamly.Internal.Data.ParserK.Types as PRK
581+import qualified Streamly.Internal.Data.Zipper as ZR
579582
580583------------------------------------------------------------------------------
581584-- Deconstruction
@@ -1205,6 +1208,15 @@ runSink = fold . toFold
12051208parse :: MonadThrow m => Parser m a b -> SerialT m a -> m b
12061209parse (Parser step initial extract) = P.parselMx' step initial extract
12071210
1211+{-# INLINE parseK #-}
1212+parseK :: Monad m => PRK.Parser m a b -> SerialT m a -> m b
1213+parseK parser xs = do
1214+ r <- PRK.runParser parser (ZR.fromStream $ K.toStream xs)
1215+ (\(_, b) -> return b)
1216+ case r of
1217+ Left err -> error err
1218+ Right b -> return b
1219+
12081220------------------------------------------------------------------------------
12091221-- Specialized folds
12101222------------------------------------------------------------------------------
streamly.cabal+2
@@ -325,6 +325,8 @@ library
325325 , Streamly.Internal.Memory.ArrayStream
326326 , Streamly.Internal.Data.Fold.Types
327327 , Streamly.Internal.Data.Fold
328+ , Streamly.Internal.Data.Zipper
329+ , Streamly.Internal.Data.ParserK.Types
328330 , Streamly.Internal.Data.Parser
329331 , Streamly.Internal.Data.Parser.Types
330332 , Streamly.Internal.Data.Parser.Tee