@@ -0,0 +1,170 @@
1+{-# LANGUAGE ExistentialQuantification #-}
2+{-# LANGUAGE BangPatterns #-}
3+{-# LANGUAGE DeriveFunctor #-}
4+{-# LANGUAGE DeriveAnyClass #-}
5+{-# LANGUAGE DeriveGeneric #-}
6+
7+-- |
8+-- Module : Streamly.Parser.Types
9+-- Copyright : (c) 2020 Composewell Technologies
10+-- License : BSD3
11+-- Maintainer : streamly@composewell.com
12+-- Stability : experimental
13+-- Portability : GHC
14+--
15+-- Streaming and backtracking parsers.
16+--
17+-- The 'Parser' type or a parsing fold is a generalization of the 'Fold' type.
18+-- The 'Fold' type always succeeds on each input. Therefore it does not need to
19+-- buffer the input. In contrast, a 'Parser' may fail and backtrack to replay
20+-- the input again to explore another branch of the parser. Therefore, it needs
21+-- to buffer the input.
22+--
23+-- The 'Parser' type is an extension of the 'Fold' type. The 'Step' functor
24+-- for the 'Parser' type has been specifically designed for stream parsing
25+-- applications, assuming the input to be a seuqentially read stream and
26+-- potentially buffering a contiguous segment of the stream based on the
27+-- parser's need.
28+--
29+-- To provide richer communication with the input stream, the 'Step' functor of
30+-- a 'Parser' provides more powerful commands to manipulate the input stream
31+-- after each step invocation. It allows the fold driver to:
32+--
33+-- 1. Buffer the input until explicitly asked to drop the input
34+-- 2. Drop the input stream beyond a "point of no return" in the history
35+-- because the input till that point has been consumed irrevocably.
36+-- 2. Go back in the buffered stream upto the point of no return.
37+--
38+-- Based on application requirements it should be possible to design even a
39+-- richer interface to manipulate the input stream/buffer. For example, we
40+-- could randomly seek into the stream in the forward or reverse directions or
41+-- we can even seek to the end or from the end or seek from the beginning.
42+
43+-- We can distribute and scan/parse a stream using both folds and parsers and
44+-- merge the resulting streams using different merge strategies (e.g.
45+-- interleaving or serial).
46+
47+module Streamly.Internal.Data.Parser.Types
48+ (
49+ Step (..)
50+ , Parser (..)
51+ )
52+where
53+
54+import Fusion.Plugin.Types (Fuse(..))
55+
56+-- | The return type of a 'Parser' step.
57+--
58+-- A parser is driven by a parse driver one step at a time, at any time the
59+-- driver may @extract@ the result of the parser. The parser may ask the driver
60+-- to backtrack at any point, therefore, the driver holds the input up to a
61+-- point of no return in a backtracking buffer. The buffer grows or shrinks
62+-- based on the return values of the parser step execution.
63+--
64+-- When a parser step is executed it generates a new state of the parse result
65+-- along with a command to the driver. The command tells the driver whether to
66+-- keep the input stream for backtracking or drop it, and how much to keep. The
67+-- constructors of 'Step' represent the commands to the driver.
68+--
69+-- /Internal/
70+--
71+{-# ANN type Step Fuse #-}
72+data Step s =
73+ Yield Int s
74+ -- ^ @Yield offset state@ indicates that the parser has yielded a new
75+ -- result which is a point of no return. The result can be extracted
76+ -- using @extract@. The driver drops the buffer except @offset@ elements
77+ -- before the current position in stream. The rule is that if a parser
78+ -- has yielded at least once it cannot return a failure result.
79+
80+ | Skip Int s
81+ -- ^ @Skip offset state@ indicates that the parser has consumed the current
82+ -- input but no new result has been generated. A new @state@ is generated.
83+ -- However, if we use @extract@ on @state@ it will generate a result from
84+ -- the previous @Yield@. When @offset@ is non-zero it is a backward offset
85+ -- from the current position in the stream from which the driver will feed
86+ -- the next input to the parser. The offset cannot be beyond the latest
87+ -- point of no return created by @Yield@.
88+
89+ | Stop Int s
90+ -- ^ @Stop offset state@ asks the driver to stop driving the parser because
91+ -- it has reached a fixed point and further input will not change the
92+ -- result. @offset@ is the count of unused elements which includes the
93+ -- element on which 'Stop' occurred. Once a fold stops, driving it further
94+ -- may produce undefined behavior.
95+
96+-- | A parsing fold is represented as @Parser step initial extract@. Before we
97+-- drive a parser we call the @initial@ action to retrieve the initial state of
98+-- the fold. The driver invokes @step@ with the state returned by the previous
99+-- step and the next input element. It results into a new state and a command
100+-- to the driver represented by 'Step' type. At any point of time the driver
101+-- can call @extract@ to inspect the result of the fold. It may result in an
102+-- error or an output value.
103+--
104+data Parser m a b =
105+ forall s. Parser (s -> a -> m (Step s)) (m s) (s -> m (Either String b))
106+
107+-- | Maps a function over the output of the fold.
108+--
109+instance Monad m => Functor (Parser m a) where
110+ {-# INLINE fmap #-}
111+ fmap f (Parser step initial extract) =
112+ Parser step initial (fmap3 f extract)
113+
114+ where
115+
116+ fmap3 g = fmap (fmap (fmap g))
117+
118+ {-# INLINE (<$) #-}
119+ (<$) b = \_ -> pure b
120+
121+{-# ANN type SeqParseState Fuse #-}
122+data SeqParseState sl f sr =
123+ SeqParseL sl | SeqParseR f sr | SeqParseLErr String
124+
125+-- | Apply two parsing folds sequentially to an input. The input is provided to
126+-- the first parser, if the parser fails then whole composition fails. If the
127+-- parser succeeds, the remaining input is supplied to the second parser. If
128+-- the second parser succeeds the composed parser succeeds otherwise it fails.
129+--
130+instance Monad m => Applicative (Parser m a) where
131+ {-# INLINE pure #-}
132+ pure b = Parser (\_ _ -> return $ Stop 0 ())
133+ (return ())
134+ (\_ -> return $ Right b)
135+
136+ {-# INLINE (<*>) #-}
137+ (Parser stepL initialL extractL) <*> (Parser stepR initialR extractR) =
138+ Parser step initial extract
139+
140+ where
141+
142+ initial = SeqParseL <$> initialL
143+
144+ -- Note: For the composed parse to terminate, the left parser has to be
145+ -- a terminating parser returning a Halt at some point.
146+ step (SeqParseL st) a = do
147+ r <- stepL st a
148+ case r of
149+ Yield n s -> return $ Yield n (SeqParseL s)
150+ Skip n s -> return $ Skip n (SeqParseL s)
151+ Stop n s -> do
152+ res <- extractL s
153+ case res of
154+ Left err -> return $ Stop n (SeqParseLErr err)
155+ Right f -> Skip n <$> (SeqParseR f <$> initialR)
156+
157+ step (SeqParseR f st) a = do
158+ r <- stepR st a
159+ return $ case r of
160+ Yield n s -> Yield n (SeqParseR f s)
161+ Skip n s -> Skip n (SeqParseR f s)
162+ Stop n s -> Stop n (SeqParseR f s)
163+
164+ step (SeqParseLErr _) _ = error "step called in SeqParseLErr"
165+
166+ -- XXX bimap to add "<*>: Right parse failed" to the error message
167+ extract (SeqParseR f s) = fmap (fmap f) (extractR s)
168+ extract (SeqParseL _) = return $ Left "<*>: Incomplete left parse"
169+ extract (SeqParseLErr err) =
170+ return $ Left $ "<*>: Left parse failed\n" ++ err