packages feed

sexp-grammar-1.2.0: src/Data/InvertibleGrammar/Monad.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ViewPatterns     #-}

module Data.InvertibleGrammar.Monad
  ( module Control.Monad.ContextError
  , dive
  , step
  , locate
  , grammarError
  , runGrammarMonad
  , Propagation
  , GrammarError (..)
  , Mismatch (..)
  ) where

import Control.Applicative
import Data.Set (Set)
import qualified Data.Set as S
import Data.List (intercalate)
import Data.Semigroup
import Control.Monad.ContextError

initPropagation :: p -> Propagation p
initPropagation = Propagation [0]

data Propagation p = Propagation
  { pProp :: [Int]
  , pPos  :: p
  } deriving (Show)

instance Eq (Propagation p) where
  Propagation xs _ == Propagation ys _ = xs == ys

instance Ord (Propagation p) where
  compare (Propagation as _) (Propagation bs _) =
    reverse as `compare` reverse bs
  {-# INLINE compare #-}

data Mismatch = Mismatch
  { mismatchExpected :: Set String
  , mismatchGot :: Maybe String
  } deriving (Show, Eq)

runGrammarMonad :: p -> (p -> String) -> ContextError (Propagation p) (GrammarError p) a -> Either String a
runGrammarMonad initPos showPos m =
  case runContextError m (initPropagation initPos) of
    Left (GrammarError p mismatch) ->
      Left $ renderMismatch (showPos (pPos p)) mismatch
    Right a -> Right a

renderMismatch :: String -> Mismatch -> String
renderMismatch pos (Mismatch (S.toList -> expected) got) =
  unlines $
    [ pos ++ ": mismatch:"
    ] ++ case (expected, got) of
           ([], Nothing)    -> [ "unknown error happened" ]
           ([], Just got')  -> [ "unexpected: " ++ got' ]
           (_:_, Nothing)   -> [ "expected: " ++ intercalate ", " expected ]
           (_:_, Just got') -> [ "expected: " ++ intercalate ", " expected
                               , "     got: " ++ got'
                               ]


data GrammarError p = GrammarError (Propagation p) Mismatch
  deriving (Show)

instance Semigroup (GrammarError p) where
  GrammarError pos m <> GrammarError pos' m'
    | pos > pos' = GrammarError pos m
    | pos < pos' = GrammarError pos' m'
    | otherwise  = GrammarError pos $
      Mismatch
        (mismatchExpected m <> mismatchExpected m')
        (mismatchGot m <|> mismatchGot m')

dive :: MonadContextError (Propagation p) e m => m a -> m a
dive =
  localContext $ \(Propagation xs pos) ->
    Propagation (0 : xs) pos
{-# INLINE dive #-}

step :: MonadContextError (Propagation p) e m => m ()
step =
  modifyContext $ \propagation ->
    propagation
      { pProp = case pProp propagation of
          (x : xs) -> succ x : xs
          [] -> [0]
      }
{-# INLINE step #-}

locate :: MonadContextError (Propagation p) e m => p -> m ()
locate pos =
  modifyContext $ \propagation ->
    propagation { pPos = pos }
{-# INLINE locate #-}

grammarError :: MonadContextError (Propagation p) (GrammarError p) m => Mismatch -> m a
grammarError mismatch = throwInContext (\ctx -> GrammarError ctx mismatch)
{-# INLINE grammarError #-}