gigaparsec-0.2.0.0: src/Text/Gigaparsec/Internal/Errors.hs
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE RecordWildCards, BangPatterns, NamedFieldPuns, CPP #-}
#include "portable-unlifted.h"
{-# OPTIONS_GHC -Wno-partial-fields -Wno-all-missed-specialisations #-}
{-# OPTIONS_HADDOCK hide #-}
{-# OPTIONS_GHC -Wno-missing-import-lists #-}
module Text.Gigaparsec.Internal.Errors (module Text.Gigaparsec.Internal.Errors) where
import Prelude hiding (lines)
import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty, (<|))
import Data.Set (Set)
import Data.Set qualified as Set (empty, map, union, null, foldr, insert)
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(..))
CPP_import_PortableUnlifted
type Span :: *
type Span = Word
type CaretWidth :: UnliftedDatatype
data CaretWidth = FlexibleCaret { width :: {-# UNPACK #-} !Span }
| RigidCaret { width :: {-# UNPACK #-} !Span }
isFlexible :: CaretWidth -> Bool
isFlexible FlexibleCaret{} = True
isFlexible _ = False
type ParseError :: UnliftedDatatype
data ParseError = VanillaError { presentationOffset :: {-# UNPACK #-} !Word
, line :: {-# UNPACK #-} !Word
, col :: {-# UNPACK #-} !Word
, unexpected :: !(Either Word UnexpectItem) -- TODO: unlift this!
-- sadly, this prevents unlifting of ExpectItem
-- perhaps we should make an unlifted+levity polymorphic Set?
, expecteds :: !(Set ExpectItem)
, reasons :: !(Set String)
, lexicalError :: !Bool -- TODO: strict bools
-- TODO: remove:
, underlyingOffset :: {-# UNPACK #-} !Word
, entrenchment :: {-# UNPACK #-} !Word
}
| SpecialisedError { presentationOffset :: {-# UNPACK #-} !Word
, line :: {-# UNPACK #-} !Word
, col :: {-# UNPACK #-} !Word
, msgs :: ![String]
--, caretWidth :: {-# UNPACK #-} !Span --FIXME: need defunc before this goes away
, caretWidth :: CaretWidth
-- TODO: remove:
, underlyingOffset :: {-# UNPACK #-} !Word
, entrenchment :: {-# UNPACK #-} !Word
}
type Input :: *
type Input = NonEmpty Char
type UnexpectItem :: *
data UnexpectItem = UnexpectRaw !Input {-# UNPACK #-} !Word
| UnexpectNamed !String CaretWidth
| UnexpectEndOfInput
type ExpectItem :: *
data ExpectItem = ExpectRaw !String
| ExpectNamed !String
| ExpectEndOfInput
deriving stock (Eq, Ord, Show)
entrenched :: ParseError -> Bool
entrenched err = entrenchment err /= 0
emptyErr :: Word -> Word -> Word -> Word -> ParseError
emptyErr !presentationOffset !line !col !width = VanillaError {
presentationOffset = presentationOffset,
line = line,
col = col,
unexpected = Left width,
expecteds = Set.empty,
reasons = Set.empty,
lexicalError = False,
underlyingOffset = presentationOffset,
entrenchment = 0
}
expectedErr :: String -> Word -> Word -> Word -> Set ExpectItem -> Word -> ParseError
expectedErr !input !presentationOffset !line !col !expecteds !width = VanillaError {
presentationOffset = presentationOffset,
line = line,
col = col,
unexpected = case nonEmpty input of
Nothing -> Right UnexpectEndOfInput
Just cs -> Right (UnexpectRaw cs width),
expecteds = expecteds,
reasons = Set.empty,
lexicalError = False,
underlyingOffset = presentationOffset,
entrenchment = 0
}
specialisedErr :: Word -> Word -> Word -> [String] -> CaretWidth -> ParseError
specialisedErr !presentationOffset !line !col !msgs caretWidth = SpecialisedError {..}
where !underlyingOffset = presentationOffset
!entrenchment = 0 :: Word
unexpectedErr :: Word -> Word -> Word -> Set ExpectItem -> String -> CaretWidth -> ParseError
unexpectedErr !presentationOffset !line !col !expecteds !name caretWidth = VanillaError {
presentationOffset = presentationOffset,
line = line,
col = col,
expecteds = expecteds,
unexpected = Right (UnexpectNamed name caretWidth),
reasons = Set.empty,
lexicalError = False,
underlyingOffset = presentationOffset,
entrenchment = 0
}
labelErr :: Word -> Set String -> ParseError -> ParseError
labelErr !offset expecteds err@VanillaError{}
| offset == presentationOffset err = err { expecteds = Set.map ExpectNamed expecteds }
labelErr _ _ err = err
explainErr :: Word -> String -> ParseError -> ParseError
explainErr !offset reason err@VanillaError{}
| offset == presentationOffset err = err { reasons = Set.insert reason (reasons err) }
explainErr _ _ err = err
amendErr :: Word -> Word -> Word -> ParseError -> ParseError
amendErr !offset !line !col err
| not (entrenched err) = err {
presentationOffset = offset,
underlyingOffset = offset,
line = line,
col = col
}
amendErr _ _ _ err = err
partialAmendErr :: Word -> Word -> Word -> ParseError -> ParseError
partialAmendErr !offset !line !col err
| not (entrenched err) = err {
presentationOffset = offset,
line = line,
col = col
}
partialAmendErr _ _ _ err = err
entrenchErr :: ParseError -> ParseError
entrenchErr err = err { entrenchment = entrenchment err + 1 }
dislodgeErr :: Word -> ParseError -> ParseError
dislodgeErr by err
| entrenchment err == 0 = err
-- this case is important to avoid underflow on the unsigned Word
| by >= entrenchment err = err { entrenchment = 0 }
| otherwise = err { entrenchment = entrenchment err - by }
setLexical :: ParseError -> ParseError
setLexical err@VanillaError{} = err { lexicalError = True }
setLexical err = err
useHints :: Set ExpectItem -> ParseError -> ParseError
useHints !hints err@VanillaError{expecteds} = err { expecteds = Set.union hints expecteds }
useHints _ err = err
mergeErr :: ParseError -> ParseError -> ParseError
mergeErr err1 err2
| underlyingOffset err1 > underlyingOffset err2 = err1
| underlyingOffset err1 < underlyingOffset err2 = err2
| presentationOffset err1 > presentationOffset err2 = err1
| presentationOffset err1 < presentationOffset err2 = err2
-- offsets are all equal, kinds must match
mergeErr err1@SpecialisedError{caretWidth} _err2@VanillaError{}
| isFlexible caretWidth = err1 -- TODO: flexible caret merging from err2
| otherwise = err1
mergeErr _err1@VanillaError{} err2@SpecialisedError{caretWidth}
| isFlexible caretWidth = err2 -- TODO: flexible caret merging from err1
| otherwise = err2
mergeErr err1@VanillaError{} err2@VanillaError{} =
err1 { unexpected = mergeUnexpect (unexpected err1) (unexpected err2)
, expecteds = Set.union (expecteds err1) (expecteds err2)
, reasons = Set.union (reasons err1) (reasons err2)
, lexicalError = lexicalError err1 || lexicalError err2
}
mergeErr err1@SpecialisedError{} err2@SpecialisedError{} =
err1 { msgs = msgs err1 ++ msgs err2
, caretWidth = mergeCaret (caretWidth err1) (caretWidth err2)
}
mergeCaret :: CaretWidth -> CaretWidth -> CaretWidth
mergeCaret caret@RigidCaret{} FlexibleCaret{} = caret
mergeCaret FlexibleCaret{} caret@RigidCaret{} = caret
mergeCaret caret1 caret2 = caret1 { width = max (width caret1) (width caret2) }
mergeUnexpect :: Either Word UnexpectItem -> Either Word UnexpectItem -> Either Word UnexpectItem
mergeUnexpect (Left w1) (Left w2) = Left (max w1 w2)
-- TODO: widening can occur with flexible or raw tokens
mergeUnexpect Left{} w@Right{} = w
mergeUnexpect w@Right{} Left{} = w
-- finally, two others will merge independently
mergeUnexpect (Right item1) (Right item2) = Right (mergeItem item1 item2)
where mergeItem UnexpectEndOfInput _ = UnexpectEndOfInput
mergeItem _ UnexpectEndOfInput = UnexpectEndOfInput
mergeItem it1@(UnexpectNamed _ cw1) it2@(UnexpectNamed _ cw2)
| isFlexible cw1, not (isFlexible cw2) = it2
| not (isFlexible cw1), isFlexible cw2 = it1
| width cw1 < width cw2 = it2
| otherwise = it1
mergeItem item@UnexpectNamed{} _ = item
mergeItem _ item@UnexpectNamed{} = item
mergeItem (UnexpectRaw cs w1) (UnexpectRaw _ w2) = UnexpectRaw cs (max w1 w2)
isExpectedEmpty :: ParseError -> Bool
isExpectedEmpty VanillaError{expecteds} = Set.null expecteds
isExpectedEmpty _ = True
{-# 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 (width 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