-- | Given the textual parse input, the parse failure on the input, and the number of lines preceding the failure to
-- show, produce a human-readable failure description.
failureDescription :: forall s pos. (Ord s, TextualMonoid s, Position pos) => s -> ParseFailure pos s -> Int -> s
failureDescription input (ParseFailure pos expected erroneous) contextLineCount =
   Position.context input pos contextLineCount
   <> mconcat
      (intersperse ", but " $ filter (not . null)
       [onNonEmpty ("expected " <>) $ oxfordComma " or " (fromDescription <$> expected),
        oxfordComma " and " (fromDescription <$> erroneous)])
   where oxfordComma :: s -> [s] -> s
         oxfordComma _ [] = ""
         oxfordComma _ [x] = x
         oxfordComma conjunction [x, y] = x <> conjunction <> y
         oxfordComma conjunction (x:y:rest) = mconcat (intersperse ", " (x : y : onLast (drop 1 conjunction <>) rest))
         onNonEmpty f x = if null x then x else f x
         onLast _ [] = []
         onLast f [x] = [f x]
         onLast f (x:xs) = x : onLast f xs
         fromDescription (StaticDescription s) = fromString s
         fromDescription (LiteralDescription s) = "string \"" <> s <> "\""
failureDescription :: forall s pos. (Ord s, TextualMonoid s, Position pos) => s -> ParseFailure pos s -> Int -> s
failureDescription input (ParseFailure pos expected erroneous) contextLineCount =
   Position.context input pos contextLineCount
   <> mconcat
      (intersperse ", but " $ filter (not . null)
       [onNonEmpty ("expected " <>) $ oxfordComma " or " (fromDescription <$> expected),
        oxfordComma " and " (fromDescription <$> erroneous)])
   where oxfordComma :: s -> [s] -> s
         oxfordComma _ [] = ""
         oxfordComma _ [x] = x
         oxfordComma conjunction [x, y] = x <> conjunction <> y
         oxfordComma conjunction (x:y:rest) = mconcat (intersperse ", " (x : y : onLast (drop 1 conjunction <>) rest))
         onNonEmpty f x = if null x then x else f x
         onLast _ [] = []
         onLast f [x] = [f x]
         onLast f (x:xs) = x : onLast f xs
         fromDescription (StaticDescription s) = fromString s
         fromDescription (LiteralDescription s) = "string \"" <> s <> "\""
failureDescription input (ParseFailure pos expected erroneous) contextLineCount =
   Position.context input pos contextLineCount
   <> mconcat
      (intersperse ", but " $ filter (not . null)
       [onNonEmpty ("expected " <>) $ oxfordComma " or " (fromDescription <$> expected),
        oxfordComma " and " (fromDescription <$> erroneous)])
   where oxfordComma :: s -> [s] -> s
         oxfordComma _ [] = ""
         oxfordComma _ [x] = x
         oxfordComma conjunction [x, y] = x <> conjunction <> y
         oxfordComma conjunction (x:y:rest) = mconcat (intersperse ", " (x : y : onLast (drop 1 conjunction <>) rest))
         onNonEmpty f x = if null x then x else f x
         onLast _ [] = []
         onLast f [x] = [f x]
         onLast f (x:xs) = x : onLast f xs
         fromDescription (StaticDescription s) = fromString s
         fromDescription (LiteralDescription s) = "string \"" <> s <> "\""