streamly-core-0.3.0: src/Streamly/Data/ParserK.hs
{-# LANGUAGE CPP #-}
-- |
-- Module : Streamly.Data.ParserK
-- Copyright : (c) 2023 Composewell Technologies
-- License : BSD-3-Clause
-- Maintainer : streamly@composewell.com
-- Stability : pre-release
-- Portability : GHC
--
-- See the general notes about parsing in the "Streamly.Data.Parser" module.
-- This (ParserK) module implements a Continuation Passing Style (CPS) wrapper
-- over the fused "Streamly.Data.Parser" module. It is a faster CPS parser than
-- attoparsec.
--
-- The 'ParserK' type represents a stream-consumer as a composition of function
-- calls, therefore, a function call overhead is incurred at each composition.
-- It is reasonably fast in general but may be a few times slower than the
-- fused 'Streamly.Data.Parser.Parser' type. However, unlike fused parsers, it
-- allows for scalable dynamic composition, especially, 'ParserK' can be used
-- in recursive calls. Operations like 'splitWith' on 'ParserK' type have
-- linear (O(n)) performance with respect to the number of compositions.
--
-- 'ParserK' is preferred over the fused 'Streamly.Data.Parser.Parser' when
-- extensive applicative, alternative and monadic composition is required, or
-- when recursive or dynamic composition of parsers is required. 'ParserK' also
-- allows efficient parsing of a stream of byte arrays, it can also break the
-- input stream into a parse result and the remaining stream so that the stream
-- can be parsed independently in segments.
--
-- == How to parse a stream?
--
-- All the fused parsers from the "Streamly.Data.Parser" module can be
-- converted to the CPS ParserK, for use with different types of parser
-- drivers, using
-- the @toParserK@ combinators -
-- Streamly.Data.Array.'Streamly.Data.Array.toParserK',
-- Streamly.Data.StreamK.'Streamly.Data.StreamK.toParserK', and
-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.toParserK'
--
-- To parse a stream of unboxed arrays, use
-- Streamly.Data.Array.'Streamly.Data.Array.parse' for running the parser, this
-- is the preferred and most efficient way to parse chunked input. The
-- Streamly.Data.Array.'Streamly.Data.Array.parseBreak' function returns the
-- remaining stream as well along with the parse result.
--
-- To parse a stream of boxed arrays, use
-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.parse' or
-- Streamly.Data.Array.Generic.'Streamly.Data.Array.Generic.parseBreak' to run
-- the parser.
--
-- To parse a stream of individual elements, use
-- Streamly.Data.StreamK.'Streamly.Data.StreamK.parse' and
-- Streamly.Data.StreamK.'Streamly.Data.StreamK.parseBreak' to run the parser.
--
-- == Applicative Composition
--
-- Applicative parsers are simpler but we cannot use lookbehind as we can in
-- the monadic parsers.
--
-- If we have to parse "9a" or "a9" but not "99" or "aa" we can use the
-- following Applicative, backtracking parser:
--
-- >>> -- parse p1 : p2 : []
-- >>> token p1 p2 = ((:) <$> p1 <*> ((:) <$> p2 <*> pure []))
-- >>> :{
-- backtracking :: Monad m => ParserK Char m String
-- backtracking = StreamK.toParserK $
-- token (Parser.satisfy isDigit) (Parser.satisfy isAlpha) -- e.g. "9a"
-- <|>
-- token (Parser.satisfy isAlpha) (Parser.satisfy isDigit) -- e.g. "a9"
-- :}
--
-- == Monadic Composition
--
-- Monad composition can be used to implement lookbehind parsers, we can dynamically
-- compose new parsers based on the results of the previously parsed values.
--
-- In the previous example, we know that if the first parse resulted in a digit
-- at the first place then the second parse is going to fail. However, we
-- waste that information and parse the first character again in the second
-- parse only to know that it is not an alphabetic char. By using lookbehind
-- in a 'Monad' composition we can make dynamic decisions based on previously
-- parsed information and avoid redundant work:
--
-- >>> data DigitOrAlpha = Digit Char | Alpha Char
--
-- >>> :{
-- lookbehind :: Monad m => ParserK Char m String
-- lookbehind = do
-- x1 <- StreamK.toParserK $
-- Digit <$> Parser.satisfy isDigit
-- <|> Alpha <$> Parser.satisfy isAlpha
-- -- Note: the parse depends on what we parsed already
-- x2 <- StreamK.toParserK $
-- case x1 of
-- Digit _ -> Parser.satisfy isAlpha
-- Alpha _ -> Parser.satisfy isDigit
-- return $ case x1 of
-- Digit x -> [x,x2]
-- Alpha x -> [x,x2]
-- :}
--
-- == Experimental APIs
--
-- Please refer to "Streamly.Internal.Data.ParserK" for functions that have
-- not yet been released.
--
module Streamly.Data.ParserK
(
-- * Setup
-- | To execute the code examples provided in this module in ghci, please
-- run the following commands first.
--
-- $setup
-- * Parser Type
ParserK
-- * Parsers
-- -- ** Without Input
, fromPure
, fromEffect
, die
-- * Deprecated
, fromFold
, fromParser
, adapt
, adaptC
, adaptCG
)
where
import Control.Monad.IO.Class (MonadIO)
import Streamly.Internal.Data.Fold (Fold)
import Streamly.Internal.Data.Unbox (Unbox)
import Streamly.Internal.Data.Array (Array)
import qualified Streamly.Internal.Data.Parser as ParserD
import qualified Streamly.Internal.Data.Array as Array
import Streamly.Internal.Data.ParserK
#include "DocTestDataParserK.hs"
{-# DEPRECATED fromFold "Please use \"Array.toParserK . Parser.fromFold\" instead." #-}
{-# INLINE fromFold #-}
fromFold :: (MonadIO m, Unbox a) => Fold m a b -> ParserK (Array a) m b
fromFold = Array.toParserK . ParserD.fromFold
{-# DEPRECATED fromParser "Please use \"Array.toParserK\" instead." #-}
{-# INLINE fromParser #-}
fromParser ::
(MonadIO m, Unbox a) => ParserD.Parser a m b -> ParserK (Array a) m b
fromParser = Array.toParserK