packages feed

smith-0.2.0.0: src/Data/Parser.hs

{-# language BangPatterns #-}
{-# language BinaryLiterals #-}
{-# language DataKinds #-}
{-# language DeriveFunctor #-}
{-# language DerivingStrategies #-}
{-# language GADTSyntax #-}
{-# language KindSignatures #-}
{-# language LambdaCase #-}
{-# language MagicHash #-}
{-# language MultiWayIf #-}
{-# language PolyKinds #-}
{-# language RankNTypes #-}
{-# language ScopedTypeVariables #-}
{-# language StandaloneDeriving #-}
{-# language TypeApplications #-}
{-# language UnboxedSums #-}
{-# language UnboxedTuples #-}

-- | Parse token sequences.
module Data.Parser
  ( Parser
  , Result(..)
  , Slice(..)
  , parseSmallArray
  , parseSmallArrayEither
  , parseSmallArrayEffectfully
    -- * Primitives
  , any
  , anySentinel
  , opt
  , peek
  , peekSentinel
  , optPeek
  , token
  , token2
  , token4
  , effect
  , fail
  , trySatisfy
    -- * Control Flow
  , foldSepBy1
  , foldSepByUntilEoi
  , listSepByUntilEoi
  , foldUntil
  , until
  , sepBy1_
  , sepBy1
  , skipWhile
    -- * End of Input
  , isEndOfInput
  , endOfInput
    -- * Lifting
  , liftEither
  ) where

import Prelude hiding (length,any,fail,until)

import Data.Bool (bool)
import Data.Primitive (SmallArray(..))
import Data.Bytes.Parser (Result(..),Slice(..))
import Data.Parser.Unsafe (Parser(..))
import GHC.Exts (TYPE,Int(I#),Int#)
import GHC.ST (ST(ST),runST)

import qualified Data.Primitive.Contiguous as C
import qualified Data.Primitive as PM
import qualified GHC.Exts as Exts
import qualified Data.List as List

type Result# e (a :: TYPE r) =
  (# e
  | (# a, Int#, Int# #) #) -- ints are offset and length

-- | Consumes and returns the next token from the input.
-- Fails if no tokens are left.
any :: e -> Parser a e s a
{-# inline any #-}
any e = uneffectful $ \array off len -> case len of
  0 -> Failure e
  _ ->
    let w = PM.indexSmallArray array off
     in Success (Slice (off + 1) (len - 1) w)

-- | Consumes and returns the next token from the input.
-- Returns the sentinel token if no tokens are left.
anySentinel :: a -> Parser a e s a
{-# inline anySentinel #-}
anySentinel sentinel = uneffectful $ \array off len -> case len of
  0 -> Success (Slice off len sentinel)
  _ ->
    let w = PM.indexSmallArray array off
     in Success (Slice (off + 1) (len - 1) w)

-- | Consume a token from the input or return @Nothing@ if
-- end of the stream has been reached. This parser never fails.
opt :: Parser a e s (Maybe a)
{-# inline opt #-}
opt = uneffectful $ \array off len -> case len of
  0 -> Success (Slice off 0 Nothing)
  _ ->
    let w = PM.indexSmallArray array off
     in Success (Slice (off + 1) (len - 1) (Just w))

-- | Looks at the next token from the input. If the token matches
-- the predicate, consume the token and return @True@. Otherwise,
-- do not consume the token and return @False@. If no tokens
-- remain in the input, return @False@. This parser never fails.
trySatisfy :: (a -> Bool) -> Parser a e s Bool
{-# inline trySatisfy #-}
trySatisfy p = uneffectful $ \array off len -> case len of
  0 -> Success (Slice off 0 False)
  _ -> let w = PM.indexSmallArray array off in
    case p w of
      True -> Success (Slice (off + 1) (len - 1) True)
      False -> Success (Slice off len False)

-- | Lift an effect into a parser.
effect :: ST s b -> Parser a e s b
{-# inline effect #-}
effect (ST f) = Parser
  (\(# _, off, len #) s0 -> case f s0 of
    (# s1, b #) -> (# s1, (# | (# b, off, len #) #) #)
  )

-- | Consumes and returns the next token from the input.
-- Fails if no tokens are left.
fail :: e -> Parser a e s b
{-# inline fail #-}
fail e = Parser (\_ s0 -> (# s0, (# e | #) #) )

-- | Consumes the next token from the input. Fails if it
-- is not equal to the expected value.
token :: Eq a
  => e -- ^ Error message
  -> a -- ^ Expected value of next token
  -> Parser a e s ()
{-# inline token #-}
token e a = do
  b <- any e
  bool (fail e) (pure ()) (a == b)

token2 :: Eq a => e -> a -> a -> Parser a e s ()
token2 e a b = token e a *> token e b

token4 :: Eq a => e -> a -> a -> a -> a -> Parser a e s ()
token4 e a b c d = token e a *> token e b *> token e c *> token e d

-- | Returns the next token from the input without consuming
-- it. Fails if no tokens are left.
peek :: e -> Parser a e s a
{-# inline peek #-}
peek e = uneffectful $ \array off len -> if len > 0
  then
    let w = PM.indexSmallArray array off
     in Success (Slice off len w)
  else Failure e

-- | Variant of 'peek' that returns the sentinel token instead of failing
-- when no tokens are left. It is prudent, but not required, to use
-- this only on input that does not contain the sentinel token.
peekSentinel ::
     a -- ^ Sentinel token
  -> Parser a e s a
{-# inline peekSentinel #-}
peekSentinel sentinel = uneffectful $ \array off len -> if len > 0
  then
    let w = PM.indexSmallArray array off
     in Success (Slice off len w)
  else Success (Slice off len sentinel)

-- | Returns the next token from the input without consuming it. Returns
-- @Nothing@ if at the end of the input.
optPeek :: Parser a e s (Maybe a)
{-# inline optPeek #-}
optPeek = uneffectful $ \array off len -> if len > 0
  then
    let w = PM.indexSmallArray array off
     in Success (Slice off len (Just w))
  else Success (Slice off len Nothing)

-- | Fold over the tokens, repeatedly running @step@ followed
-- by @separator@ until @separator@ returns 'False'. This is
-- strict in the accumulator and always runs @step@ at least
-- once. There is no backtracking; any failure causes the whole
-- combinator to fail.
foldSepBy1 ::
     Parser a e s Bool -- ^ Separator
  -> (b -> Parser a e s b) -- ^ Step
  -> b -- ^ Initial value
  -> Parser a e s b
{-# inline foldSepBy1 #-}
foldSepBy1 sep f b0 = f b0 >>= go
  where
  go !b = sep >>= \case
    True -> f b >>= go
    False -> pure b

foldSepByUntilEoi ::
     Parser a e s () -- ^ Separator (cannot check for end-of-input)
  -> (b -> Parser a e s b) -- ^ Step
  -> b -- ^ Initial value
  -> Parser a e s b
{-# inline foldSepByUntilEoi #-}
foldSepByUntilEoi sep f b0 = isEndOfInput >>= \case
  True -> pure b0
  False -> f b0 >>= go
  where
  go !b = isEndOfInput >>= \case
    True -> pure b
    False -> do
      sep
      f b >>= go

listSepByUntilEoi ::
     Parser a e s () -- ^ Separator (cannot check for end-of-input)
  -> Parser a e s b -- ^ Parse single element
  -> Parser a e s [b]
{-# inline listSepByUntilEoi #-}
listSepByUntilEoi sep f = do
  xs <- foldSepByUntilEoi sep
    (\ !acc -> do
      b <- f 
      pure (b : acc)
    ) []
  pure $! List.reverse xs

-- | Repeatedly run the parser, folding over the results, until a token
-- that satisfies the predicate is encountered. This is strict in the
-- accumulator. Tokens the do not match predicate are not consumed.
-- For example, consider the input sequence:
--
-- > Var "a", Var "x1", Var "x2", CloseParen
--
-- This could be matched by:
--
-- > P.foldUntil
-- >   (== CloseParen)
-- >   (\xs -> P.any ErrMsg >>= \case {Var x -> pure (x : xs); _ -> P.fail ErrMsg})
-- >   []
--
-- The accumulated list would be backwards in this example, and the
-- cursor would be positioned before, not after, @CloseParen@.
foldUntil ::
     (a -> Bool) -- ^ When token satisfies predicate, finish without consuming it
  -> (b -> Parser a e s b) -- ^ Step, consuming and accumulating
  -> b -- ^ Initial value
  -> Parser a e s b
{-# inline foldUntil #-}
foldUntil isTerminator step !b0 = go b0
  where
  go !b = optPeek >>= \case
    Nothing -> pure b
    Just t -> if isTerminator t
      then pure b
      else do
        b' <- step b
        go b'

-- | Variant of 'foldUntil' that collects elements into
-- an array.
until :: (C.ContiguousU arr, C.Element arr b)
  => (a -> Bool) -- ^ When token satisfies predicate, finish without consuming it
  -> Parser a e s b -- ^ Step, producing element for array 
  -> Parser a e s (arr b)
{-# inline until #-}
until isTerminator p = do
  let cap0 = 8
  buf0 <- effect (C.new cap0)
  let go !buf !ix !cap = if ix < cap
        then optPeek >>= \case
          Nothing -> effect (C.resize buf ix >>= C.unsafeFreeze)
          Just t -> if isTerminator t
            then effect (C.resize buf ix >>= C.unsafeFreeze)
            else do
              b <- p
              effect (C.write buf ix b)
              go buf (ix + 1) cap
        else do
          let cap' = cap * 2
          buf' <- effect $ do
            buf' <- C.new cap'
            C.copyMut buf' 0 (C.sliceMut buf 0 cap)
            pure buf'
          go buf' ix cap'
  go buf0 0 cap0

-- | Fold over the tokens, repeatedly running @step@ followed
-- by @separator@ until @separator@ returns 'False'. Collects
-- all parsed elements into an array (@PrimArray@, @Array@, etc.).
-- Consider the elements:
sepBy1 :: (C.ContiguousU arr, C.Element arr b)
  => Parser a e s Bool -- ^ Separator
  -> Parser a e s b -- ^ Element parser
  -> Parser a e s (arr b)
{-# inline sepBy1 #-}
sepBy1 sep p = do
  let cap0 = 8
  buf0 <- effect (C.new cap0)
  b0 <- p
  effect (C.write buf0 0 b0)
  let go !buf !ix !cap = if ix < cap
        then sep >>= \case
          True -> do
            b <- p
            effect (C.write buf ix b)
            go buf (ix + 1) cap
          False -> effect (C.resize buf ix >>= C.unsafeFreeze)
        else do
          let cap' = cap * 2
          buf' <- effect $ do
            buf' <- C.new cap'
            C.copyMut buf' 0 (C.sliceMut buf 0 cap)
            pure buf'
          go buf' ix cap'
  go buf0 1 cap0

-- | Skip tokens for which the predicate is true.
skipWhile ::
     (a -> Bool) -- ^ Predicate
  -> Parser a e s ()
{-# inline skipWhile #-}
skipWhile f = go where
  go = opt >>= \case
    Nothing -> pure ()
    Just t -> case f t of
      True -> go
      False -> internalUnconsume 1

-- | Fold over the tokens, repeatedly running @step@ followed
-- by @separator@ until @separator@ returns 'False'. The results
-- of @step@ are discarded, but in conjunction with @effect@,
-- this can be used to populate an array or a builder. This
-- always runs @step@ at least once.
--
-- > sepBy1 sep step === step *> (sep >>= bool (pure ()) (step *> (sep >>= bool (pure ()) (...))))
--
-- For example, consider this input sequence:
--
-- > Var "a", Comma, Var "x1", Comma, Var "x2", CloseParen
--
-- This could be matched by:
--
-- > P.sepBy1_
-- >   (P.any ErrMsg >>= \case
-- >     {Comma -> True; CloseParen -> False; _ -> P.fail ErrMsg}
-- >   )
-- >   (P.any ErrMsg >>= \case {Var _ -> pure (); _ -> P.fail ErrMsg})
sepBy1_ ::
     Parser a e s Bool -- ^ Separator
  -> Parser a e s b -- ^ Step
  -> Parser a e s ()
{-# inline sepBy1_ #-}
sepBy1_ sep f = f *> go where
  go = sep >>= \case
    True -> f *> go
    False -> pure ()

uneffectful :: (SmallArray a -> Int -> Int -> Result e b) -> Parser a e s b
{-# inline uneffectful #-}
uneffectful f = Parser
  ( \(# arr,off,len #) s0 -> (# s0, unboxResult (f (SmallArray arr) (I# off) (I# len)) #) )

unboxResult :: Result e a -> Result# e a
unboxResult (Success (Slice (I# b) (I# c) a)) = (# | (# a, b, c #) #)
unboxResult (Failure e) = (# e | #)

parseSmallArray ::
     forall a e b. (forall s. Parser a e s b)
  -> SmallArray a
  -> Result e b
parseSmallArray p (SmallArray arr) = runST action
  where
  action :: forall s. ST s (Result e b)
  action = case p @s of
    Parser f -> ST
      (\s0 -> case f (# arr, 0#, (Exts.sizeofSmallArray# arr) #) s0 of
        (# s1, r #) -> (# s1, boxResult r #)
      )

parseSmallArrayEither ::
     forall a e b. (forall s. Parser a e s b)
  -> SmallArray a
  -> Either e b
parseSmallArrayEither p !xs = case parseSmallArray p xs of
  Success (Slice _ _ c) -> Right c
  Failure e -> Left e

parseSmallArrayEffectfully :: Parser a e s b -> SmallArray a -> ST s (Result e b)
parseSmallArrayEffectfully (Parser f) (SmallArray arr) = ST
  (\s0 -> case f (# arr, 0#, (Exts.sizeofSmallArray# arr) #) s0 of
    (# s1, r #) -> (# s1, boxResult r #)
  )

boxResult :: Result# e a -> Result e a
boxResult (# | (# a, b, c #) #) = Success (Slice (I# b) (I# c) a)
boxResult (# e | #) = Failure e

-- A copy of unconsume so that the modules do not need to
-- be restructured.
internalUnconsume :: Int -> Parser a e s ()
{-# inline internalUnconsume #-}
internalUnconsume n = uneffectful $ \_ off len ->
  Success (Slice (off - n) (len + n) ())

liftEither :: Either e b -> Parser a e s b
liftEither = \case
  Left e -> fail e
  Right b -> pure b

-- | Returns true if there are no more tokens in the input. Returns false
-- otherwise. Always succeeds.
isEndOfInput :: Parser a e s Bool
isEndOfInput = uneffectful $ \_ off len -> case len of
  0 -> Success (Slice off 0 True)
  _ -> Success (Slice off len False)

endOfInput :: e -> Parser a e s ()
endOfInput e = uneffectful $ \_ off len -> case len of
  0 -> Success (Slice off 0 ())
  _ -> Failure e