packages feed

ideas-1.9.1: src/Ideas/Utils/Decoding.hs

{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleInstances, MultiParamTypeClasses #-}
-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------

module Ideas.Utils.Decoding
   ( Decoder, evalDecoder, runDecoder, mapError, getLoc, putLoc, changeLoc
   , Encoder, runEncoder
   , Error, ErrorType, Loc(..), nextLoc, raiseError, errorStr
     -- re-exports
   , Alternative(..), optional, MonadReader(..), MonadState(..), MonadError(..)
   , gets
   ) where

import Control.Arrow
import Control.Applicative
import Control.Monad
import Control.Monad.Except hiding (mapError)
import Control.Monad.Reader
import Control.Monad.State
import Data.List
import Data.String

-------------------------------------------------------------------

newtype Decoder env err s a = Dec { fromDec :: StateT (Loc, s) (ReaderT env (Except err)) a }
 deriving (Functor, Applicative, Alternative, MonadPlus, MonadReader env, MonadError err)

instance Semigroup a => Semigroup (Decoder env err s a) where
   (<>) = liftA2 (<>)

instance Monoid a => Monoid (Decoder env err s a) where
   mempty  = pure mempty

instance Monad (Decoder env err s) where
   Dec m >>= f = Dec $ m >>= fromDec . f

instance MonadState s (Decoder env err s) where
   state f = Dec $ state $ \(loc, s) -> let (a, s') = f s in (a, (loc, s'))

runDecoder :: Decoder env err s a -> env -> s -> Either err (a, s)
runDecoder p env s = fmap snd <$> runExcept (runReaderT (runStateT (fromDec p) (Root 0, s)) env)

evalDecoder :: Decoder env err s a -> env -> s -> Either err a
evalDecoder p env = fmap fst . runDecoder p env

mapError ::  (err1 -> err2) -> Decoder env err1 s a -> Decoder env err2 s a
mapError f p = do
   env <- reader id
   s1  <- get
   case runDecoder p env s1 of
      Left e1 -> throwError (f e1)
      Right (a, s2) -> put s2 >> return a

getLoc :: Decoder env err s Loc
getLoc = Dec $ gets fst

putLoc :: Loc -> Decoder env err s ()
putLoc = changeLoc . const 

changeLoc :: (Loc -> Loc) -> Decoder env err s ()
changeLoc = Dec . modify . first

-------------------------------------------------------------------

type Encoder env err = Decoder env err ()

runEncoder :: Encoder env err a -> env -> Either err a
runEncoder p env = evalDecoder p env ()

--------------------------------------------------------------------------------
-- Errors

newtype Error a = E [Either String (ErrorType, Loc, Maybe a)]

type ErrorType = String 

instance Show a => Show (Error a) where
   show (E xs)
      | null xs   = "Parse error"
      | otherwise = unlines (map (either id f) xs)
    where
      
      f (tp, loc, ma) = unlines
         [ "Parse error: " ++ tp
         , "  * Location: " ++ show loc
         , "  * Found: " ++ maybe "" show ma
         ]

instance IsString (Error a) where
   fromString s = E [Left s]

instance Semigroup (Error a) where
   E xs <> E ys = E (xs <> ys)

instance Monoid (Error a) where
   mempty = E []

data Loc = Root Int | LocByPos Int Loc | LocByKey Int String Loc

instance Show Loc where
   show loc
      | null parts = "root"
      | otherwise  = intercalate "." parts
    where
      parts = collect loc

      collect (Root n)         = [ "root+" ++ show n | n > 0 ]
      collect (LocByPos n l)   = collect l ++ [show n]
      collect (LocByKey n k l) = collect l ++ [k ++ if n==0 then "" else "+" ++ show n]

nextLoc :: Loc -> Loc
nextLoc (Root n)         = Root (n+1)
nextLoc (LocByPos n l)   = LocByPos (n+1) l
nextLoc (LocByKey n k l) = LocByKey (n+1) k l

errorStr :: IsString err => String -> Decoder env err s a
errorStr = throwError . fromString

raiseError :: ErrorType -> Maybe a -> Decoder env (Error a) s b
raiseError tp a = do
   loc <- getLoc
   throwError $ E [Right (tp, loc, a)]