yamlet-1.0.0.0: src/Yamlet/Internal/Parser/Monad.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE UnboxedTuples #-}
{-# OPTIONS_HADDOCK not-home #-}
-- | A backtracking parser over the bytes of UTF-8 encoded text.
--
-- This module is intended for internal use only, and may change without warning
-- in subsequent releases.
module Yamlet.Internal.Parser.Monad
( -- * Parser
P
, Env (..)
, ParseError (..)
, runParser
-- * Combinators
, (<|>)
, many
, many_
, optional
, optional_
, option
, notFollowedBy
-- * Primitives
, env
, pos
, setPos
, furthest
, advance
, addTagBytes
, peek
, peekAt
, failure
, guardP
, throwAt
, throwUnexpected
, withEnd
, withHandles
, char
, skipWhile
, scan
, Scanned (..)
, withScan
-- * Input access
, byteAt
, byteBefore
, slice
, toOffset
) where
import Control.Monad
import Data.Map.Strict qualified as M
import Data.Text qualified as T
import Data.Text.Array qualified as A
import Data.Text.Internal qualified as T
import Data.Word
import GHC.Exts (Int (I#), Int#, isTrue#, (+#), (>#))
import Yamlet.Internal.Syntax
-- | The input of the parser.
data Env = Env
{ array :: !A.Array
, base :: !Int
-- ^ The index of the first byte of the input.
, end :: !Int
-- ^ The index past the last byte that the parser can read.
, streamEnd :: !Int
-- ^ The index past the last byte of the input. A document ends before it
-- at a document marker.
, handles :: !(M.Map T.Text T.Text)
-- ^ The tag handles of the current document.
}
-- | An error that no backtracking can recover from: an error at the index
-- with the message, or a failure at the index in the environment, whose
-- message the caller of the parser finds.
data ParseError
= ParseError !Int !String
| UnexpectedParseError !Env !Int
-- | The result of a parser: a value with the new position, a failure, or an
-- error. Both the value and the failure carry the furthest position at which
-- a parser failed, the likely location of a syntax error. The value also
-- carries the bytes that the prefixes of @%TAG@ directives added to the tags,
-- see 'addTagBytes'.
type Res# a = (# (# a, Int#, Int#, Int# #) | Int# | ParseError #)
pattern OK# :: a -> Int# -> Int# -> Int# -> Res# a
pattern OK# a p f t = (# (# a, p, f, t #) | | #)
pattern Fail# :: Int# -> Res# a
pattern Fail# f = (# | f | #)
pattern Err# :: ParseError -> Res# a
pattern Err# e = (# | | e #)
{-# COMPLETE OK#, Fail#, Err# #-}
newtype P a = P (Env -> Int# -> Int# -> Int# -> Res# a)
runP :: P a -> Env -> Int# -> Int# -> Int# -> Res# a
runP (P g) = g
instance Functor P where
fmap f (P g) = P $ \e p fu t -> case g e p fu t of
OK# a p' fu' t' -> OK# (f a) p' fu' t'
Fail# fu' -> Fail# fu'
Err# err -> Err# err
instance Applicative P where
pure a = P $ \_ p fu t -> OK# a p fu t
(<*>) = ap
P g *> P h = P $ \e p fu t -> case g e p fu t of
OK# _ p' fu' t' -> h e p' fu' t'
Fail# fu' -> Fail# fu'
Err# err -> Err# err
-- The default builds the result with 'fmap' and '<*>'.
P g <* P h = P $ \e p fu t -> case g e p fu t of
OK# a p' fu' t' -> case h e p' fu' t' of
OK# _ p'' fu'' t'' -> OK# a p'' fu'' t''
Fail# fu'' -> Fail# fu''
Err# err -> Err# err
Fail# fu' -> Fail# fu'
Err# err -> Err# err
instance Monad P where
P g >>= k = P $ \e p fu t -> case g e p fu t of
OK# a p' fu' t' -> runP (k a) e p' fu' t'
Fail# fu' -> Fail# fu'
Err# err -> Err# err
-- | Run a parser from the given index. Return the result, the index after it
-- and the furthest failure.
runParser :: Env -> Int -> P a -> Either ParseError (Maybe a, Int, Int)
runParser e (I# p) (P g) = case g e p p 0# of
OK# a p' fu _ -> Right (Just a, I# p', I# fu)
Fail# fu -> Right (Nothing, I# p, I# fu)
Err# err -> Left err
----------------------------------------
-- Combinators
infixl 3 <|>
-- | Ordered choice. Try the second parser if the first one fails.
(<|>) :: P a -> P a -> P a
P g <|> P h = P $ \e p fu t -> case g e p fu t of
Fail# fu' -> h e p fu' t
r -> r
-- | Zero or more times. Stop if the parser succeeds without input.
many :: P a -> P [a]
many (P g) = P $ \e p0 fu0 t0 ->
let go acc p fu t = case g e p fu t of
OK# a p' fu' t'
| isTrue# (p' ># p) -> go (a : acc) p' fu' t'
| otherwise -> OK# (reverse acc) p fu' t
Fail# fu' -> OK# (reverse acc) p fu' t
Err# err -> Err# err
in go [] p0 fu0 t0
-- | Zero or more times, discard the results.
many_ :: P a -> P ()
many_ (P g) = P $ \e p0 fu0 t0 ->
let go p fu t = case g e p fu t of
OK# _ p' fu' t'
| isTrue# (p' ># p) -> go p' fu' t'
| otherwise -> OK# () p fu' t
Fail# fu' -> OK# () p fu' t
Err# err -> Err# err
in go p0 fu0 t0
optional :: P a -> P (Maybe a)
optional p = (Just <$> p) <|> pure Nothing
optional_ :: P a -> P ()
optional_ p = void p <|> pure ()
option :: a -> P a -> P a
option a p = p <|> pure a
-- | Succeed without input if the parser fails. An error of the parser stays
-- an error, as in '<|>'.
notFollowedBy :: P a -> P ()
notFollowedBy (P g) = P $ \e p fu t -> case g e p fu t of
OK# {} -> Fail# (furthestOf p fu)
Fail# _ -> OK# () p fu t
Err# err -> Err# err
-- | The furthest of a position where the parser failed and the furthest
-- failure so far.
furthestOf :: Int# -> Int# -> Int#
furthestOf p fu = if isTrue# (p ># fu) then p else fu
----------------------------------------
-- Primitives
env :: P Env
env = P $ \e p fu t -> OK# e p fu t
pos :: P Int
pos = P $ \_ p fu t -> OK# (I# p) p fu t
setPos :: Int -> P ()
setPos (I# p) = P $ \_ _ fu t -> OK# () p fu t
-- | The furthest position at which a parser failed so far.
furthest :: P Int
furthest = P $ \_ p fu t -> OK# (I# fu) p fu t
advance :: Int -> P ()
advance (I# n) = P $ \_ p fu t -> OK# () (p +# n) fu t
-- | Add the bytes that the prefix of a @%TAG@ directive adds to a tag, and
-- return the bytes that such prefixes added so far. A parser that fails
-- drops its bytes, so only the tags of the result count.
addTagBytes :: Int -> P Int
addTagBytes (I# n) = P $ \_ p fu t -> let t' = t +# n in OK# (I# t') p fu t'
-- | The byte at the current position, 0 at the end of the input.
peek :: P Word8
peek = P $ \e p fu t -> OK# (byteAt e (I# p)) p fu t
-- | The byte at the given distance from the current position.
peekAt :: Int -> P Word8
peekAt k = P $ \e p fu t -> OK# (byteAt e (I# p + k)) p fu t
failure :: P a
failure = P $ \_ p fu _ -> Fail# (furthestOf p fu)
guardP :: Bool -> P ()
guardP b = unless b failure
-- | Stop with an error at the given index.
throwAt :: Int -> String -> P a
throwAt i msg = P $ \_ _ _ _ -> Err# (ParseError i msg)
-- | Stop at the index, with an error whose message the caller of the parser
-- finds.
throwUnexpected :: Int -> P a
throwUnexpected i = P $ \e _ _ _ -> Err# (UnexpectedParseError e i)
-- | Run a parser that cannot read past the given index.
withEnd :: Int -> P a -> P a
withEnd end (P g) = P $ \e p fu t -> g e {end = end} p fu t
withHandles :: M.Map T.Text T.Text -> P a -> P a
withHandles hs (P g) = P $ \e p fu t -> g e {handles = hs} p fu t
char :: Word8 -> P ()
char w = P $ \e p fu t ->
if byteAt e (I# p) == w
then OK# () (p +# 1#) fu t
else Fail# (furthestOf p fu)
skipWhile :: (Word8 -> Bool) -> P ()
skipWhile f = P $ \e p fu t ->
let go i = if f (byteAt e i) then go (i + 1) else i
in case go (I# p) of I# p' -> OK# () p' fu t
-- | The result of a scanning loop.
data Scanned a
= -- | The value and the index after it.
Done !Int !a
| -- | The input does not match. The index is the location of the mismatch.
NoMatch !Int
| -- | An error at the index.
Failed !Int !String
-- | Run a pure loop over the input from the current position.
withScan :: (Env -> Int -> Scanned a) -> P a
withScan f = P $ \e p fu t -> case f e (I# p) of
Done (I# q) a -> OK# a q fu t
NoMatch (I# q) -> Fail# (furthestOf q fu)
Failed i msg -> Err# (ParseError i msg)
-- | Move to the index that the function computes from the current one.
scan :: (Env -> Int -> Int) -> P ()
scan f = P $ \e p fu t -> case f e (I# p) of I# p' -> OK# () p' fu t
----------------------------------------
-- Input access
byteAt :: Env -> Int -> Word8
byteAt e i
| i < e.end = A.unsafeIndex e.array i
| otherwise = 0
-- | The byte before the index, 0 at the start of the input.
byteBefore :: Env -> Int -> Word8
byteBefore e i
| i > e.base = A.unsafeIndex e.array (i - 1)
| otherwise = 0
slice :: Env -> Int -> Int -> T.Text
slice e i j
| j > i = T.Text e.array i (j - i)
-- With 'T.empty', GHC moves the content of an empty quoted key, e.g. in
-- "'': x", to a constant, and builds the node of the key as a thunk that
-- waits for the evaluation of 'T.empty'. A pragma on a copy of 'T.empty'
-- does not prevent this. The heap check of the render tests finds this
-- thunk.
| otherwise = T.Text e.array i 0
toOffset :: Env -> Int -> Offset
toOffset e i = Offset (i - e.base)