{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Text.Megaparsec.Error
(
ErrorItem (..)
, ErrorFancy (..)
, ParseError (..)
, mapParseError
, errorOffset
, setErrorOffset
, ParseErrorBundle (..)
, attachSourcePos
, ShowErrorComponent (..)
, errorBundlePretty
, parseErrorPretty
, parseErrorTextPretty )
where
import Control.DeepSeq
import Control.Exception
import Control.Monad.State.Strict
import Data.Data (Data)
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (isNothing)
import Data.Proxy
import Data.Set (Set)
import Data.Typeable (Typeable)
import Data.Void
import GHC.Generics
import Text.Megaparsec.Pos
import Text.Megaparsec.State
import Text.Megaparsec.Stream
import qualified Data.List.NonEmpty as NE
import qualified Data.Set as E
data ErrorItem t
= Tokens (NonEmpty t)
| Label (NonEmpty Char)
| EndOfInput
deriving (Show, Read, Eq, Ord, Data, Typeable, Generic, Functor)
instance NFData t => NFData (ErrorItem t)
data ErrorFancy e
= ErrorFail String
| ErrorIndentation Ordering Pos Pos
| ErrorCustom e
deriving (Show, Read, Eq, Ord, Data, Typeable, Generic, Functor)
instance NFData a => NFData (ErrorFancy a) where
rnf (ErrorFail str) = rnf str
rnf (ErrorIndentation ord ref act) = ord `seq` rnf ref `seq` rnf act
rnf (ErrorCustom a) = rnf a
data ParseError s e
= TrivialError Int (Maybe (ErrorItem (Token s))) (Set (ErrorItem (Token s)))
| FancyError Int (Set (ErrorFancy e))
deriving (Typeable, Generic)
deriving instance ( Show (Token s)
, Show e
) => Show (ParseError s e)
deriving instance ( Eq (Token s)
, Eq e
) => Eq (ParseError s e)
deriving instance ( Data s
, Data (Token s)
, Ord (Token s)
, Data e
, Ord e
) => Data (ParseError s e)
instance ( NFData (Token s)
, NFData e
) => NFData (ParseError s e)
instance (Stream s, Ord e) => Semigroup (ParseError s e) where
(<>) = mergeError
{-# INLINE (<>) #-}
instance (Stream s, Ord e) => Monoid (ParseError s e) where
mempty = TrivialError 0 Nothing E.empty
mappend = (<>)
{-# INLINE mappend #-}
instance ( Show s
, Show (Token s)
, Show e
, ShowErrorComponent e
, Stream s
, Typeable s
, Typeable e )
=> Exception (ParseError s e) where
displayException = parseErrorPretty
mapParseError :: Ord e'
=> (e -> e')
-> ParseError s e
-> ParseError s e'
mapParseError _ (TrivialError o u p) = TrivialError o u p
mapParseError f (FancyError o x) = FancyError o (E.map (fmap f) x)
errorOffset :: ParseError s e -> Int
errorOffset (TrivialError o _ _) = o
errorOffset (FancyError o _) = o
setErrorOffset :: Int -> ParseError s e -> ParseError s e
setErrorOffset o (TrivialError _ u p) = TrivialError o u p
setErrorOffset o (FancyError _ x) = FancyError o x
mergeError :: (Stream s, Ord e)
=> ParseError s e
-> ParseError s e
-> ParseError s e
mergeError e1 e2 =
case errorOffset e1 `compare` errorOffset e2 of
LT -> e2
EQ ->
case (e1, e2) of
(TrivialError s1 u1 p1, TrivialError _ u2 p2) ->
TrivialError s1 (n u1 u2) (E.union p1 p2)
(FancyError {}, TrivialError {}) -> e1
(TrivialError {}, FancyError {}) -> e2
(FancyError s1 x1, FancyError _ x2) ->
FancyError s1 (E.union x1 x2)
GT -> e1
where
n Nothing Nothing = Nothing
n (Just x) Nothing = Just x
n Nothing (Just y) = Just y
n (Just x) (Just y) = Just (max x y)
{-# INLINE mergeError #-}
data ParseErrorBundle s e = ParseErrorBundle
{ bundleErrors :: NonEmpty (ParseError s e)
, bundlePosState :: PosState s
} deriving (Generic)
deriving instance ( Show s
, Show (Token s)
, Show e
) => Show (ParseErrorBundle s e)
deriving instance ( Eq s
, Eq (Token s)
, Eq e
) => Eq (ParseErrorBundle s e)
deriving instance ( Typeable s
, Typeable (Token s)
, Typeable e
) => Typeable (ParseErrorBundle s e)
deriving instance ( Data s
, Data (Token s)
, Ord (Token s)
, Data e
, Ord e
) => Data (ParseErrorBundle s e)
instance ( NFData s
, NFData (Token s)
, NFData e
) => NFData (ParseErrorBundle s e)
instance ( Show s
, Show (Token s)
, Show e
, ShowErrorComponent e
, Stream s
, Typeable s
, Typeable e
) => Exception (ParseErrorBundle s e) where
displayException = errorBundlePretty
attachSourcePos
:: (Traversable t, Stream s)
=> (a -> Int)
-> t a
-> PosState s
-> (t (a, SourcePos), PosState s)
attachSourcePos projectOffset xs = runState (traverse f xs)
where
f a = do
pst <- get
let pst' = reachOffsetNoLine (projectOffset a) pst
put pst'
return (a, pstateSourcePos pst')
{-# INLINEABLE attachSourcePos #-}
class Ord a => ShowErrorComponent a where
showErrorComponent :: a -> String
errorComponentLen :: a -> Int
errorComponentLen _ = 1
instance ShowErrorComponent Void where
showErrorComponent = absurd
errorBundlePretty
:: forall s e. ( Stream s
, ShowErrorComponent e
)
=> ParseErrorBundle s e
-> String
errorBundlePretty ParseErrorBundle {..} =
let (r, _) = foldl f (id, bundlePosState) bundleErrors
in drop 1 (r "")
where
f :: (ShowS, PosState s)
-> ParseError s e
-> (ShowS, PosState s)
f (o, !pst) e = (o . (outChunk ++), pst')
where
(sline, pst') = reachOffset (errorOffset e) pst
epos = pstateSourcePos pst'
outChunk =
"\n" <> sourcePosPretty epos <> ":\n" <>
padding <> "|\n" <>
lineNumber <> " | " <> sline <> "\n" <>
padding <> "| " <> rpadding <> pointer <> "\n" <>
parseErrorTextPretty e
lineNumber = (show . unPos . sourceLine) epos
padding = replicate (length lineNumber + 1) ' '
rpadding =
if pointerLen > 0
then replicate rpshift ' '
else ""
rpshift = unPos (sourceColumn epos) - 1
pointer = replicate pointerLen '^'
pointerLen =
if rpshift + elen > slineLen
then slineLen - rpshift + 1
else elen
slineLen = length sline
pxy = Proxy :: Proxy s
elen =
case e of
TrivialError _ Nothing _ -> 1
TrivialError _ (Just x) _ -> errorItemLength pxy x
FancyError _ xs ->
E.foldl' (\a b -> max a (errorFancyLength b)) 1 xs
parseErrorPretty
:: (Stream s, ShowErrorComponent e)
=> ParseError s e
-> String
parseErrorPretty e =
"offset=" <> show (errorOffset e) <> ":\n" <> parseErrorTextPretty e
parseErrorTextPretty
:: forall s e. (Stream s, ShowErrorComponent e)
=> ParseError s e
-> String
parseErrorTextPretty (TrivialError _ us ps) =
if isNothing us && E.null ps
then "unknown parse error\n"
else messageItemsPretty "unexpected " (showErrorItem pxy `E.map` maybe E.empty E.singleton us) <>
messageItemsPretty "expecting " (showErrorItem pxy `E.map` ps)
where
pxy = Proxy :: Proxy s
parseErrorTextPretty (FancyError _ xs) =
if E.null xs
then "unknown fancy parse error\n"
else unlines (showErrorFancy <$> E.toAscList xs)
showErrorItem :: Stream s => Proxy s -> ErrorItem (Token s) -> String
showErrorItem pxy = \case
Tokens ts -> showTokens pxy ts
Label label -> NE.toList label
EndOfInput -> "end of input"
errorItemLength :: Stream s => Proxy s -> ErrorItem (Token s) -> Int
errorItemLength pxy = \case
Tokens ts -> tokensLength pxy ts
_ -> 1
showErrorFancy :: ShowErrorComponent e => ErrorFancy e -> String
showErrorFancy = \case
ErrorFail msg -> msg
ErrorIndentation ord ref actual ->
"incorrect indentation (got " <> show (unPos actual) <>
", should be " <> p <> show (unPos ref) <> ")"
where
p = case ord of
LT -> "less than "
EQ -> "equal to "
GT -> "greater than "
ErrorCustom a -> showErrorComponent a
errorFancyLength :: ShowErrorComponent e => ErrorFancy e -> Int
errorFancyLength = \case
ErrorCustom a -> errorComponentLen a
_ -> 1
messageItemsPretty
:: String
-> Set String
-> String
messageItemsPretty prefix ts
| E.null ts = ""
| otherwise =
prefix <> (orList . NE.fromList . E.toAscList) ts <> "\n"
orList :: NonEmpty String -> String
orList (x:|[]) = x
orList (x:|[y]) = x <> " or " <> y
orList xs = intercalate ", " (NE.init xs) <> ", or " <> NE.last xs