packages feed

aihc-parser-1.0.0.2: src/Aihc/Parser/Internal/Errors.hs

module Aihc.Parser.Internal.Errors
  ( parseErrorBundleToSpannedText,
    parseErrorsToSpannedText,
  )
where

import Aihc.Parser.Lex (LexToken (..), TokenOrigin (..))
import Aihc.Parser.Syntax (SourceSpan (..))
import Aihc.Parser.Types (FoundToken (..), ParseErrorBundle, ParserErrorComponent (..), TokStream)
import Data.List qualified as List
import Data.List.NonEmpty qualified as NE
import Data.Maybe (fromMaybe)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as T
import Prettyprinter (Doc, defaultLayoutOptions, layoutPretty, pretty, vcat)
import Prettyprinter.Render.Text qualified as RText
import Text.Megaparsec qualified as MP
import Text.Megaparsec.Error (ErrorFancy (..), ErrorItem (..))
import Text.Megaparsec.Error qualified as MPE

parseErrorBundleToSpannedText :: ParseErrorBundle -> [(SourceSpan, Text)]
parseErrorBundleToSpannedText bundle =
  parseErrorsToSpannedText (NE.toList (MPE.bundleErrors bundle))

parseErrorsToSpannedText :: [MPE.ParseError TokStream ParserErrorComponent] -> [(SourceSpan, Text)]
parseErrorsToSpannedText errs =
  [ (fromMaybe NoSourceSpan mSpan, RText.renderStrict (layoutPretty defaultLayoutOptions doc))
  | err <- List.sortOn MP.errorOffset errs,
    (mSpan, doc) <- renderParseErrors err
  ]

renderParseErrors :: MPE.ParseError TokStream ParserErrorComponent -> [(Maybe SourceSpan, Doc ann)]
renderParseErrors err =
  case err of
    MPE.TrivialError _ mUnexpected expected ->
      let mSpan = trivialUnexpectedSpan mUnexpected
       in [(mSpan, vcat (map pretty (renderTrivialError mUnexpected expected)))]
    MPE.FancyError _ fancySet ->
      map renderFancyError (Set.toAscList fancySet)
  where
    trivialUnexpectedSpan :: Maybe (ErrorItem LexToken) -> Maybe SourceSpan
    trivialUnexpectedSpan mItem =
      case mItem of
        Just (Tokens ts) -> Just (lexTokenSpan (NE.head ts))
        _ -> Nothing

    renderFancyError :: ErrorFancy ParserErrorComponent -> (Maybe SourceSpan, Doc ann)
    renderFancyError fancy =
      case fancy of
        ErrorCustom custom ->
          ( customFoundSpan custom,
            vcat (map pretty (customMessageLines custom))
          )
        ErrorFail message -> (Nothing, pretty message)
        _ ->
          ( Nothing,
            pretty (show fancy)
          )

    customFoundSpan :: ParserErrorComponent -> Maybe SourceSpan
    customFoundSpan (UnexpectedTokenExpecting (Just found) _ _) =
      Just (foundTokenSpan found)
    customFoundSpan _ = Nothing

    customMessageLines :: ParserErrorComponent -> [String]
    customMessageLines e@(UnexpectedTokenExpecting mFound _ contexts) =
      [maybe "unexpected end of input" renderUnexpectedToken mFound, MPE.showErrorComponent e]
        <> map (\context -> "context: " <> T.unpack context) contexts

renderTrivialError :: Maybe (ErrorItem LexToken) -> Set.Set (ErrorItem LexToken) -> [String]
renderTrivialError mUnexpected expected =
  maybe [] (\item -> ["unexpected " <> renderErrorItem item]) mUnexpected
    <> ["expecting " <> renderExpectedItems (Set.toAscList expected) | not (Set.null expected)]

renderErrorItem :: ErrorItem LexToken -> String
renderErrorItem item =
  case item of
    Tokens toks -> unwords (map (T.unpack . lexTokenText) (NE.toList toks))
    Label label -> NE.toList label
    EndOfInput -> "end of input"

renderExpectedItems :: [ErrorItem LexToken] -> String
renderExpectedItems items =
  case map renderErrorItem items of
    [] -> ""
    [item] -> item
    [itemA, itemB] -> itemA <> " or " <> itemB
    rendered -> List.intercalate ", " (init rendered) <> ", or " <> last rendered

renderUnexpectedToken :: FoundToken -> String
renderUnexpectedToken found =
  "unexpected " <> tokenDescriptor found

tokenDescriptor :: FoundToken -> String
tokenDescriptor found =
  case foundTokenOrigin found of
    InsertedLayout -> "end of input"
    FromSource ->
      "'" <> T.unpack (foundTokenText found) <> "'"