module Text.Megaparsec.Error
( ErrorItem (..)
, ErrorComponent (..)
, Dec (..)
, ParseError (..)
, ShowToken (..)
, ShowErrorComponent (..)
, parseErrorPretty
, sourcePosStackPretty
, parseErrorTextPretty )
where
import Control.DeepSeq
import Control.Monad.Catch
import Data.Data (Data)
import Data.Foldable (concat)
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Semigroup
import Data.Set (Set)
import Data.Typeable (Typeable)
import GHC.Generics
import Prelude hiding (concat)
import Test.QuickCheck hiding (label)
import qualified Data.List.NonEmpty as NE
import qualified Data.Set as E
import Text.Megaparsec.Pos
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative
#endif
data ErrorItem t
= Tokens (NonEmpty t)
| Label (NonEmpty Char)
| EndOfInput
deriving (Show, Read, Eq, Ord, Data, Typeable, Generic)
instance NFData t => NFData (ErrorItem t)
instance Arbitrary t => Arbitrary (ErrorItem t) where
arbitrary = oneof
[ Tokens <$> (NE.fromList . getNonEmpty <$> arbitrary)
, Label <$> (NE.fromList . getNonEmpty <$> arbitrary)
, return EndOfInput ]
class Ord e => ErrorComponent e where
representFail :: String -> e
representIndentation
:: Ordering
-> Pos
-> Pos
-> e
instance ErrorComponent () where
representFail _ = ()
representIndentation _ _ _ = ()
data Dec
= DecFail String
| DecIndentation Ordering Pos Pos
deriving (Show, Read, Eq, Ord, Data, Typeable)
instance NFData Dec where
rnf (DecFail str) = rnf str
rnf (DecIndentation ord ref act) = ord `seq` rnf ref `seq` rnf act
instance Arbitrary Dec where
arbitrary = oneof
[ sized (\n -> do
k <- choose (0, n `div` 2)
DecFail <$> vectorOf k arbitrary)
, DecIndentation <$> arbitrary <*> arbitrary <*> arbitrary ]
instance ErrorComponent Dec where
representFail = DecFail
representIndentation = DecIndentation
data ParseError t e = ParseError
{ errorPos :: NonEmpty SourcePos
, errorUnexpected :: Set (ErrorItem t)
, errorExpected :: Set (ErrorItem t)
, errorCustom :: Set e
} deriving (Show, Read, Eq, Data, Typeable, Generic)
instance (NFData t, NFData e) => NFData (ParseError t e)
instance (Ord t, Ord e) => Semigroup (ParseError t e) where
(<>) = mergeError
instance (Ord t, Ord e) => Monoid (ParseError t e) where
mempty = ParseError (initialPos "" :| []) E.empty E.empty E.empty
mappend = (<>)
instance ( Show t
, Typeable t
, Ord t
, ShowToken t
, Show e
, Typeable e
, ShowErrorComponent e )
=> Exception (ParseError t e) where
#if MIN_VERSION_base(4,8,0)
displayException = parseErrorPretty
#endif
instance (Arbitrary t, Ord t, Arbitrary e, Ord e)
=> Arbitrary (ParseError t e) where
arbitrary = ParseError
<$> (NE.fromList . getNonEmpty <$> arbitrary)
#if MIN_VERSION_QuickCheck(2,8,2)
<*> arbitrary
<*> arbitrary
<*> arbitrary
#else
<*> (E.fromList <$> arbitrary)
<*> (E.fromList <$> arbitrary)
<*> (E.fromList <$> arbitrary)
#endif
mergeError :: (Ord t, Ord e)
=> ParseError t e
-> ParseError t e
-> ParseError t e
mergeError e1@(ParseError pos1 u1 p1 x1) e2@(ParseError pos2 u2 p2 x2) =
case pos1 `compare` pos2 of
LT -> e2
EQ -> ParseError pos1 (E.union u1 u2) (E.union p1 p2) (E.union x1 x2)
GT -> e1
class ShowToken a where
showTokens :: NonEmpty a -> String
instance ShowToken Char where
showTokens = stringPretty
stringPretty :: NonEmpty Char -> String
stringPretty (x:|[]) = charPretty x
stringPretty ('\r':|"\n") = "crlf newline"
stringPretty xs = "\"" ++ NE.toList xs ++ "\""
charPretty :: Char -> String
charPretty '\NUL' = "null (control character)"
charPretty '\SOH' = "start of heading (control character)"
charPretty '\STX' = "start of text (control character)"
charPretty '\ETX' = "end of text (control character)"
charPretty '\EOT' = "end of transmission (control character)"
charPretty '\ENQ' = "enquiry (control character)"
charPretty '\ACK' = "acknowledge (control character)"
charPretty '\BEL' = "bell (control character)"
charPretty '\BS' = "backspace"
charPretty '\t' = "tab"
charPretty '\n' = "newline"
charPretty '\v' = "vertical tab"
charPretty '\f' = "form feed (control character)"
charPretty '\r' = "carriage return"
charPretty '\SO' = "shift out (control character)"
charPretty '\SI' = "shift in (control character)"
charPretty '\DLE' = "data link escape (control character)"
charPretty '\DC1' = "device control one (control character)"
charPretty '\DC2' = "device control two (control character)"
charPretty '\DC3' = "device control three (control character)"
charPretty '\DC4' = "device control four (control character)"
charPretty '\NAK' = "negative acknowledge (control character)"
charPretty '\SYN' = "synchronous idle (control character)"
charPretty '\ETB' = "end of transmission block (control character)"
charPretty '\CAN' = "cancel (control character)"
charPretty '\EM' = "end of medium (control character)"
charPretty '\SUB' = "substitute (control character)"
charPretty '\ESC' = "escape (control character)"
charPretty '\FS' = "file separator (control character)"
charPretty '\GS' = "group separator (control character)"
charPretty '\RS' = "record separator (control character)"
charPretty '\US' = "unit separator (control character)"
charPretty '\DEL' = "delete (control character)"
charPretty ' ' = "space"
charPretty '\160' = "non-breaking space"
charPretty x = "'" ++ [x] ++ "'"
class Ord a => ShowErrorComponent a where
showErrorComponent :: a -> String
instance (Ord t, ShowToken t) => ShowErrorComponent (ErrorItem t) where
showErrorComponent (Tokens ts) = showTokens ts
showErrorComponent (Label label) = NE.toList label
showErrorComponent EndOfInput = "end of input"
instance ShowErrorComponent Dec where
showErrorComponent (DecFail msg) = msg
showErrorComponent (DecIndentation 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 "
parseErrorPretty :: ( Ord t
, ShowToken t
, ShowErrorComponent e )
=> ParseError t e
-> String
parseErrorPretty e =
sourcePosStackPretty (errorPos e) ++ ":\n" ++ parseErrorTextPretty e
sourcePosStackPretty :: NonEmpty SourcePos -> String
sourcePosStackPretty ms = concatMap f rest ++ sourcePosPretty pos
where (pos :| rest') = ms
rest = reverse rest'
f p = "in file included from " ++ sourcePosPretty p ++ ",\n"
messageItemsPretty :: ShowErrorComponent a
=> String
-> Set a
-> String
messageItemsPretty prefix ts
| E.null ts = ""
| otherwise =
let f = orList . NE.fromList . E.toAscList . E.map showErrorComponent
in prefix ++ f 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
parseErrorTextPretty :: ( Ord t
, ShowToken t
, ShowErrorComponent e )
=> ParseError t e
-> String
parseErrorTextPretty (ParseError _ us ps xs) =
if E.null us && E.null ps && E.null xs
then "unknown parse error\n"
else concat
[ messageItemsPretty "unexpected " us
, messageItemsPretty "expecting " ps
, unlines (showErrorComponent <$> E.toAscList xs) ]