streamly-core-0.2.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 module implements a using Continuation Passing Style (CPS) wrapper over
-- the "Streamly.Data.Parser" module. It is as fast or faster than attoparsec.
--
-- == Parser vs ParserK
--
-- 'ParserK' is preferred over 'Streamly.Data.Parser.Parser' when extensive
-- applicative, alternative and monadic composition is required, or when
-- recursive or dynamic composition of parsers is required. The
-- 'Streamly.Data.Parser.Parser' type fuses statically and creates efficient
-- loops whereas 'ParserK' uses function call based composition and has
-- comparatively larger runtime overhead but it is better suited to the
-- specific use cases mentioned above. 'ParserK' also allows to efficient parse
-- a stream of arrays, it can also break the input stream into a parse result
-- and remaining stream so that the stream can be parsed independently in
-- segments.
--
-- == Using ParserK
--
-- All the parsers from the "Streamly.Data.Parser" module can be adapted to
-- ParserK using the 'Streamly.Data.ParserK.adaptC',
-- 'Streamly.Internal.Data.ParserK.adapt', and
-- 'Streamly.Internal.Data.ParserK.adaptCG' combinators.
--
-- 'Streamly.Data.StreamK.parseChunks' runs a parser on a stream of unboxed
-- arrays, this is the preferred and most efficient way to parse chunked input.
-- The more general 'Streamly.Data.StreamK.parseBreakChunks' function returns
-- the remaining stream as well along with the parse result. There are
-- 'Streamly.Internal.Data.StreamK.parseChunksGeneric',
-- 'Streamly.Internal.Data.StreamK.parseBreakChunksGeneric' as well to run
-- parsers on boxed arrays. 'Streamly.Internal.Data.StreamK.parse',
-- 'Streamly.Internal.Data.StreamK.parseBreak' run parsers on a stream of
-- individual elements instead of stream of arrays.
--
-- == Monadic Composition
--
-- Monad composition can be used for lookbehind parsers, we can dynamically
-- compose new parsers based on the results of the previously parsed values.
--
-- If we have to parse "a9" or "9a" but not "99" or "aa" we can use the
-- following non-monadic, backtracking parser:
--
-- >>> digits p1 p2 = ((:) <$> p1 <*> ((:) <$> p2 <*> pure []))
-- >>> :{
-- backtracking :: Monad m => ParserK Char m String
-- backtracking = ParserK.adapt $
-- digits (Parser.satisfy isDigit) (Parser.satisfy isAlpha)
-- <|>
-- digits (Parser.satisfy isAlpha) (Parser.satisfy isDigit)
-- :}
--
-- 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
-- avoid redundant work:
--
-- >>> data DigitOrAlpha = Digit Char | Alpha Char
--
-- >>> :{
-- lookbehind :: Monad m => ParserK Char m String
-- lookbehind = do
-- x1 <- ParserK.adapt $
-- Digit <$> Parser.satisfy isDigit
-- <|> Alpha <$> Parser.satisfy isAlpha
-- -- Note: the parse depends on what we parsed already
-- x2 <- ParserK.adapt $
-- 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
-- ** Conversions
, adapt
, adaptC
, adaptCG
-- , toParser
-- ** Without Input
, fromPure
, fromEffect
, die
-- * Deprecated
, fromFold
, fromParser
)
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 Streamly.Internal.Data.ParserK.Type
#include "DocTestDataParserK.hs"
{-# DEPRECATED fromFold "Please use \"ParserK.adaptC . Parser.fromFold\" instead." #-}
{-# INLINE fromFold #-}
fromFold :: (MonadIO m, Unbox a) => Fold m a b -> ParserK (Array a) m b
fromFold = adaptC . ParserD.fromFold
{-# DEPRECATED fromParser "Please use \"adaptC\" instead." #-}
{-# INLINE fromParser #-}
fromParser ::
(MonadIO m, Unbox a) => ParserD.Parser a m b -> ParserK (Array a) m b
fromParser = adaptC