on master help sign in
Add resumable unfolds for efficient nested parsing
22c83f4Harendra Kumar
benchmark/Streamly/Benchmark/Data/Parser.hs+32−2
@@ -35,9 +35,12 @@ import qualified Data.Foldable as F
35import qualified Control.Applicative as AP
35import qualified Control.Applicative as AP
36import qualified Streamly.FileSystem.Handle as Handle
36import qualified Streamly.FileSystem.Handle as Handle
37import qualified Streamly.Prelude as S
37import qualified Streamly.Prelude as S
38+import qualified Streamly.Internal.Data.Array.Foreign as Array
38import qualified Streamly.Internal.Data.Fold as FL
39import qualified Streamly.Internal.Data.Fold as FL
39import qualified Streamly.Internal.Data.Parser as PR
40import qualified Streamly.Internal.Data.Parser as PR
40import qualified Streamly.Internal.Data.Stream.IsStream as IP
41import qualified Streamly.Internal.Data.Stream.IsStream as IP
42+import qualified Streamly.Internal.Data.Unfold.Resume as UnfoldR
43+import qualified Streamly.Internal.Data.Unfold.Source as Source
41
44
42import Gauge hiding (env)
45import Gauge hiding (env)
43import Streamly.Prelude (SerialT)
46import Streamly.Prelude (SerialT)
@@ -235,6 +238,22 @@ parseManyChunksOfSum n inh =
235 (PR.fromFold $ FL.takeLE n FL.sum)
238 (PR.fromFold $ FL.takeLE n FL.sum)
236 (S.unfold Handle.read inh)
239 (S.unfold Handle.read inh)
237
240
241+-------------------------------------------------------------------------------
242+-- Parsing with unfolds
243+-------------------------------------------------------------------------------
244+
245+{-# INLINE parseManyUnfoldArrays #-}
246+parseManyUnfoldArrays :: Int -> [Array.Array Int] -> IO ()
247+parseManyUnfoldArrays count arrays = do
248+ let src = Source.source (arrays, Nothing)
249+ let parser = PR.fromFold (FL.takeLE count FL.drain)
250+ let readSrc =
251+ Source.read
252+ $ UnfoldR.concat UnfoldR.fromList Array.readResumable
253+ let streamParser =
254+ UnfoldR.simplify (UnfoldR.parseMany parser readSrc)
255+ S.drain $ S.unfold streamParser src
256+
238-------------------------------------------------------------------------------
257-------------------------------------------------------------------------------
239-- Parsers in which -fspec-constr-recursive=16 is problematic
258-- Parsers in which -fspec-constr-recursive=16 is problematic
240-------------------------------------------------------------------------------
259-------------------------------------------------------------------------------
@@ -338,6 +357,14 @@ o_1_space_filesystem env =
338 $ \inh _ -> inline parseManyChunksOfSum 1 inh
357 $ \inh _ -> inline parseManyChunksOfSum 1 inh
339 ]
358 ]
340
359
360+o_1_space_serial_unfold :: Int -> [Array.Array Int] -> [Benchmark]
361+o_1_space_serial_unfold bound arrays =
362+ [ bench "parseMany/Unfold/1000 arrays/1 parse"
363+ $ nfIO $ parseManyUnfoldArrays bound arrays
364+ , bench "parseMany/Unfold/1000 arrays/100000 parses"
365+ $ nfIO $ parseManyUnfoldArrays 1 arrays
366+ ]
367+
341o_n_heap_serial :: Int -> [Benchmark]
368o_n_heap_serial :: Int -> [Benchmark]
342o_n_heap_serial value =
369o_n_heap_serial value =
343 [
370 [
@@ -370,14 +397,17 @@ main :: IO ()
370main = do
397main = do
371 (value, cfg, benches) <- parseCLIOpts defaultStreamSize
398 (value, cfg, benches) <- parseCLIOpts defaultStreamSize
372 env <- mkHandleBenchEnv
399 env <- mkHandleBenchEnv
400+ arrays <- IP.toList $ IP.arraysOf 100 $ sourceUnfoldrM value 0
373− value `seq` runMode (mode cfg) cfg benches (allBenchmarks env value)
401+ value `seq` runMode (mode cfg) cfg benches (allBenchmarks env value arrays)
374
402
375 where
403 where
376
404
377− allBenchmarks env value =
405+ allBenchmarks env value arrays =
378 [ bgroup (o_1_space_prefix moduleName) (o_1_space_serial value)
406 [ bgroup (o_1_space_prefix moduleName) (o_1_space_serial value)
379 , bgroup
407 , bgroup
380 (o_1_space_prefix moduleName ++ "/filesystem")
408 (o_1_space_prefix moduleName ++ "/filesystem")
381 (o_1_space_filesystem env)
409 (o_1_space_filesystem env)
410+ , bgroup (o_1_space_prefix moduleName)
411+ (o_1_space_serial_unfold value arrays)
382 , bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
412 , bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
383 ]
413 ]
benchmark/Streamly/Benchmark/Data/Parser/ParserD.hs+32−2
@@ -26,9 +26,12 @@ import qualified Data.Traversable as TR
26import qualified Data.Foldable as F
26import qualified Data.Foldable as F
27import qualified Control.Applicative as AP
27import qualified Control.Applicative as AP
28import qualified Streamly.Prelude as S
28import qualified Streamly.Prelude as S
29+import qualified Streamly.Internal.Data.Array.Foreign as Array
29import qualified Streamly.Internal.Data.Fold as FL
30import qualified Streamly.Internal.Data.Fold as FL
30import qualified Streamly.Internal.Data.Parser.ParserD as PR
31import qualified Streamly.Internal.Data.Parser.ParserD as PR
31import qualified Streamly.Internal.Data.Stream.IsStream as IP
32import qualified Streamly.Internal.Data.Stream.IsStream as IP
33+import qualified Streamly.Internal.Data.Unfold.Resume as UnfoldR
34+import qualified Streamly.Internal.Data.Unfold.Source as Source
32
35
33import Gauge
36import Gauge
34import Streamly.Prelude (SerialT, MonadAsync, IsStream)
37import Streamly.Prelude (SerialT, MonadAsync, IsStream)
@@ -209,6 +212,22 @@ parseManyGroupsRolling :: MonadThrow m => Bool -> SerialT m Int -> m ()
209parseManyGroupsRolling b =
212parseManyGroupsRolling b =
210 IP.drain . IP.parseManyD (PR.groupByRolling (\_ _ -> b) FL.drain)
213 IP.drain . IP.parseManyD (PR.groupByRolling (\_ _ -> b) FL.drain)
211
214
215+-------------------------------------------------------------------------------
216+-- Parsing with unfolds
217+-------------------------------------------------------------------------------
218+
219+{-# INLINE parseManyUnfoldArrays #-}
220+parseManyUnfoldArrays :: Int -> [Array.Array Int] -> IO ()
221+parseManyUnfoldArrays count arrays = do
222+ let src = Source.source (arrays, Nothing)
223+ let parser = PR.fromFold (FL.takeLE count FL.drain)
224+ let readSrc =
225+ Source.read
226+ $ UnfoldR.concat UnfoldR.fromList Array.readResumable
227+ let streamParser =
228+ UnfoldR.simplify (UnfoldR.parseManyD parser readSrc)
229+ S.drain $ S.unfold streamParser src
230+
212-------------------------------------------------------------------------------
231-------------------------------------------------------------------------------
213-- Parsers in which -fspec-constr-recursive=16 is problematic
232-- Parsers in which -fspec-constr-recursive=16 is problematic
214-------------------------------------------------------------------------------
233-------------------------------------------------------------------------------
@@ -305,6 +324,14 @@ o_1_space_serial_nested value =
305 $ (parseManyGroupsRolling True)
324 $ (parseManyGroupsRolling True)
306 ]
325 ]
307
326
327+o_1_space_serial_unfold :: Int -> [Array.Array Int] -> [Benchmark]
328+o_1_space_serial_unfold bound arrays =
329+ [ bench "parseMany/Unfold/1000 arrays/1 parse"
330+ $ nfIO $ parseManyUnfoldArrays bound arrays
331+ , bench "parseMany/Unfold/1000 arrays/100000 parses"
332+ $ nfIO $ parseManyUnfoldArrays 1 arrays
333+ ]
334+
308o_n_heap_serial :: Int -> [Benchmark]
335o_n_heap_serial :: Int -> [Benchmark]
309o_n_heap_serial value =
336o_n_heap_serial value =
310 [
337 [
@@ -334,14 +361,17 @@ o_n_space_serial value =
334main :: IO ()
361main :: IO ()
335main = do
362main = do
336 (value, cfg, benches) <- parseCLIOpts defaultStreamSize
363 (value, cfg, benches) <- parseCLIOpts defaultStreamSize
364+ arrays <- IP.toList $ IP.arraysOf 100 $ sourceUnfoldrM value 0
337− value `seq` runMode (mode cfg) cfg benches (allBenchmarks value)
365+ value `seq` runMode (mode cfg) cfg benches (allBenchmarks value arrays)
338
366
339 where
367 where
340
368
341− allBenchmarks value =
369+ allBenchmarks value arrays =
342 [ bgroup (o_1_space_prefix moduleName) (o_1_space_serial value)
370 [ bgroup (o_1_space_prefix moduleName) (o_1_space_serial value)
343 , bgroup (o_1_space_prefix moduleName) (o_1_space_serial_spanning value)
371 , bgroup (o_1_space_prefix moduleName) (o_1_space_serial_spanning value)
344 , bgroup (o_1_space_prefix moduleName) (o_1_space_serial_nested value)
372 , bgroup (o_1_space_prefix moduleName) (o_1_space_serial_nested value)
373+ , bgroup (o_1_space_prefix moduleName)
374+ (o_1_space_serial_unfold value arrays)
345 , bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
375 , bgroup (o_n_heap_prefix moduleName) (o_n_heap_serial value)
346 , bgroup (o_n_space_prefix moduleName) (o_n_space_serial value)
376 , bgroup (o_n_space_prefix moduleName) (o_n_space_serial value)
347 ]
377 ]
src/Streamly/Internal/Data/Array/Foreign.hs+7
@@ -68,6 +68,7 @@ module Streamly.Internal.Data.Array.Foreign
68 , A.toStream
68 , A.toStream
69 , A.toStreamRev
69 , A.toStreamRev
70 , read
70 , read
71+ , readResumable
71 , unsafeRead
72 , unsafeRead
72 , A.readRev
73 , A.readRev
73 -- , readChunksOf
74 -- , readChunksOf
@@ -170,6 +171,7 @@ import qualified Streamly.Internal.Data.Stream.Prelude as P
170import qualified Streamly.Internal.Data.Stream.Serial as Serial
171import qualified Streamly.Internal.Data.Stream.Serial as Serial
171import qualified Streamly.Internal.Data.Stream.StreamD as D
172import qualified Streamly.Internal.Data.Stream.StreamD as D
172import qualified Streamly.Internal.Data.Unfold as Unfold
173import qualified Streamly.Internal.Data.Unfold as Unfold
174+import qualified Streamly.Internal.Data.Unfold.Resume.Type as UnfoldR
173import qualified Streamly.Internal.Ring.Foreign as RB
175import qualified Streamly.Internal.Ring.Foreign as RB
174
176
175-------------------------------------------------------------------------------
177-------------------------------------------------------------------------------
@@ -212,6 +214,11 @@ fromStream = P.foldOnce A.write
212read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
214read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
213read = Unfold.lmap A.unsafeThaw MA.read
215read = Unfold.lmap A.unsafeThaw MA.read
214
216
217+{-# INLINE_NORMAL readResumable #-}
218+readResumable :: forall m a. (Monad m, Storable a) =>
219+ UnfoldR.Unfold m (Array a) a
220+readResumable = UnfoldR.lmap A.unsafeThaw A.unsafeFreeze MA.readResumable
221+
215-- | Unfold an array into a stream, does not check the end of the array, the
222-- | Unfold an array into a stream, does not check the end of the array, the
216-- user is responsible for terminating the stream within the array bounds. For
223-- user is responsible for terminating the stream within the array bounds. For
217-- high performance application where the end condition can be determined by
224-- high performance application where the end condition can be determined by
src/Streamly/Internal/Data/Array/Foreign/Mut/Types.hs+17−5
@@ -78,6 +78,7 @@ module Streamly.Internal.Data.Array.Foreign.Mut.Types
78 , ReadUState
78 , ReadUState
79 , read
79 , read
80 , readRev
80 , readRev
81+ , readResumable
81 , flattenArrays
82 , flattenArrays
82 , flattenArraysRev
83 , flattenArraysRev
83
84
@@ -146,6 +147,7 @@ import qualified GHC.Exts as Exts
146import qualified Streamly.Internal.Data.Fold.Types as FL
147import qualified Streamly.Internal.Data.Fold.Types as FL
147import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
148import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
148import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
149import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
150+import qualified Streamly.Internal.Data.Unfold.Resume as UnfoldR
149import qualified Streamly.Internal.Foreign.Malloc as Malloc
151import qualified Streamly.Internal.Foreign.Malloc as Malloc
150
152
151import Prelude hiding (length, foldr, read, unlines, splitAt)
153import Prelude hiding (length, foldr, read, unlines, splitAt)
@@ -498,12 +500,12 @@ data ReadUState a = ReadUState
498 {-# UNPACK #-} !(ForeignPtr a) -- foreign ptr with end of array pointer
500 {-# UNPACK #-} !(ForeignPtr a) -- foreign ptr with end of array pointer
499 {-# UNPACK #-} !(Ptr a) -- current pointer
501 {-# UNPACK #-} !(Ptr a) -- current pointer
500
502
501−-- | Unfold an array into a stream.
503+-- | Resumable unfold of an array.
502--
504--
503−-- @since 0.7.0
504−{-# INLINE_NORMAL read #-}
505+{-# INLINE_NORMAL readResumable #-}
505−read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
506+readResumable :: forall m a. (Monad m, Storable a) =>
507+ UnfoldR.Unfold m (Array a) a
506−read = Unfold step inject
508+readResumable = UnfoldR.Unfold step inject extract
507 where
509 where
508
510
509 inject (Array (ForeignPtr start contents) (Ptr end) _) =
511 inject (Array (ForeignPtr start contents) (Ptr end) _) =
@@ -524,6 +526,16 @@ read = Unfold step inject
524 return $ D.Yield x
526 return $ D.Yield x
525 (ReadUState fp (p `plusPtr` sizeOf (undefined :: a)))
527 (ReadUState fp (p `plusPtr` sizeOf (undefined :: a)))
526
528
529+ extract (ReadUState (ForeignPtr end contents) (Ptr p)) =
530+ return $ Just $ Array (ForeignPtr p contents) (Ptr end) (Ptr end)
531+
532+-- | Unfold an array into a stream.
533+--
534+-- @since 0.7.0
535+{-# INLINE_NORMAL read #-}
536+read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
537+read = UnfoldR.simplify readResumable
538+
527-- | Unfold an array into a stream in reverse order.
539-- | Unfold an array into a stream in reverse order.
528--
540--
529-- /Internal/
541-- /Internal/
src/Streamly/Internal/Data/Unfold/Resume.hsnew file+229
@@ -0,0 +1,229 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Unfold.Resume
3+-- Copyright : (c) 2021 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+-- Unfolds in "Streamly.Internal.Data.Unfold" module run to completion, they
10+-- cannot be stopped and then resumed. In contrast, the unfolds in this module
11+-- allow extraction of the intermediate state which can be used to resume the
12+-- unfold later on.
13+--
14+-- For simple cases the seed itself can be used to represent the intermediate
15+-- state as well. For example, if we are iterating through a list or an array
16+-- we can return the remaining list or the remaining array slice as the
17+-- state and resume from it later.
18+--
19+-- In general, the intermediate state could be different than the seed. Also,
20+-- when unfolds are nested, for example using 'concat', then each nesting layer
21+-- would have its own state.
22+--
23+-- In imperative terms, an unfold is like a for loop (or an iterator), the
24+-- state of the unfold is the state of the loop. The state of the 'concat' of
25+-- unfolds is like the state of each loop in the nesting of loops.
26+--
27+
28+module Streamly.Internal.Data.Unfold.Resume
29+ ( Unfold (..)
30+
31+ -- * Converting
32+ , simplify
33+
34+ -- * Unfolds
35+ , nil
36+ , nilM
37+ , unfoldrM
38+ , fromStreamD
39+ , fromList
40+
41+ -- * Combinators
42+ , concat
43+ , parse
44+ , parseMany
45+ , parseManyD
46+ )
47+where
48+
49+#include "inline.hs"
50+
51+import Control.Exception (assert)
52+import Control.Monad.Catch (MonadThrow, throwM)
53+import GHC.Exts (SpecConstrAnnotation(..))
54+import GHC.Types (SPEC(..))
55+import Streamly.Internal.Data.Parser.ParserD (ParseError(..), Step(..))
56+import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
57+import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..))
58+import Streamly.Internal.Data.SVar (defState)
59+import Streamly.Internal.Data.Unfold.Source (Source)
60+
61+import qualified Streamly.Internal.Data.Parser.ParserD as ParserD
62+import qualified Streamly.Internal.Data.Parser.ParserK.Types as ParserK
63+import qualified Streamly.Internal.Data.Unfold.Types as Unfold
64+import qualified Streamly.Internal.Data.Unfold.Source as Source
65+
66+import Streamly.Internal.Data.Unfold.Resume.Type
67+import Prelude hiding (concat)
68+
69+-- XXX We should write unfolds as resumable unfolds where possible and define
70+-- the regular unfolds using "simplify".
71+--
72+-------------------------------------------------------------------------------
73+-- Converting to regular unfolds
74+-------------------------------------------------------------------------------
75+
76+-- | Simplify a resumable unfold to a regular unfold.
77+--
78+-- /Internal/
79+{-# INLINE simplify #-}
80+simplify :: Unfold m a b -> Unfold.Unfold m a b
81+simplify (Unfold step inject _) = Unfold.Unfold step inject
82+
83+-------------------------------------------------------------------------------
84+-- Unfolds
85+-------------------------------------------------------------------------------
86+
87+-- | Convert a StreamD stream into an Unfold.
88+--
89+-- /Internal/
90+{-# INLINE_NORMAL fromStreamD #-}
91+fromStreamD :: Monad m => Unfold m (Stream m a) a
92+fromStreamD = Unfold step return (return . Just)
93+ where
94+
95+ {-# INLINE_LATE step #-}
96+ step (UnStream step1 state1) = do
97+ r <- step1 defState state1
98+ return $ case r of
99+ Yield x s -> Yield x (Stream step1 s)
100+ Skip s -> Skip (Stream step1 s)
101+ Stop -> Stop
102+
103+-------------------------------------------------------------------------------
104+-- Parsing
105+-------------------------------------------------------------------------------
106+
107+-- GHC parser does not accept {-# ANN type [] NoSpecConstr #-}, so we need
108+-- to make a newtype.
109+{-# ANN type List NoSpecConstr #-}
110+newtype List a = List {getList :: [a]}
111+
112+{-# INLINE_NORMAL parseD #-}
113+parseD
114+ :: MonadThrow m
115+ => ParserD.Parser m a b
116+ -> Unfold m (Source s a) a
117+ -> Source s a
118+ -> m (b, Maybe (Source s a))
119+parseD
120+ (ParserD.Parser pstep initial extract)
121+ (Unfold ustep uinject uextract)
122+ seed = do
123+
124+ state <- uinject seed
125+ initial >>= go SPEC state (List [])
126+
127+ where
128+
129+ -- XXX currently we are using a dumb list based approach for backtracking
130+ -- buffer. This can be replaced by a sliding/ring buffer using Data.Array.
131+ -- That will allow us more efficient random back and forth movement.
132+ {-# INLINE go #-}
133+ go !_ st buf !pst = do
134+ r <- ustep st
135+ case r of
136+ Yield x s -> do
137+ pRes <- pstep pst x
138+ case pRes of
139+ Partial 0 pst1 -> go SPEC s (List []) pst1
140+ Partial n pst1 -> do
141+ assert (n <= length (x:getList buf)) (return ())
142+ let src0 = Prelude.take n (x:getList buf)
143+ src = Prelude.reverse src0
144+ gobuf SPEC s (List []) (List src) pst1
145+ Continue 0 pst1 -> go SPEC s (List (x:getList buf)) pst1
146+ Continue n pst1 -> do
147+ assert (n <= length (x:getList buf)) (return ())
148+ let (src0, buf1) = splitAt n (x:getList buf)
149+ src = Prelude.reverse src0
150+ gobuf SPEC s (List buf1) (List src) pst1
151+ Done n b -> do
152+ assert (n <= length (x:getList buf)) (return ())
153+ let src0 = Prelude.take n (x:getList buf)
154+ src = Prelude.reverse src0
155+ s1 <- uextract s
156+ return (b, fmap (Source.unread src) s1)
157+ Error err -> throwM $ ParseError err
158+ Skip s -> go SPEC s buf pst
159+ Stop -> do
160+ b <- extract pst
161+ -- XXX we should return the remaining buffer
162+ return (b, Nothing)
163+
164+ gobuf !_ s buf (List []) !pst = go SPEC s buf pst
165+ gobuf !_ s buf (List (x:xs)) !pst = do
166+ pRes <- pstep pst x
167+ case pRes of
168+ Partial 0 pst1 ->
169+ gobuf SPEC s (List []) (List xs) pst1
170+ Partial n pst1 -> do
171+ assert (n <= length (x:getList buf)) (return ())
172+ let src0 = Prelude.take n (x:getList buf)
173+ src = Prelude.reverse src0 ++ xs
174+ gobuf SPEC s (List []) (List src) pst1
175+ Continue 0 pst1 ->
176+ gobuf SPEC s (List (x:getList buf)) (List xs) pst1
177+ Continue n pst1 -> do
178+ assert (n <= length (x:getList buf)) (return ())
179+ let (src0, buf1) = splitAt n (x:getList buf)
180+ src = Prelude.reverse src0 ++ xs
181+ gobuf SPEC s (List buf1) (List src) pst1
182+ Done n b -> do
183+ assert (n <= length (x:getList buf)) (return ())
184+ let src0 = Prelude.take n (x:getList buf)
185+ src = Prelude.reverse src0
186+ s1 <- uextract s
187+ return (b, fmap (Source.unread src) s1)
188+ Error err -> throwM $ ParseError err
189+
190+-- | Parse a resumable unfold returning the parsed value and the state of the
191+-- unfold which can be resumed later to unfold the remaining values.
192+--
193+-- /Internal/
194+{-# INLINE [3] parse #-}
195+parse
196+ :: MonadThrow m
197+ => ParserK.Parser m a b
198+ -> Unfold m (Source s a) a
199+ -> Source s a
200+ -> m (b, Maybe (Source s a))
201+parse = parseD . ParserK.fromParserK
202+
203+-------------------------------------------------------------------------------
204+-- Nested parsing
205+-------------------------------------------------------------------------------
206+
207+{-# INLINE parseManyD #-}
208+parseManyD :: MonadThrow m =>
209+ ParserD.Parser m a b -> Unfold m (Source x a) a -> Unfold m (Source x a) b
210+parseManyD parser reader = Unfold step inject return
211+
212+ where
213+
214+ inject = return . Just
215+
216+ {-# INLINE_LATE step #-}
217+ step (Just src) = do
218+ (b, s1) <- parseD parser reader src
219+ return $ Yield b s1
220+ step Nothing = return Stop
221+
222+-- | Apply a parser repeatedly on an unfold to generate an unfold of parsed
223+-- values.
224+--
225+-- /Internal/
226+{-# INLINE parseMany #-}
227+parseMany :: MonadThrow m =>
228+ ParserK.Parser m a b -> Unfold m (Source x a) a -> Unfold m (Source x a) b
229+parseMany parser = parseManyD (ParserK.fromParserK parser)
src/Streamly/Internal/Data/Unfold/Resume/Type.hsnew file+168
@@ -0,0 +1,168 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Unfold.Resume.Type
3+-- Copyright : (c) 2021 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+-- Resumable unfolds. See "Streamly.Internal.Data.Unfold.Resume" for
10+-- introduction.
11+--
12+
13+module Streamly.Internal.Data.Unfold.Resume.Type
14+ (
15+ -- * Type
16+ Unfold (..)
17+
18+ -- * Unfolds
19+ , nil
20+ , nilM
21+ , unfoldrM
22+ , fromList
23+
24+ -- * Mapping
25+ , lmap
26+
27+ -- * Nesting
28+ , concat
29+ )
30+where
31+
32+#include "inline.hs"
33+
34+import Fusion.Plugin.Types (Fuse(..))
35+import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
36+import Prelude hiding (concat)
37+
38+------------------------------------------------------------------------------
39+-- Type
40+------------------------------------------------------------------------------
41+
42+-- | An @Unfold m a b@ is a generator of a stream of values of type @b@ from a
43+-- seed of type 'a' in 'Monad' @m@.
44+--
45+-- /Internal/
46+
47+data Unfold m a b =
48+ -- | @Unfold step inject extract@
49+ forall s. Unfold (s -> m (Step s b)) (a -> m s) (s -> m (Maybe a))
50+
51+------------------------------------------------------------------------------
52+-- Unfolds
53+------------------------------------------------------------------------------
54+
55+-- XXX we do not need a Maybe, we can just use undefined or error. There is no
56+-- state to extract in this case, and it should not be used.
57+--
58+-- However, in certain cases we may not be able to extract, e.g. if we are
59+-- unfolding a Map, we may not be able to resume reading a Map. In such cases
60+-- it may be useful to return a Nothing. Or maybe we do not allow unfolding
61+-- such structures?
62+{-# INLINE nilM #-}
63+nilM :: Monad m => (a -> m c) -> Unfold m a b
64+nilM f = Unfold step return (\_ -> return Nothing)
65+
66+ where
67+
68+ {-# INLINE_LATE step #-}
69+ step x = f x >> return Stop
70+
71+{-# INLINE nil #-}
72+nil :: Monad m => Unfold m a b
73+nil = nilM (\_ -> return ())
74+
75+{-# INLINE unfoldrM #-}
76+unfoldrM :: Monad m => (a -> m (Maybe (b, a))) -> Unfold m a b
77+unfoldrM next = Unfold step return (return . Just)
78+ where
79+ {-# INLINE_LATE step #-}
80+ step st = do
81+ r <- next st
82+ return $ case r of
83+ Just (x, s) -> Yield x s
84+ Nothing -> Stop
85+
86+-- | Convert a list of pure values to a 'Stream'
87+--
88+-- /Internal/
89+{-# INLINE_LATE fromList #-}
90+fromList :: Monad m => Unfold m [a] a
91+fromList = Unfold step inject (return . Just)
92+ where
93+ inject = return
94+ {-# INLINE_LATE step #-}
95+ step (x:xs) = return $ Yield x xs
96+ step [] = return Stop
97+
98+------------------------------------------------------------------------------
99+-- Mapping
100+------------------------------------------------------------------------------
101+
102+-- | We can only lmap between two types that can be interconverted without
103+-- loss.
104+--
105+-- /Internal/
106+{-# INLINE_NORMAL lmap #-}
107+lmap :: Functor m => (a -> c) -> (c -> a) -> Unfold m c b -> Unfold m a b
108+lmap f g (Unfold ustep uinject uextract) =
109+ Unfold ustep (uinject . f) (fmap (fmap g) . uextract)
110+
111+------------------------------------------------------------------------------
112+-- Nesting
113+------------------------------------------------------------------------------
114+
115+{-# ANN type ConcatState Fuse #-}
116+data ConcatState s1 s2 = ConcatOuter s1 | ConcatInner s1 s2
117+
118+-- | Apply the second unfold to each output element of the first unfold and
119+-- flatten the output in a single stream.
120+--
121+-- /Internal/
122+--
123+{-# INLINE_NORMAL concat #-}
124+concat :: Monad m => Unfold m a b -> Unfold m b c -> Unfold m (a, Maybe b) c
125+concat (Unfold step1 inject1 extract1) (Unfold step2 inject2 extract2) =
126+ Unfold step inject extract
127+
128+ where
129+
130+ inject (x, Nothing) = do
131+ s <- inject1 x
132+ return $ ConcatOuter s
133+ inject (x, Just y) = do
134+ s1 <- inject1 x
135+ s2 <- inject2 y
136+ return $ ConcatInner s1 s2
137+
138+ {-# INLINE_LATE step #-}
139+ step (ConcatOuter st) = do
140+ r <- step1 st
141+ case r of
142+ Yield x s -> do
143+ innerSt <- inject2 x
144+ return $ Skip (ConcatInner s innerSt)
145+ Skip s -> return $ Skip (ConcatOuter s)
146+ Stop -> return Stop
147+
148+ step (ConcatInner ost ist) = do
149+ r <- step2 ist
150+ return $ case r of
151+ Yield x s -> Yield x (ConcatInner ost s)
152+ Skip s -> Skip (ConcatInner ost s)
153+ Stop -> Skip (ConcatOuter ost)
154+
155+ extract (ConcatOuter s1) = do
156+ r <- extract1 s1
157+ return $ case r of
158+ Nothing -> Nothing
159+ Just a -> Just (a, Nothing)
160+ extract (ConcatInner s1 s2) = do
161+ r1 <- extract1 s1
162+ case r1 of
163+ Nothing -> return Nothing
164+ Just a -> do
165+ r2 <- extract2 s2
166+ return $ case r2 of
167+ Nothing -> Just (a, Nothing)
168+ Just b -> Just (a, Just b)
src/Streamly/Internal/Data/Unfold/Source.hsnew file+84
@@ -0,0 +1,84 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Unfold.Source
3+-- Copyright : (c) 2021 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+-- A stream source which can be unfolded with some possibly buffered data.
10+-- Allows to 'unread' data i.e. push unused data back to the source. This is
11+-- useful in parsing applications with backtracking.
12+--
13+
14+module Streamly.Internal.Data.Unfold.Source
15+ ( Source
16+
17+ -- * Creation
18+ , source
19+
20+ -- * Reading
21+ , read
22+ , unread
23+ )
24+where
25+
26+#include "inline.hs"
27+
28+import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
29+import Streamly.Internal.Data.Unfold.Resume.Type (Unfold(..))
30+import Prelude hiding (read)
31+
32+-- | An unfold seed with some buffered data. It allows us to 'unread' or return
33+-- some data after reading it. Useful in backtracked parsing.
34+--
35+data Source a b = Source [b] a
36+
37+-- | Make a source from a seed value. The buffer would start as empty. You can
38+-- use 'unread' to add to the buffer.
39+--
40+-- /Internal/
41+source :: a -> Source a b
42+source = Source []
43+
44+-- | An unfold to read from a 'Source'. If the buffer has data then it is read
45+-- first otherwise the seed is unfolded using the supplied unfold.
46+--
47+-- /Internal/
48+{-# INLINE_NORMAL read #-}
49+read :: Monad m => Unfold m a b -> Unfold m (Source a b) b
50+read (Unfold step1 inject1 extract1) = Unfold step inject extract
51+
52+ where
53+
54+ inject (Source [] a) = do
55+ s <- inject1 a
56+ return $ Left s
57+ inject (Source xs a) = return $ Right (xs, a)
58+
59+ {-# INLINE_LATE step #-}
60+ step (Left s) = do
61+ r <- step1 s
62+ return $ case r of
63+ Yield x s1 -> Yield x (Left s1)
64+ Skip s1 -> Skip (Left s1)
65+ Stop -> Stop
66+ step (Right ([], _)) = error "Bug: unreachable"
67+ step (Right (x:[], a)) = do
68+ s <- inject1 a
69+ return $ Yield x (Left s)
70+ step (Right (x:xs, a)) = return $ Yield x (Right (xs, a))
71+
72+ extract (Left s) = do
73+ r <- extract1 s
74+ return $ case r of
75+ Nothing -> Nothing
76+ Just x -> Just $ Source [] x
77+ extract (Right (xs, a)) = return $ Just $ Source xs a
78+
79+-- | Return some unused data back to the source. The data is prepended (or
80+-- consed) to the source.
81+--
82+-- /Internal/
83+unread :: [b] -> Source a b -> Source a b
84+unread xs (Source ys seed) = Source (xs ++ ys) seed
streamly.cabal+3
@@ -433,6 +433,9 @@ library
433 , Streamly.Internal.Data.Stream.StreamD.Type
433 , Streamly.Internal.Data.Stream.StreamD.Type
434 , Streamly.Internal.Data.Stream.StreamDK.Type
434 , Streamly.Internal.Data.Stream.StreamDK.Type
435 , Streamly.Internal.Data.Unfold.Types
435 , Streamly.Internal.Data.Unfold.Types
436+ , Streamly.Internal.Data.Unfold.Resume.Type
437+ , Streamly.Internal.Data.Unfold.Resume
438+ , Streamly.Internal.Data.Unfold.Source
436 , Streamly.Internal.Data.Sink.Types
439 , Streamly.Internal.Data.Sink.Types
437 , Streamly.Internal.Data.Parser.ParserK.Types
440 , Streamly.Internal.Data.Parser.ParserK.Types
438 , Streamly.Internal.Data.Parser.ParserD.Types
441 , Streamly.Internal.Data.Parser.ParserD.Types
test/Streamly/Test/Data/Parser/ParserD.hs+32
@@ -16,6 +16,9 @@ import qualified Streamly.Internal.Data.Array.Foreign as A
16import qualified Streamly.Internal.Data.Fold as FL
16import qualified Streamly.Internal.Data.Fold as FL
17import qualified Streamly.Internal.Data.Parser.ParserD as P
17import qualified Streamly.Internal.Data.Parser.ParserD as P
18import qualified Streamly.Internal.Data.Stream.IsStream as S
18import qualified Streamly.Internal.Data.Stream.IsStream as S
19+import qualified Streamly.Internal.Data.Unfold as Unfold
20+import qualified Streamly.Internal.Data.Unfold.Source as Source
21+import qualified Streamly.Internal.Data.Unfold.Resume as UnfoldR
19import qualified Test.Hspec as H
22import qualified Test.Hspec as H
20
23
21import Prelude hiding (sequence)
24import Prelude hiding (sequence)
@@ -604,6 +607,34 @@ parseMany =
604 )
607 )
605 listEquals (==) outs ins
608 listEquals (==) outs ins
606
609
610+-- basic sanity test for parsing from arrays
611+parseUnfold :: Property
612+parseUnfold = do
613+ let len = 200
614+ -- ls = input list (stream)
615+ -- clen = chunk size
616+ -- tlen = parser take size
617+ forAll
618+ ((,,)
619+ <$> vectorOf len (chooseAny :: Gen Int)
620+ <*> chooseInt (1, len)
621+ <*> chooseInt (1, len)) $ \(ls, clen, tlen) ->
622+ monadicIO $ do
623+ arrays <- S.toList $ S.arraysOf clen (S.fromList ls)
624+ let src = Source.source (arrays, Nothing)
625+ let parser = P.fromFold (FL.takeLE tlen FL.toList)
626+ let readSrc =
627+ Source.read
628+ $ UnfoldR.concat UnfoldR.fromList A.readResumable
629+ let streamParser =
630+ UnfoldR.simplify (UnfoldR.parseManyD parser readSrc)
631+ xs <- run
632+ $ S.toList
633+ $ S.concatUnfold Unfold.fromList
634+ $ S.unfold streamParser src
635+
636+ listEquals (==) xs ls
637+
607-------------------------------------------------------------------------------
638-------------------------------------------------------------------------------
608-- Test for a particular case hit during fs events testing
639-- Test for a particular case hit during fs events testing
609-------------------------------------------------------------------------------
640-------------------------------------------------------------------------------
@@ -683,6 +714,7 @@ main =
683 describe "Stream parsing" $ do
714 describe "Stream parsing" $ do
684 prop "parseMany" parseMany
715 prop "parseMany" parseMany
685 prop "parseMany2Events" parseMany2Events
716 prop "parseMany2Events" parseMany2Events
717+ prop "parseUnfold" parseUnfold
686
718
687 describe "test for accumulator" $ do
719 describe "test for accumulator" $ do
688 prop "P.fromFold FL.sum = FL.sum" fromFold
720 prop "P.fromFold FL.sum = FL.sum" fromFold