packages feed

flatparse-0.5.4.0: src/FlatParse/Minimal.hs

{-# language Strict, UnboxedTuples, ImportQualifiedPost, ScopedTypeVariables, TypeApplications #-}
-- {-# options_ghc -ddump-simpl -ddump-to-file -dsuppress-all -dno-suppress-type-signatures #-}

{-|
A stripped-down module that's mostly useful for __deserialization__.

- There's no precise error reporting and no efficient '(<|>)' operator. Internally, we only have
  parsing errors that are represented as an 'IO' exception. This exception can be thrown and caught,
  but the overheads of the exception machinery makes this ill-advised for control flow. Instead, users
  should use 'switch' to branch on tag-like input data.
- It is not possible to build 'ByteString'-s from the input during parsing.
- Since we only use 'IO' exceptions for failure, the implementation does not need to use any unboxed
  sum types. This makes the generated code smaller and faster. Also, __unboxing__ result types works
  robustly, so there's no need to use CPS-style combinators for that.
-}

module FlatParse.Minimal (
    module FlatParse.Minimal
  , module FlatParse.Minimal.Exception
  , FlatParse.Common.Position.Pos(..)
  , FlatParse.Common.Position.endPos
  , FlatParse.Common.Position.addrToPos#
  , FlatParse.Common.Position.posToAddr#
  , FlatParse.Common.Position.Span(..)
  , FlatParse.Common.Position.unsafeSlice
  , Common.strToUtf8
  , Common.utf8ToStr
  ) where

import Control.Exception
import Control.Monad.IO.Class
import GHC.Exts
import GHC.ForeignPtr
import GHC.IO (IO(..))
import Language.Haskell.TH

import Data.ByteString qualified  as B
import Data.ByteString.Internal qualified  as B
import Data.ByteString.Unsafe qualified  as B

import FlatParse.Basic qualified as FPB
import FlatParse.Basic.Switch qualified as FPB
import FlatParse.Common.Assorted qualified as Common
import FlatParse.Common.Numbers qualified as Common
import FlatParse.Common.Position
import FlatParse.Common.Switch qualified as Common
import FlatParse.Minimal.Exception

{-# inline parseError #-}
-- | Throw the designated parse error.
parseError :: Parser a
parseError = Parser \eob s st -> parseError# st

newtype Parser a = Parser# {runParser# :: Addr# -> Addr# -> State# RealWorld -> (# a, Addr#, State# RealWorld #)}

pattern Parser :: (Addr# -> Addr# -> State# RealWorld -> (# a, Addr#, State# RealWorld #))
                  -> Parser a
pattern Parser f <- Parser# f where
  Parser f = Parser# (oneShot \eob s st -> f eob s st)
{-# inline Parser #-}
{-# complete Parser #-}

-- Catching parsing exception
----------------------------------------------------------------------------------------------------

data ParseRes# a = ParseRes# a Addr#

{-# inline parserToIO #-}
parserToIO :: Parser a
           -> (Addr# -> Addr# -> IO (ParseRes# a))
parserToIO (Parser f) eob s =
  IO \st -> case f eob s st of (# a, s, st #) -> (# st, ParseRes# a s #)

{-# inline ioToParser #-}
ioToParser :: (Addr# -> Addr# -> IO (ParseRes# a))
           -> Parser a
ioToParser f = Parser \eob s st ->
  case f eob s of IO f -> case f st of (# st, ParseRes# a s #) -> (# a, s, st #)

-- | Catch a 'ParseException' and retry the second parser. This is not efficient enough for control
--   flow purposes. One use case is in debugging: you can catch and rethrow errors at specific
--   program points where informative debug messages can be printed.
{-# inline catchParseError #-}
catchParseError :: Parser a -> Parser a -> Parser a
catchParseError f g = ioToParser \eob s ->
  catch @ParseException (parserToIO f eob s) \_ -> parserToIO g eob s

----------------------------------------------------------------------------------------------------

instance Functor Parser where
  {-# inline fmap #-}
  fmap = \f (Parser g) -> Parser \eob s st -> case g eob s st of
    (# a, s, st #) -> let b = f a in (# b, s, st #)

  {-# inline (<$) #-}
  (<$) = \a -> fmap (\_ -> a)

instance Applicative Parser where
  {-# inline pure #-}
  pure = \a -> Parser \eob s st -> (# a, s, st #)

  {-# inline (<*>) #-}
  (<*>) = \(Parser mf) (Parser ma) -> Parser \eob s st -> case mf eob s st of
    (# f, s, st #) -> case ma eob s st of
      (# a, s, st #) -> let b = f a in (# b, s, st #)

  {-# inline (<*) #-}
  (<*) = \(Parser ma) (Parser mb) -> Parser \eob s st -> case ma eob s st of
    (# !a, s, st #) -> case mb eob s st of
      (# !b, s, st #) -> (# a, s, st #)

  {-# inline (*>) #-}
  (*>) = \(Parser ma) (Parser mb) -> Parser \eob s st -> case ma eob s st of
    (# !a, s, st #) -> mb eob s st

-- | Variant of 'pure' that does not force its argument.
{-# inline pureLazy #-}
pureLazy :: a -> Parser a
pureLazy = \ a -> Parser \eob s st -> (# a, s, st #)

instance Monad Parser where
  {-# inline return #-}
  return = pure

  {-# inline (>>=) #-}
  (>>=) = \(Parser ma) f -> Parser \eob s st -> case ma eob s st of
    (# !a, s, st #) -> runParser# (f a) eob s st

  {-# inline (>>) #-}
  (>>) = (*>)

instance MonadIO Parser where
  {-# inline liftIO #-}
  liftIO = \(IO ma) -> Parser \eob s st -> case ma st of
    (# st, !a #) -> (# a, s, st #)

-- | End-of-file, else throw error.
{-# inline eof #-}
eof :: Parser ()
eof = Parser \eob s st -> case eqAddr# eob s of
  1# -> (# (), s, st #)
  _  -> parseError# st

-- | Parsing result.
data Result a
  = OK a B.ByteString -- ^ Success case with result value and the remaining input.
  | Error             -- ^ Parsing error.
  deriving Show

-- | Run a parser.
{-# inlinable runParser #-}
runParser :: Parser a -> B.ByteString -> IO (Result a)
runParser (Parser f) b@(B.PS (ForeignPtr _ fp) _ (I# len)) =
  B.unsafeUseAsCString b \(Ptr buf) -> do
    let end = plusAddr# buf len
    (IO \st -> case f end buf st of
      (# a, s, st #) -> let offset = minusAddr# s buf
                            !rest  = B.drop (I# offset) b
                        in (# st, OK a rest  #))
    `catch` \(_ :: ParseException) -> pure Error

{-# inline runParserUtf8 #-}
-- | Run a pure parser on a 'String', converting it to the corresponding UTF-8 bytes.
--   Reminder: @OverloadedStrings@ for 'B.ByteString' does not yield a valid UTF-8
--   encoding! For non-ASCII 'B.ByteString' literal input, use this wrapper or
--   properly convert your input first.
runParserUtf8 :: Parser a -> String -> IO (Result a)
runParserUtf8 p s = runParser p (Common.strToUtf8 s)

-- | Parse a non-empty ASCII decimal digit sequence as a 'Word'.
--   Fails on overflow.
{-# inline anyAsciiDecimalWord #-}
anyAsciiDecimalWord :: Parser Word
anyAsciiDecimalWord = Parser \eob s st -> case Common.anyAsciiDecimalWord# eob s of
  (# | (# x, s #) #) -> (# W# x, s, st #)
  (# _ | #)          -> parseError# st

-- | Parse a non-empty ASCII decimal digit sequence as a positive 'Int'.
--   Fails on overflow.
{-# inline anyAsciiDecimalInt #-}
anyAsciiDecimalInt :: Parser Int
anyAsciiDecimalInt = Parser \eob s st -> case Common.anyAsciiDecimalInt# eob s of
  (# | (# x, s #) #) -> (# I# x, s, st #)
  (# _ | #)          -> parseError# st

-- | Parse a non-empty ASCII decimal digit sequence as a positive 'Integer'.
{-# inline anyAsciiDecimalInteger #-}
anyAsciiDecimalInteger :: Parser Integer
anyAsciiDecimalInteger = unsafeEmbedBasicIO $ FPB.anyAsciiDecimalInteger

{-# inline unsafeEmbedBasicIO #-}
-- | Run a @FlatParse.Basic@ parser. Warning: the parser must not return any 'ByteString'
--   constructed from the input buffer!
unsafeEmbedBasicIO :: FPB.ParserIO () a -> Parser a
unsafeEmbedBasicIO = \(FPB.ParserT f) -> Parser \eob s st ->
  case f FinalPtr eob s st of
    (# st, (# (# !a, s #) | | #) #) -> (# a, s, st #)
    (# st, _                     #) -> parseError# st


-- | Run a @FlatParse.Minimal@ parser inside a @FlatParse.Basic@ parser.
{-# inline embedMinimal #-}
embedMinimal :: Parser a -> FPB.ParserIO () a
embedMinimal = \(Parser f) -> FPB.ParserT \_ eob s st ->
  case f eob s st of (# !a, s, st #) -> FPB.OK# st a s

{-# inline spanOf #-}
-- | Return the consumed span of a parser.
spanOf :: Parser a -> Parser Span
spanOf (Parser f) = Parser \eob s st -> case f eob s st of
  (# a, s', st' #) -> let span = Span (addrToPos# eob s) (addrToPos# eob s')
                      in (# span, s', st' #)

-- | Skip forward @n@ bytes. Errors if fewer than @n@ bytes are available.  Behavior is undefined if
--   the given number is negative.
{-# inline skip #-}
skip :: Int -> Parser ()
skip = \(I# n) -> Parser \eob s st ->
  case n <=# minusAddr# eob s of
    1# -> (# (), plusAddr# s n, st #)
    _  -> parseError# st

-- | Go back @i@ bytes in the input. Takes a positive integer.
--
-- Extremely unsafe. Makes no checks. Almost certainly a Bad Idea.
{-# inline skipBack #-}
skipBack :: Int -> Parser ()
skipBack = \(I# n) -> Parser \eob s st -> (# (), plusAddr# s (negateInt# n), st #)

----------------------------------------------------------------------------------------------------

-- | Parse a UTF-8 string literal. This is a template function, you can use it as @$(string "foo")@,
--   for example, and the splice has type @Parser e ()@.
string :: String -> Q Exp
string str = [| unsafeEmbedBasicIO $(FPB.bytes (Common.strToBytes str)) |]

-- | Parse a UTF-8 character literal. This is a template function, you can use it as
--   @$(char \'x\')@, for example, and the splice in this case has type @Parser e ()@.
char :: Char -> Q Exp
char c = string [c]

{-|
This is a template function which makes it possible to branch on a collection of string literals in
an efficient way. By using `switch`, such branching is compiled to a trie of primitive parsing
operations, which has optimized control flow, vectorized reads and grouped checking for needed input
bytes.

The syntax is slightly magical, it overloads the usual @case@ expression. An example:

@
    $(switch [| case _ of
        "foo" -> pure True
        "bar" -> pure False |])
@

The underscore is mandatory in @case _ of@. Each branch must be a string literal, but optionally
we may have a default case, like in

@
    $(switch [| case _ of
        "foo" -> pure 10
        "bar" -> pure 20
        _     -> pure 30 |])
@

All case right hand sides must be parsers with the same type. That type is also the type
of the whole 'switch' expression.

A 'switch' has longest match semantics, and the order of cases does not matter, except for
the default case, which may only appear as the last case.

If a 'switch' does not have a default case, and no case matches the input, then it throws an
error. A fallthrough to the default case does not consume any input.
-}
switch :: Q Exp -> Q Exp
switch exp = do
  (!cases, !fallback) <- Common.parseSwitch exp
  !fromMinimal <- [| embedMinimal |]
  !toMinimal   <- [| unsafeEmbedBasicIO |]
  let !cases' = (fmap . fmap) (AppE fromMinimal) cases
  let !fallback' = fmap (AppE fromMinimal) fallback
  !t <- FPB.genTrie $! FPB.genSwitchTrie' Nothing cases' fallback'
  pure $ AppE toMinimal t