packages feed

bookhound-0.2.1: src/Bookhound/Parser.hs

module Bookhound.Parser (Parser(parse), ParseResult(..), ParseError(..), mkParser, runParser, throwError, andThen, exactly, eof , lookAhead , notFollowedBy, both, choice, anyOf, allOf, anyChar, satisfy, withTransform, withError, withErrorN, except) where

import           Bookhound.Utils.Foldable  (findJust)
import           Control.Applicative       (Alternative (..), liftA2)
import           Control.Monad             (MonadPlus)
import           Control.Monad.Error.Class (MonadError (..))
import           Data.Either               (fromRight)
import           Data.Foldable             (foldl')
import           Data.Set                  (Set)
import qualified Data.Set                  as Set
import           Data.Text                 (Text, unpack)
import qualified Data.Text                 as Text

data Parser a
  = P
      { parse     :: Input -> ParseResult a
      , transform :: forall b. Maybe (Parser b -> Parser b)
      , errors    :: Set (Int, ParseError)
      }

instance Functor Parser where
  fmap f (P p t e) = applyTransformError t e $
    mkParser (fmap f . p)

instance Applicative Parser where
  pure a      = mkParser (`Result` a)
  liftA2 f (P p t e) mb@(P _ t' e') =
    applyTransformsErrors [t, t'] [e, e'] $ mkParser \x ->
      case p x of
        Result i a -> parse (f a <$> mb) i
        Error pe   -> Error pe

instance Monad Parser where
  (>>=) (P p t e) f =
    applyTransformError t e $ mkParser \x ->
      case p x of
        Result i a -> parse (f a) i
        Error pe   -> Error pe

instance Semigroup a => Semigroup (Parser a) where
  (<>) = liftA2 (<>)

instance Monoid a => Monoid (Parser a) where
  mempty = pure mempty

instance Alternative Parser where
  (<|>) (P p t e) ~(P p' t' e') =
    applyTransformsErrors [ t, t' ] [ e, e' ] $
      mkParser
        \x -> case p x of
          Error _ -> p' x
          result  -> result
  empty = mkParser \i ->
    if Text.null i then
      Error UnexpectedEof
    else
      Error $ ExpectedEof i

instance MonadPlus Parser

instance MonadError ParseError Parser where
  throwError = mkParser . const . Error
  catchError p errFn = mkParser
        \x -> case parse p x of
          Error err -> parse (errFn err) x
          result    -> result

anyChar :: Parser Char
anyChar = mkParser $
   maybe (Error UnexpectedEof) (uncurry $ flip Result) . Text.uncons

runParser :: Parser a -> Input -> Either [ParseError] a
runParser p@(P _ _ e) i = toEither $ parse (exactly p) i
  where
    toEither = \case
      Result _ a -> Right a
      Error pe   -> Left $ filter hasPriorityError [pe]
        <> (snd <$> reverse (Set.toList e))
        <> filter (not . hasPriorityError) [pe]

andThen :: Parser Text -> Parser a -> Parser a
andThen p1 p2@(P _ t e) = applyTransformError t e $
  mkParser (\i -> parse p2 $ fromRight i (runParser p1 i))

exactly :: Parser a -> Parser a
exactly p = p <* eof

eof :: Parser ()
eof = mkParser \i ->
    if i == mempty then
      Result i ()
    else
      Error $ ExpectedEof i

lookAhead :: Parser a -> Parser a
lookAhead (P p t e) =
  applyTransformError t e $
    mkParser
      \x -> case p x of
        Result _ a -> Result x a
        err        -> err

notFollowedBy :: Parser a -> Parser ()
notFollowedBy (P p t e) =
  applyTransformError t e $
    mkParser
      \x -> case p x of
        Result _ _ -> Error UnexpectedEof
        _          -> Result x ()

choice :: Foldable f => f (Parser a) -> Parser a
choice = anyOf

anyOf :: Foldable f => f (Parser a) -> Parser a
anyOf = foldl' (<|>) empty

allOf :: Foldable f => f (Parser a) -> Parser a
allOf = foldl' both (pure undefined)

both :: Parser a -> Parser a -> Parser a
both (P p t e) (P p' t' e') =
  applyTransformsErrors [ t, t' ] [ e, e' ] $
    mkParser
      \x -> case p x of
        Result _ _ -> p' x
        err        -> err

except :: Parser a -> Parser a -> Parser a
except (P p t e) (P p' _ _) = applyTransformError t e $ mkParser (
  \x -> case p' x of
    Result _ _ -> Error $ ExpectedEof x
    Error _    -> p x
  )

satisfy :: (a -> Bool) -> Parser a -> Parser a
satisfy cond ma = do
  c2 <- ma
  if cond c2 then
    pure c2
  else
    empty

withError :: Text -> Parser a -> Parser a
withError = withErrorN 0

withErrorN :: Int -> Text -> Parser a -> Parser a
withErrorN n str = applyError . Set.singleton $ (n, ErrorAt str)


withTransform :: (forall b. Parser b -> Parser b) -> Parser a -> Parser a
withTransform t = applyTransform $ Just t




applyTransformsErrors :: (forall b. [Maybe (Parser b -> Parser b)])
                      -> [Set (Int, ParseError)]
                      -> Parser a
                      -> Parser a
applyTransformsErrors ts es =
  applyTransformError (findJust ts) (mconcat es)


applyTransformError :: (forall b. Maybe (Parser b -> Parser b))
                    -> Set (Int, ParseError)
                    -> Parser a
                    -> Parser a
applyTransformError t e = applyTransform t . applyError e



applyTransform :: (forall b. Maybe (Parser b -> Parser b)) -> Parser a -> Parser a
applyTransform f p =  maybe p (\f' -> (f' p) {transform = f}) f

applyError :: Set (Int, ParseError) -> Parser a -> Parser a
applyError e p@(P _ _ e') = p {errors = e <> e'}

mkParser :: (Input -> ParseResult a) -> Parser a
mkParser p = P {parse = p, transform = Nothing, errors = Set.empty}


hasPriorityError :: ParseError -> Bool
hasPriorityError (ErrorAt _) = True
hasPriorityError _           = False

data ParseResult a
  = Result Input a
  | Error ParseError
  deriving (Eq)

instance Functor ParseResult where
  fmap f (Result i a) = Result i (f a)
  fmap _ (Error pe)   = Error pe


instance Show a => Show (ParseResult a) where
  show (Result i a) = "Pending: " <> " >" <> unpack i <> "< "
                                  <> "\n\nResult: \n" <> show a
  show (Error err)  = show err

data ParseError
  = UnexpectedEof
  | ExpectedEof Input
  | ErrorAt Text
  deriving (Eq, Ord)

instance Show ParseError where
  show UnexpectedEof        = "Unexpected end of stream"
  show (ExpectedEof i)      = "Expected end of stream, but got "
                               <> ">" <> unpack i <> "<"
  show (ErrorAt s)          = "Error at " <> unpack s

type Input = Text