packages feed

inferno-core-0.1.0.0: src/Inferno/Parse/Error.hs

module Inferno.Parse.Error where

import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NEList
import Data.Maybe (isNothing)
import Data.Proxy (Proxy (..))
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text, pack, replace, unpack)
import Text.Megaparsec
  ( ErrorFancy (..),
    ErrorItem (..),
    ParseError (..),
    ShowErrorComponent (..),
    Stream (Token),
    unPos,
  )
import Text.Megaparsec.Stream (showTokens)

drop_ :: String -> String
drop_ "" = ""
drop_ ('_' : xs) = xs
drop_ xs = xs

orList :: NonEmpty Text -> String
orList (x :| []) = drop_ $ unpack x
orList xs =
  "one of the following:\n∙ "
    <> intercalate "\n∙ " (map (drop_ . unpack . replace "\n" "\n  ") $ NEList.toList xs)

showErrorItem :: ErrorItem (Token Text) -> String
showErrorItem = \case
  Tokens ts -> showTokens (Proxy :: Proxy Text) ts
  Label lbl -> NEList.toList lbl
  EndOfInput -> "end of input"

messageItemsPretty ::
  -- | Prefix to prepend
  String ->
  -- | Collection of messages
  Set String ->
  -- | Result of rendering
  String
messageItemsPretty prefix ts
  | Set.null ts = ""
  | otherwise =
    prefix <> (orList . NEList.fromList . Set.toAscList . Set.map pack) ts <> "\n"

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

prettyError ::
  ShowErrorComponent e =>
  -- | Parse error to render
  ParseError Text e ->
  String
prettyError (TrivialError _ us ps) =
  if isNothing us && Set.null ps
    then "unknown parse error\n"
    else
      messageItemsPretty "unexpected " (showErrorItem `Set.map` maybe Set.empty Set.singleton us)
        <> messageItemsPretty "expecting " (showErrorItem `Set.map` ps)
prettyError (FancyError _ xs) =
  if Set.null xs
    then "unknown fancy parse error\n"
    else unlines (showErrorFancy <$> Set.toAscList xs)