gigaparsec-0.2.2.2: src/Text/Gigaparsec/Internal/Errors/ParseError.hs
{-# LANGUAGE Safe #-}
{-# OPTIONS_HADDOCK hide #-}
{-# OPTIONS_GHC -Wno-partial-fields -Wno-missing-import-lists #-}
module Text.Gigaparsec.Internal.Errors.ParseError (
module Text.Gigaparsec.Internal.Errors.ParseError
) where
import Prelude hiding (lines)
import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty, (<|))
import Data.Set qualified as Set (map, foldr)
import Text.Gigaparsec.Errors.ErrorBuilder (ErrorBuilder, Token)
import Text.Gigaparsec.Errors.ErrorBuilder qualified as Builder (ErrorBuilder(..))
import Text.Gigaparsec.Errors.ErrorBuilder qualified as Token (Token(..))
import Text.Gigaparsec.Internal.Errors.CaretControl
import Text.Gigaparsec.Internal.Errors.ErrorItem
import Data.Set (Set)
type ParseError :: *
data ParseError = VanillaError { presentationOffset :: {-# UNPACK #-} !Word
, line :: {-# UNPACK #-} !Word
, col :: {-# UNPACK #-} !Word
, unexpected :: !(Either Word UnexpectItem)
, expecteds :: !(Set ExpectItem)
, reasons :: !(Set String)
, lexicalError :: !Bool
}
| SpecialisedError { presentationOffset :: {-# UNPACK #-} !Word
, line :: {-# UNPACK #-} !Word
, col :: {-# UNPACK #-} !Word
, msgs :: ![String]
, caretWidth :: {-# UNPACK #-} !Span
}
{-# INLINABLE fromParseError #-}
fromParseError :: forall err. ErrorBuilder err => Maybe FilePath -> String -> ParseError -> err
fromParseError srcFile input err =
Builder.format (Builder.pos @err (line err) (col err)) (Builder.source @err srcFile)
(formatErr err)
where formatErr :: ParseError -> Builder.ErrorInfoLines err
formatErr VanillaError{..} =
Builder.vanillaError @err
(Builder.unexpected @err (either (const Nothing) (Just . fst) unexpectedTok))
(Builder.expected @err (Builder.combineExpectedItems @err (Set.map expectItem expecteds)))
(Builder.combineMessages @err (Set.foldr (\r -> (Builder.reason @err r :)) [] reasons))
(Builder.lineInfo @err curLine linesBefore linesAfter caret (trimToLine caretSize))
where unexpectedTok = unexpectItem lexicalError <$> unexpected
caretSize = either id snd unexpectedTok
formatErr SpecialisedError{..} =
Builder.specialisedError @err
(Builder.combineMessages @err (map (Builder.message @err) msgs))
(Builder.lineInfo @err curLine linesBefore linesAfter caret (trimToLine caretWidth))
expectItem :: ExpectItem -> Builder.Item err
expectItem (ExpectRaw t) = Builder.raw @err t
expectItem (ExpectNamed n) = Builder.named @err n
expectItem ExpectEndOfInput = Builder.endOfInput @err
unexpectItem :: Bool -> UnexpectItem -> (Builder.Item err, Span)
unexpectItem lexical (UnexpectRaw cs demanded) =
case Builder.unexpectedToken @err cs demanded lexical of
t@(Token.Raw tok) -> (Builder.raw @err tok, tokenSpan t)
Token.Named name w -> (Builder.named @err name, w)
unexpectItem _ (UnexpectNamed name caretWidth) = (Builder.named @err name, width caretWidth)
unexpectItem _ UnexpectEndOfInput = (Builder.endOfInput @err, 1)
-- it is definitely the case that there are at least `line` lines
(allLinesBefore, curLine, allLinesAfter) = breakLines (line err - 1) (lines input)
linesBefore = drop (length allLinesBefore - Builder.numLinesBefore @err) allLinesBefore
linesAfter = take (Builder.numLinesAfter @err) allLinesAfter
caret = col err - 1
trimToLine width = min width (fromIntegral (length curLine) - caret + 1)
lines :: String -> NonEmpty String
lines [] = "" :| []
lines ('\n':cs) = "" <| lines cs
lines (c:cs) = let l :| ls = lines cs in (c:l) :| ls
breakLines :: Word -> NonEmpty String -> ([String], String, [String])
breakLines 0 (l :| ls) = ([], l, ls)
breakLines n (l :| ls) = case nonEmpty ls of
Nothing -> error "the focus line is guaranteed to exist"
Just ls' -> let (before, focus, after) = breakLines (n - 1) ls'
in (l : before, focus, after)
tokenSpan :: Token -> Word
tokenSpan (Token.Raw cs) = fromIntegral (length cs)
tokenSpan (Token.Named _ w) = w