looksee-0.3.0: src/Looksee.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
-- | A simple text parser with decent errors
module Looksee
( Span (..)
, LineColLookup ()
, calculateLineCol
, lookupLineCol
, Label (..)
, textSpan
, SplitComp (..)
, Reason (..)
, ErrF (..)
, Err (..)
, errSpan
, errReason
, AltPhase (..)
, InfixPhase (..)
, ParserT
, Parser
, parseT
, parse
, parseI
, spanP
, throwP
, altP
, emptyP
, endP
, optP
, greedyP
, greedy1P
, lookP
, labelP
, textP
, textP_
, charP
, charP_
, breakP
, someBreakP
, splitP
, splitCompP
, split1P
, split2P
, leadP
, lead1P
, trailP
, trail1P
, infixRP
, someInfixRP
, takeP
, dropP
, takeExactP
, dropExactP
, takeWhileP
, dropWhileP
, takeWhile1P
, dropWhile1P
, takeAllP
, dropAllP
, takeAll1P
, dropAll1P
, betweenP
, sepByP
, spaceP
, stripP
, stripStartP
, stripEndP
, measureP
, unconsP
, headP
, signedWithP
, signedP
, intP
, uintP
, decP
, udecP
, sciP
, usciP
, numP
, unumP
, repeatP
, repeat1P
, space1P
, strip1P
, stripStart1P
, stripEnd1P
, sepBy1P
, sepBy2P
, transP
, scopeP
, iterP
, strP
, doubleStrP
, singleStrP
, HasErrMessage (..)
, errataE
, renderE
, printE
)
where
import Control.Applicative (Alternative (..), liftA2)
import Control.Exception (Exception)
import Control.Monad (ap, void)
import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Identity (Identity (..))
import Control.Monad.Morph (MFunctor (..))
import Control.Monad.Reader (MonadReader (..))
import Control.Monad.State.Strict (MonadState (..), StateT (..), evalStateT, gets, state)
import Control.Monad.Trans (MonadTrans (..))
import Control.Monad.Writer.Strict (MonadWriter (..))
import Data.Bifoldable (Bifoldable (..))
import Data.Bifunctor (Bifunctor (..))
import Data.Bifunctor.TH (deriveBifoldable, deriveBifunctor, deriveBitraversable)
import Data.Bitraversable (Bitraversable (..))
import Data.Char (digitToInt, isDigit, isSpace)
import Data.Foldable (toList)
import Data.Functor.Foldable (Base, Corecursive (..), Recursive (..))
import Data.Maybe (fromMaybe, isJust, maybeToList)
import Data.Ratio ((%))
import Data.Scientific (Scientific)
import Data.Scientific qualified as S
import Data.Sequence (Seq (..))
import Data.Sequence qualified as Seq
import Data.String (IsString)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as TIO
import Data.Text.Lazy qualified as TL
import Data.Typeable (Typeable)
import Data.Vector (Vector)
import Data.Vector qualified as V
import Data.Void (Void, absurd)
import Errata qualified as E
import Errata.Styles qualified as E
import Errata.Types qualified as E
import System.IO (stderr)
-- | A generic span, used for tracking ranges of offsets or (line, col)
data Span a = Span {spanStart :: !a, spanEnd :: !a}
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
-- | Auxiliary data structure to translate offsets to (line, col)
type LineColLookup = Vector (Int, Int)
-- | Construct an offset lookup from a document
calculateLineCol :: Text -> LineColLookup
calculateLineCol t = V.unfoldrN (T.length t) go ((0, 0), T.unpack t)
where
go (p@(!line, !col), xs) =
case xs of
[] -> Nothing
x : xs' -> Just (p, if x == '\n' then ((line + 1, 0), xs') else ((line, col + 1), xs'))
-- | Returns 0-based (line, col) for the given offset.
-- Clamps to the valid range of offsets, returning (0, 0) for
-- empty text. Note that the valid range is from before the first
-- character to before the last, so a 3 character string has
-- three valid offsets (0, 1, and 2).
lookupLineCol :: Int -> LineColLookup -> (Int, Int)
lookupLineCol i v =
if V.null v
then (0, 0)
else v V.! max 0 (min i (V.length v - 1))
-- | A parser label (for error reporting)
newtype Label = Label {unLabel :: Text}
deriving stock (Show)
deriving newtype (Eq, Ord, IsString)
-- | Create a span from the given text
textSpan :: Text -> Span Int
textSpan t = Span 0 (T.length t)
-- private
-- Parser state
data St = St
{ stHay :: !Text
, stSpan :: !(Span Int)
, stLabels :: !(Seq Label)
}
deriving stock (Eq, Ord, Show)
-- private
-- Returns list of possible break points with positions
-- (startStream, breakPt) breakPt (breakPt + needLen, endStream)
breakAllRP :: Text -> St -> [(St, Int, St)]
breakAllRP needle (St hay (Span r0 r1) labs) = fmap go (T.breakOnAll needle hay)
where
go (hay1, hay2) =
let end1 = r0 + T.length hay1
needLen = T.length needle
rng1 = Span r0 end1
rng2 = Span (end1 + needLen) r1
st1 = St hay1 rng1 labs
st2 = St (T.drop needLen hay2) rng2 labs
in (st1, end1, st2)
-- private
breakRP :: Text -> St -> Maybe (St, Int, St)
breakRP needle (St hay (Span r0 r1) labs) =
let (hay1, hay2) = T.breakOn needle hay
in if T.null hay2
then Nothing
else
let end1 = r0 + T.length hay1
needLen = T.length needle
rng1 = Span r0 end1
rng2 = Span (end1 + needLen) r1
st1 = St hay1 rng1 labs
st2 = St (T.drop needLen hay2) rng2 labs
in Just (st1, end1, st2)
-- private
splitRP :: Text -> St -> [(St, Int)]
splitRP needle (St hay (Span r0 _) labs) = goHead (T.splitOn needle hay)
where
needLen = T.length needle
mkSt start end hayN = St hayN (Span start end) labs
goHead = \case
[] -> []
hay0 : hays ->
let end0 = r0 + T.length hay0
in (mkSt r0 end0 hay0, 0) : goTail end0 hays
goTail !endN1 = \case
[] -> []
hayN : hays ->
let startN = endN1 + needLen
endN = startN + T.length hayN
in (mkSt startN endN hayN, endN1) : goTail endN hays
-- | Phase of alternative parsing (for error reporting)
data AltPhase = AltPhaseBranch | AltPhaseCont
deriving stock (Eq, Ord, Show, Enum, Bounded)
-- | Phase of infix/split parsing (for error reporting)
data InfixPhase = InfixPhaseLeft | InfixPhaseRight | InfixPhaseCont
deriving stock (Eq, Ord, Show, Enum, Bounded)
data SplitComp = SplitCompEQ | SplitCompGE | SplitCompGT
deriving stock (Eq, Ord, Show, Enum, Bounded)
-- | Reason for parse failure
data Reason e r
= ReasonCustom !e
| ReasonSplitComp !SplitComp !Int !Text !Int
| ReasonExpect !Text !Text
| ReasonDemand !Int !Int
| ReasonLeftover !Int
| ReasonAlt !(Seq (AltPhase, r))
| ReasonInfix !(Seq (Int, InfixPhase, r))
| ReasonFail !Text
| ReasonLabelled !Label r
| ReasonLook r
| ReasonTakeNone
| ReasonEmpty
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
deriveBifunctor ''Reason
deriveBifoldable ''Reason
deriveBitraversable ''Reason
-- | Base functor for 'Err' containing the range and reason for the error
data ErrF e r = ErrF
{ efSpan :: !(Span Int)
, efReason :: !(Reason e r)
}
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
deriveBifunctor ''ErrF
deriveBifoldable ''ErrF
deriveBitraversable ''ErrF
-- | A parse error, which may contain multiple sub-errors
newtype Err e = Err {unErr :: ErrF e (Err e)}
deriving stock (Eq, Ord, Show)
instance Functor Err where
fmap f = go
where
go (Err (ErrF ra re)) = Err (ErrF ra (bimap f go re))
instance Foldable Err where
foldr f = flip go
where
go (Err (ErrF _ re)) z = bifoldr f go z re
instance Traversable Err where
traverse f = go
where
go (Err (ErrF ra re)) = fmap (Err . ErrF ra) (bitraverse f go re)
instance (Typeable e, Show e) => Exception (Err e)
type instance Base (Err e) = ErrF e
instance Recursive (Err e) where
project = unErr
instance Corecursive (Err e) where
embed = Err
-- | Span of a parse error
errSpan :: Err e -> Span Int
errSpan = efSpan . unErr
-- | Reason for a parse error
errReason :: Err e -> Reason e (Err e)
errReason = efReason . unErr
-- private
newtype T e m a = T {unT :: ExceptT (Err e) (StateT St m) a}
deriving newtype (Functor, Applicative, Monad, MonadIO, MonadState St, MonadError (Err e))
instance MonadTrans (T e) where
lift = T . lift . lift
instance MFunctor (T e) where
hoist mn (T x) = T (hoist (hoist mn) x)
deriving instance MonadReader r m => MonadReader r (T e m)
deriving instance MonadWriter w m => MonadWriter w (T e m)
-- private
runT :: T e m a -> St -> m (Either (Err e) a, St)
runT = runStateT . runExceptT . unT
-- private
mkErrT :: Monad m => Reason e (Err e) -> T e m (Err e)
mkErrT re = gets (\st -> Err (ErrF (stSpan st) re))
-- private
-- errT :: Monad m => Reason e (Err e) -> T e m a
-- errT = mkErrT >=> throwError
-- private
tryT :: Monad m => T e m r -> T e m (Either (Err e) r)
tryT t = get >>= \st -> lift (runT t st) >>= \(er, st') -> er <$ put st'
-- | The parser monad transformer
newtype ParserT e m a = ParserT {unParserT :: forall r. (Either (Err e) a -> T e m r) -> T e m r}
instance Functor (ParserT e m) where
fmap f (ParserT g) = ParserT (\j -> g (j . fmap f))
instance Applicative (ParserT e m) where
pure a = ParserT (\j -> j (Right a))
(<*>) = ap
instance Monad (ParserT e m) where
return = pure
ParserT g >>= f = ParserT (\j -> g (\case Left e -> j (Left e); Right a -> let ParserT h = f a in h j))
instance Monad m => Alternative (ParserT e m) where
empty = emptyP
p1 <|> p2 = altP [p1, p2]
many = fmap toList . greedyP
some = fmap toList . greedy1P
-- | The parser monad
type Parser e = ParserT e Identity
instance MonadTrans (ParserT e) where
lift ma = ParserT (\j -> lift ma >>= j . Right)
instance MonadIO m => MonadIO (ParserT e m) where
liftIO ma = ParserT (\j -> liftIO ma >>= j . Right)
instance Monad m => MonadFail (ParserT e m) where
fail = errP . ReasonFail . T.pack
instance MonadReader r m => MonadReader r (ParserT e m) where
ask = ParserT (\j -> ask >>= j . Right)
local f (ParserT g) = ParserT (local f . g)
instance MonadState s m => MonadState s (ParserT e m) where
get = ParserT (\j -> lift get >>= j . Right)
put s = ParserT (\j -> lift (put s) >>= j . Right)
state f = ParserT (\j -> lift (state f) >>= j . Right)
instance Semigroup a => Semigroup (ParserT e m a) where
p <> q = liftA2 (<>) p q
instance Monoid a => Monoid (ParserT e m a) where
mempty = pure mempty
-- private
finishParserT :: Monad m => ParserT e m a -> St -> m (Either (Err e) a, St)
finishParserT (ParserT g) st =
let t = g (either throwError pure)
in runT t st
-- private
getP :: Monad m => ParserT e m St
getP = ParserT (\j -> get >>= j . Right)
-- private
getsP :: Monad m => (St -> a) -> ParserT e m a
getsP f = ParserT (\j -> gets f >>= j . Right)
-- private
putP :: Monad m => St -> ParserT e m ()
putP st = ParserT (\j -> put st >>= j . Right)
-- private
stateP :: Monad m => (St -> (a, St)) -> ParserT e m a
stateP f = ParserT (\j -> state f >>= j . Right)
-- private
errP :: Monad m => Reason e (Err e) -> ParserT e m a
errP re = ParserT (\j -> mkErrT re >>= j . Left)
-- private
leftoverP :: Monad m => ParserT e m Int
leftoverP = getsP (\st -> let Span s e = stSpan st in e - s)
-- | Run a parser transformer. You must consume all input or this will error!
-- If you really don't care about the rest of the input, you can always
-- discard it with 'dropAllP'.
parseT :: Monad m => ParserT e m a -> Text -> m (Either (Err e) a)
parseT p h = fmap fst (finishParserT (p <* endP) (St h (textSpan h) Empty))
-- | Run a parser (see 'parseT')
parse :: Parser e a -> Text -> Either (Err e) a
parse p h = runIdentity (parseT p h)
-- | Run a parser and print any errors that occur
parseI :: HasErrMessage e => Parser e a -> Text -> IO (Either (Err e) a)
parseI p h = do
let ea = parse p h
case ea of
Left e -> printE "<interactive>" h e
Right _ -> pure ()
pure ea
-- | Get the span (in character offset) at the current point representing
-- the entire parseable range. At the start of parsing this will be `Span 0 n` for
-- an `n`-character document. The start offset will increase as input is consumed,
-- and the end offset will decrease as lookahead delimits the range. To evaluate
-- the "real" range of characters consumed by a parser, construct a span with the
-- starting offsets before and after executing a subparser.
spanP :: Monad m => ParserT e m (Span Int)
spanP = getsP stSpan
-- | Throw a custom parse error
throwP :: Monad m => e -> ParserT e m a
throwP = errP . ReasonCustom
-- | Succeed if this is the end of input
endP :: Monad m => ParserT e m ()
endP = do
l <- leftoverP
if l == 0
then pure ()
else errP (ReasonLeftover l)
-- | Makes parse success optional
optP :: Monad m => ParserT e m a -> ParserT e m (Maybe a)
optP (ParserT g) = ParserT $ \j -> do
st0 <- get
g $ \case
Left _ -> put st0 >> j (Right Nothing)
Right a -> j (Right (Just a))
-- private
subAltP
:: Monad m
=> (Either (Err e) a -> T e m r)
-> St
-> Seq (AltPhase, Err e)
-> [ParserT e m a]
-> T e m r
subAltP j st0 = go
where
go !errs = \case
[] -> mkErrT (if Seq.null errs then ReasonEmpty else ReasonAlt errs) >>= j . Left
ParserT g : rest -> g $ \case
Left e -> put st0 >> go (errs :|> (AltPhaseBranch, e)) rest
Right r -> do
es <- tryT (j (Right r))
case es of
Left e -> put st0 >> go (errs :|> (AltPhaseCont, e)) rest
Right s -> pure s
-- | Parse with many possible branches
altP :: (Monad m, Foldable f) => f (ParserT e m a) -> ParserT e m a
altP falts = ParserT (\j -> get >>= \st0 -> subAltP j st0 Empty (toList falts))
-- | Fail with no results
emptyP :: Monad m => ParserT e m a
emptyP = ParserT (\j -> mkErrT ReasonEmpty >>= j . Left)
-- | Parse repeatedly until the parser fails
greedyP :: Monad m => ParserT e m a -> ParserT e m (Seq a)
greedyP p = go Empty
where
go !acc = do
ma <- optP p
case ma of
Nothing -> pure acc
Just a -> go (acc :|> a)
-- | Same as 'greedyP' but ensure at least one result
greedy1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)
greedy1P p = liftA2 (:<|) p (greedyP p)
-- | Lookahead - rewinds state if the parser succeeds, otherwise throws error
lookP :: Monad m => ParserT e m a -> ParserT e m a
lookP (ParserT g) = ParserT $ \j -> do
st0 <- get
g (\ea -> put st0 >> j (first (Err . ErrF (stSpan st0) . ReasonLook) ea))
-- | Labels parse errors
labelP :: Monad m => Label -> ParserT e m a -> ParserT e m a
labelP lab (ParserT g) = ParserT $ \j ->
g $ \case
Left e -> mkErrT (ReasonLabelled lab e) >>= j . Left
Right a -> j (Right a)
-- | Expect the given text at the start of the range
textP :: Monad m => Text -> ParserT e m Text
textP n = do
o <- takeP (T.length n)
if n == o
then pure n
else errP (ReasonExpect n o)
-- | Saves you from importing 'void'
textP_ :: Monad m => Text -> ParserT e m ()
textP_ = void . textP
-- | Expect the given character at the start of the range
charP :: Monad m => Char -> ParserT e m Char
charP = fmap T.head . textP . T.singleton
-- | Saves you from importing 'void'
charP_ :: Monad m => Char -> ParserT e m ()
charP_ = void . charP
-- | Split once on the delimiter (first argument), parsing everything before it with a narrowed range.
-- Chooses first split from START to END of range (see 'infixRP').
breakP :: Monad m => Text -> ParserT e m a -> ParserT e m a
breakP tx pa = fmap fst (infixRP tx pa (pure ()))
-- | Split once on the delimiter (first argument), parsing everything before it with a narrowed range.
-- Chooses splits from START to END of range (see 'someInfixRP').
someBreakP :: Monad m => Text -> ParserT e m a -> ParserT e m a
someBreakP tx pa = fmap fst (someInfixRP tx pa (pure ()))
-- private
subSplitP
:: Monad m
=> St
-> ParserT e m a
-> (Either (Err e) (Seq a) -> T e m r)
-> [(St, Int)]
-> T e m r
subSplitP st0 pa j = go Empty
where
go !acc = \case
[] -> j (Right acc)
(st, start') : sts -> do
put st
unParserT pa $ \case
Left _ -> do
let rng = stSpan st0
start = spanStart rng
hay' = T.drop (start' - start) (stHay st0)
range' = rng {spanStart = start'}
st' = st0 {stHay = hay', stSpan = range'}
put st'
j (Right acc)
Right a -> go (acc :|> a) sts
-- | Split on the delimiter, parsing segments with a narrowed range, until parsing fails.
-- Returns the sequence of successes with state at the delimiter preceding the failure (or end of input),
-- Note that this will always succeed, sometimes consuming no input and yielding empty results.
splitP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
splitP tx pa = ParserT (\j -> get >>= \st0 -> subSplitP st0 pa j (splitRP tx st0))
splitCompP :: Monad m => SplitComp -> Int -> Text -> ParserT e m a -> ParserT e m (Seq a)
splitCompP comp n tx pa = do
as <- splitP tx pa
let len = Seq.length as
ok = case comp of
SplitCompEQ -> len == n
SplitCompGE -> len >= n
SplitCompGT -> len > n
if ok then pure as else errP (ReasonSplitComp comp n tx len)
-- | Like 'splitP' but ensures the sequence is at least length 1.
split1P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
split1P = splitCompP SplitCompGE 1
-- | Like 'splitP' but ensures the sequence is at least length 2.
-- (This ensures there is at least one delimiter included.)
split2P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
split2P = splitCompP SplitCompGE 2
-- | Like 'splitP' but ensures a leading delimiter
leadP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
leadP tx pa = do
mu <- optP (textP tx)
case mu of
Nothing -> pure Empty
Just _ -> split1P tx pa
-- | Like 'split1P' but ensures a leading delimiter
lead1P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
lead1P tx pa = textP tx >> split1P tx pa
-- | Like 'splitP' but ensures a trailing delimiter
trailP :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
trailP tx pa = do
as <- splitP tx pa
case as of
Empty -> pure Empty
_ -> as <$ textP tx
-- | Like 'split1P' but ensures a trailing delimiter
trail1P :: Monad m => Text -> ParserT e m a -> ParserT e m (Seq a)
trail1P tx pa = split1P tx pa <* textP tx
-- private
subInfixP
:: Monad m
=> St
-> ParserT e m a
-> ParserT e m b
-> (Either (Err e) (Maybe (a, b)) -> T e m r)
-> [(St, Int, St)]
-> T e m r
subInfixP st0 pa pb j = go Empty
where
go !errs = \case
[] -> do
put st0
case errs of
Empty -> j (Right Nothing)
_ -> mkErrT (ReasonInfix errs) >>= j . Left
(stA, endA, stB) : sts -> do
put stA
unParserT (pa <* endP) $ \case
Left errA -> go (errs :|> (endA, InfixPhaseLeft, errA)) sts
Right a -> do
put stB
unParserT pb $ \case
Left errB -> go (errs :|> (endA, InfixPhaseRight, errB)) sts
Right b -> do
ec <- tryT (j (Right (Just (a, b))))
case ec of
Left errC -> go (errs :|> (endA, InfixPhaseCont, errC)) sts
Right c -> pure c
-- private
optInfixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (Maybe (a, b))
optInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb j (breakAllRP tx st0))
-- private
requireInfix
:: Monad m
=> (Either (Err e) (a, b) -> T e m r)
-> (Either (Err e) (Maybe (a, b)) -> T e m r)
requireInfix j = \case
Right mxab ->
case mxab of
Nothing -> mkErrT ReasonEmpty >>= j . Left
Just xab -> j (Right xab)
Left e -> j (Left e)
-- | Right-associative infix parsing. Searches for the operator from START to END of range,
-- trying only the first break point.
infixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (a, b)
infixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfix j) (maybeToList (breakRP tx st0)))
-- | Right-associative infix parsing. Searches for the operator from START to END of range,
-- trying subsequent break points until success.
someInfixRP :: Monad m => Text -> ParserT e m a -> ParserT e m b -> ParserT e m (a, b)
someInfixRP tx pa pb = ParserT (\j -> get >>= \st0 -> subInfixP st0 pa pb (requireInfix j) (breakAllRP tx st0))
-- | Take the given number of characters from the start of the range, or fewer if empty
takeP :: Monad m => Int -> ParserT e m Text
takeP i = stateP $ \st ->
let h = stHay st
(o, h') = T.splitAt i h
l = T.length o
r = stSpan st
r' = r {spanStart = spanStart r + l}
st' = st {stHay = h', stSpan = r'}
in (o, st')
-- | Take exactly the given number of characters from the start of the range, or error
takeExactP :: Monad m => Int -> ParserT e m Text
takeExactP i = do
et <- stateP $ \st ->
let h = stHay st
(o, h') = T.splitAt i h
l = T.length o
r = stSpan st
r' = r {spanStart = spanStart r + T.length o}
st' = st {stHay = h', stSpan = r'}
in if l == i then (Right o, st') else (Left l, st)
case et of
Left l -> errP (ReasonDemand i l)
Right a -> pure a
-- | Drop the given number of characters from the start of the range, or fewer if empty
dropP :: Monad m => Int -> ParserT e m Int
dropP = fmap T.length . takeP
-- | Drop exactly the given number of characters from the start of the range, or error
dropExactP :: Monad m => Int -> ParserT e m ()
dropExactP = void . takeExactP
-- | Take characters from the start of the range satisfying the predicate
takeWhileP :: Monad m => (Char -> Bool) -> ParserT e m Text
takeWhileP f = stateP $ \st ->
let h = stHay st
o = T.takeWhile f h
l = T.length o
h' = T.drop l h
r = stSpan st
r' = r {spanStart = spanStart r + l}
st' = st {stHay = h', stSpan = r'}
in (o, st')
-- | Like 'takeWhileP' but ensures at least 1 character has been taken
takeWhile1P :: Monad m => (Char -> Bool) -> ParserT e m Text
takeWhile1P f = do
mt <- stateP $ \st ->
let h = stHay st
o = T.takeWhile f h
l = T.length o
h' = T.drop l h
r = stSpan st
r' = r {spanStart = spanStart r + l}
st' = st {stHay = h', stSpan = r'}
in if l == 0 then (Nothing, st) else (Just o, st')
case mt of
Nothing -> errP ReasonTakeNone
Just a -> pure a
-- | Drop characters from the start of the range satisfying the predicate
dropWhileP :: Monad m => (Char -> Bool) -> ParserT e m Int
dropWhileP = fmap T.length . takeWhileP
-- | Like 'dropWhileP' but ensures at least 1 character has been dropped
dropWhile1P :: Monad m => (Char -> Bool) -> ParserT e m Int
dropWhile1P = fmap T.length . takeWhile1P
-- | Take the remaining range, leaving it empty
takeAllP :: Monad m => ParserT e m Text
takeAllP = stateP $ \st ->
let h = stHay st
r = stSpan st
r' = r {spanStart = spanEnd r}
st' = st {stHay = T.empty, stSpan = r'}
in (h, st')
-- | Like 'takeAllP' but ensures at least 1 character has been taken
takeAll1P :: Monad m => ParserT e m Text
takeAll1P = do
mt <- stateP $ \st ->
let h = stHay st
r = stSpan st
r' = r {spanStart = spanEnd r}
st' = st {stHay = T.empty, stSpan = r'}
in if T.null h then (Nothing, st) else (Just h, st')
case mt of
Nothing -> errP (ReasonDemand 1 0)
Just a -> pure a
-- | Drop the remaining range, leaving it empty
dropAllP :: Monad m => ParserT e m Int
dropAllP = fmap T.length takeAllP
-- | Like 'dropAllP' but ensures at least 1 character has been dropped
dropAll1P :: Monad m => ParserT e m Int
dropAll1P = fmap T.length takeAll1P
-- | Unwrap a monad transformer layer (see 'scopeP' for use)
transP :: (MonadTrans t, Monad m) => (forall a. t m a -> m a) -> ParserT e (t m) b -> ParserT e m b
transP f (ParserT g) = ParserT $ \j -> do
st0 <- get
(ea, st1) <- lift (f (runT (g (hoist lift . j)) st0))
put st1
either throwError pure ea
-- | Parse with some local state
scopeP :: Monad m => s -> ParserT e (StateT s m) a -> ParserT e m a
scopeP s0 = transP (`evalStateT` s0)
-- | Repeats the parser until it returns a 'Just' value
iterP :: ParserT e m (Maybe a) -> ParserT e m a
iterP p = go
where
go = p >>= maybe go pure
data StrState = StrState !Bool !(Seq Char)
-- | Parse a string with a custom quote character. Supports backslash-escaping.
strP :: Monad m => Char -> ParserT e m Text
strP d = do
textP_ (T.singleton d)
scopeP (StrState False Empty) $ iterP $ do
c <- headP
state $ \ss@(StrState esc buf) ->
if c == d
then
if esc
then (Nothing, StrState False (buf :|> c))
else (Just (T.pack (toList buf)), ss)
else
if c == '\\'
then
if esc
then (Nothing, StrState False (buf :|> c))
else (Nothing, StrState True buf)
else (Nothing, StrState False (buf :|> c))
-- | Parse a double-quoted string
doubleStrP :: Monad m => ParserT e m Text
doubleStrP = strP '"'
-- | Parse a single-quoted string
singleStrP :: Monad m => ParserT e m Text
singleStrP = strP '\''
-- | Parse between an opening delimiter (first parser) and a closing delimited (second parser)
betweenP :: ParserT e m x -> ParserT e m y -> ParserT e m a -> ParserT e m a
betweenP px py pa = px *> pa <* py
-- | Parse a sequence of items delimited by the first parser
sepByP :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
sepByP c p = go
where
go = do
ma <- optP p
case ma of
Nothing -> pure Empty
Just a -> goNext (Empty :|> a)
goNext !acc = do
mc <- optP c
case mc of
Nothing -> pure acc
Just _ -> do
a <- p
goNext (acc :|> a)
-- | Consumes many spaces at the start of the range
spaceP :: Monad m => ParserT e m ()
spaceP = void (dropWhileP isSpace)
-- | Strips spaces before and after parsing
stripP :: Monad m => ParserT e m a -> ParserT e m a
stripP p = spaceP *> p <* spaceP
-- | Strips spaces before parsing
stripStartP :: Monad m => ParserT e m a -> ParserT e m a
stripStartP p = spaceP *> p
-- | Strips spaces after parsing
stripEndP :: Monad m => ParserT e m a -> ParserT e m a
stripEndP p = p <* spaceP
-- | Parses and returns the length of the consumed input along with the result
measureP :: Monad m => ParserT e m a -> ParserT e m (a, Int)
measureP p = do
start <- getsP (spanStart . stSpan)
a <- p
end <- getsP (spanStart . stSpan)
pure (a, end - start)
-- | Takes exactly 1 character from the start of the range, returning Nothing
-- if at end of input
unconsP :: Monad m => ParserT e m (Maybe Char)
unconsP = stateP $ \st ->
let h = stHay st
mxy = T.uncons h
in case mxy of
Nothing -> (Nothing, st)
Just (x, y) ->
let r = stSpan st
r' = r {spanStart = spanStart r + 1}
st' = st {stHay = y, stSpan = r'}
in (Just x, st')
-- | Takes exactly 1 character from the start of the range, throwing error
-- if at end of input
headP :: Monad m => ParserT e m Char
headP = unconsP >>= maybe (errP (ReasonDemand 1 0)) pure
-- | Add signed-ness to any parser with a negate function
signedWithP :: Monad m => (a -> a) -> ParserT e m a -> ParserT e m a
signedWithP neg p = do
ms <- optP (charP '-')
case ms of
Nothing -> p
Just _ -> fmap neg p
-- | Add signed-ness to any numeric parser
signedP :: (Monad m, Num a) => ParserT e m a -> ParserT e m a
signedP = signedWithP negate
-- | Parse an signed integer
intP :: Monad m => ParserT e m Integer
intP = signedP uintP
-- | Parse an unsigned integer
uintP :: Monad m => ParserT e m Integer
uintP = T.foldl' (\n d -> n * 10 + fromIntegral (digitToInt d)) 0 <$> takeWhile1P isDigit
-- | Parse a signed decimal
decP :: Monad m => ParserT e m Rational
decP = signedP udecP
-- | Parse an unsigned decimal
udecP :: Monad m => ParserT e m Rational
udecP = do
whole <- fmap fromInteger uintP
hasDot <- fmap isJust (optP (charP '.'))
if hasDot
then do
(numerator, places) <- measureP uintP
let denominator = 10 ^ places
part = numerator % denominator
pure (whole + part)
else pure whole
-- | Parse a signed scientific number
sciP :: Monad m => ParserT e m Scientific
sciP = signedP usciP
-- | Parse an unsigned scientific number
usciP :: Monad m => ParserT e m Scientific
usciP = do
whole <- uintP
hasDot <- fmap isJust (optP (charP_ '.'))
(frac, places) <- if hasDot then measureP uintP else pure (0, 0)
hasEx <- fmap isJust (optP (charP_ 'e' <|> charP_ 'E'))
ex <- if hasEx then fmap fromIntegral intP else pure 0
let wholeS = S.scientific whole ex
partS = S.scientific frac (ex - places)
pure (wholeS + partS)
-- | Parse a signed integer/scientific number, defaulting to integer if possible.
numP :: Monad m => ParserT e m (Either Integer Scientific)
numP = signedWithP (bimap negate negate) unumP
-- | Parse an unsigned integer/scientific number, defaulting to integer if possible.
unumP :: Monad m => ParserT e m (Either Integer Scientific)
unumP = do
whole <- uintP
hasDot <- fmap isJust (optP (charP_ '.'))
mayFracPlaces <- if hasDot then fmap Just (measureP uintP) else pure Nothing
hasEx <- fmap isJust (optP (charP_ 'e' <|> charP_ 'E'))
mayEx <- if hasEx then fmap (Just . fromIntegral) intP else pure Nothing
case (mayFracPlaces, mayEx) of
(Nothing, Nothing) -> pure (Left whole)
_ -> do
let (frac, places) = fromMaybe (0, 0) mayFracPlaces
ex = fromMaybe 0 mayEx
wholeS = S.scientific whole ex
partS = S.scientific frac (ex - places)
pure (Right (wholeS + partS))
-- | Repeat a parser until it fails, collecting the results.
repeatP :: Monad m => ParserT e m a -> ParserT e m (Seq a)
repeatP p = go Empty
where
go !acc = do
ma <- optP p
case ma of
Nothing -> pure acc
Just a -> go (acc :|> a)
-- | Like 'repeatP' but ensures at least 1
repeat1P :: Monad m => ParserT e m a -> ParserT e m (Seq a)
repeat1P p = liftA2 (:<|) p (repeatP p)
-- | Like 'spaceP' but ensures at least 1 space removed
space1P :: Monad m => ParserT e m ()
space1P = void (dropWhile1P isSpace)
-- | Like 'stripP' but ensures at least 1 space removed
strip1P :: Monad m => ParserT e m a -> ParserT e m a
strip1P p = space1P *> p <* space1P
-- | Like 'stripStartP' but ensures at least 1 space removed
stripStart1P :: Monad m => ParserT e m a -> ParserT e m a
stripStart1P p = space1P *> p
-- | Like 'stripEndP' but ensures at least 1 space removed
stripEnd1P :: Monad m => ParserT e m a -> ParserT e m a
stripEnd1P p = p <* space1P
-- | Like 'sepByP' but ensures at least 1 element
sepBy1P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
sepBy1P px pa = liftA2 (:<|) pa (fmap (fromMaybe Empty) (optP (px *> sepByP px pa)))
-- | Like 'sepBy1P' but ensures at least 2 elements (i.e. there was a delimiter)
sepBy2P :: Monad m => ParserT e m x -> ParserT e m a -> ParserT e m (Seq a)
sepBy2P px pa = liftA2 (:<|) (pa <* px) (sepBy1P px pa)
-- | Implement this to format custom errors. The list will be joined with `unlines`.
class HasErrMessage e where
getErrMessage :: e -> [Text]
instance HasErrMessage Void where
getErrMessage = absurd
-- private
indent :: Int -> [Text] -> [Text]
indent i = let s = T.replicate (2 * i) " " in fmap (s <>)
instance HasErrMessage e => HasErrMessage (Err e) where
getErrMessage (Err (ErrF (Span start end) re)) =
let pos = "Error in range: (" <> T.pack (show start) <> ", " <> T.pack (show end) <> ")"
body = case re of
ReasonCustom e ->
let hd = "Custom error:"
tl = indent 1 (getErrMessage e)
in hd : tl
ReasonSplitComp comp n tx len ->
let op = case comp of SplitCompEQ -> "=="; SplitCompGE -> ">="; SplitCompGT -> ">"
in ["Split on \"" <> tx <> "\" with length " <> T.pack (show len) <> " not " <> op <> " " <> T.pack (show n)]
ReasonExpect expected actual ->
["Expected text: '" <> expected <> "' but found: '" <> actual <> "'"]
ReasonDemand expected actual ->
["Expected count: " <> T.pack (show expected) <> " but got: " <> T.pack (show actual)]
ReasonLeftover count ->
["Expected end but had leftover count: " <> T.pack (show count)]
ReasonAlt errs ->
let hd = "Alternatives:"
tl = indent 1 $ do
(_, e) <- toList errs
"Tried:" : indent 1 (getErrMessage e)
in hd : tl
ReasonInfix errs ->
let hd = "Infix/split failed:"
tl = indent 1 $ do
(i, _, e) <- toList errs
let x = "Tried position: " <> T.pack (show i)
x : indent 1 (getErrMessage e)
in hd : tl
ReasonFail msg -> ["User reported failure: " <> msg]
ReasonLabelled lab e ->
let hd = "Labelled parser: " <> unLabel lab
tl = indent 1 (getErrMessage e)
in hd : tl
ReasonLook e ->
let hd = "Error in lookahead:"
tl = indent 1 (getErrMessage e)
in hd : tl
ReasonTakeNone -> ["Took/dropped no elements"]
ReasonEmpty -> ["No parse results"]
in pos : body
-- | Create 'Errata' formatting a parse error
errataE :: HasErrMessage e => FilePath -> (Int -> (E.Line, E.Column)) -> Err e -> [E.Errata]
errataE fp mkP e =
let (line, col) = mkP (spanStart (errSpan e))
msg = getErrMessage e
block = E.blockSimple E.basicStyle E.basicPointer fp Nothing (line, col, col + 1, Nothing) (Just (T.unlines msg))
in [E.Errata Nothing [block] Nothing]
-- | Render a formatted error to text
renderE :: HasErrMessage e => FilePath -> Text -> Err e -> Text
renderE fp h e =
let v = calculateLineCol h
mkP i = let (l, c) = lookupLineCol i v in (l + 1, c + 1)
in TL.toStrict (E.prettyErrors h (errataE fp mkP e))
-- | Print a formatted error to stderr
printE :: HasErrMessage e => FilePath -> Text -> Err e -> IO ()
printE fp h e = TIO.hPutStrLn stderr (renderE fp h e)