{-# LANGUAGE ConstraintKinds, FlexibleContexts, MultiParamTypeClasses #-}
module Idris.Parser.Stack
(
Parser(..)
, Parsing(..)
, runparser
, ParseError
, prettyError
, Mark
, mark
, restore
, getFC
, addExtent
, trackExtent
, extent
, withExtent
, appExtent
)
where
import Idris.Core.TT (FC(..))
import Idris.Output (Message(..))
import Control.Arrow (app)
import Control.Monad.State.Strict (StateT(..), evalStateT)
import Control.Monad.Writer.Strict (MonadWriter(..), WriterT(..), listen,
runWriterT, tell)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Void (Void(..))
import System.FilePath (addTrailingPathSeparator, splitFileName)
import qualified Text.Megaparsec as P
import qualified Util.Pretty as PP
type Parser s = StateT s (WriterT FC (P.Parsec Void String))
type Parsing m = (P.MonadParsec Void String m, MonadWriter FC m)
runparser :: Parser st res -> st -> String -> String -> Either ParseError res
runparser p i inputname s =
case P.parse (runWriterT (evalStateT p i)) inputname s of
Left err -> Left $ ParseError err
Right v -> Right $ fst v
newtype ParseError = ParseError { unParseError :: P.ParseErrorBundle String Void }
parseError :: ParseError -> P.ParseError String Void
parseError = NonEmpty.head . P.bundleErrors . unParseError
parseErrorPosState :: ParseError -> P.PosState String
parseErrorPosState = P.bundlePosState . unParseError
parseErrorOffset :: ParseError -> Int
parseErrorOffset = P.errorOffset . parseError
instance Message ParseError where
messageExtent err = sourcePositionFC pos
where
(pos, _) = P.reachOffsetNoLine (parseErrorOffset err) (parseErrorPosState err)
messageText = PP.text . init . P.parseErrorTextPretty . parseError
messageSource err = Just sline
where
(_, sline, _) = P.reachOffset (parseErrorOffset err) (parseErrorPosState err)
prettyError :: ParseError -> String
prettyError = P.errorBundlePretty . unParseError
type Mark = P.State String
mark :: Parsing m => m Mark
mark = P.getParserState
restore :: Parsing m => Mark -> m ()
restore = P.setParserState
sourcePositionFC :: P.SourcePos -> FC
sourcePositionFC (P.SourcePos name line column) =
FC f (lineNumber, columnNumber) (lineNumber, columnNumber)
where
lineNumber = P.unPos line
columnNumber = P.unPos column
(dir, file) = splitFileName name
f = if dir == addTrailingPathSeparator "."
then file
else name
getFC :: Parsing m => m FC
getFC = sourcePositionFC <$> P.getSourcePos
addExtent :: MonadWriter FC m => FC -> m ()
addExtent = tell
trackExtent :: Parsing m => m a -> m a
trackExtent p = do ~(FC f (sr, sc) _) <- getFC
result <- p
~(FC f _ (er, ec)) <- getFC
addExtent (FC f (sr, sc) (er, max 1 (ec - 1)))
return result
extent :: MonadWriter FC m => m a -> m FC
extent = fmap snd . withExtent
withExtent :: MonadWriter FC m => m a -> m (a, FC)
withExtent = listen
appExtent :: MonadWriter FC m => m (FC -> a) -> m a
appExtent = fmap app . withExtent