ical-0.0.0: src/ICal/Parser.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
-- | The basic parser.
module ICal.Parser
(-- * Types
Parser
,ParseError(..)
-- * Handy starter functions
,parseEither
-- * Combinators
,begin
,object
,objects
,property
,properties
-- * Parser library
,local
,parseError
,getState
,putState)
where
import Control.Applicative
import Control.Monad.State.Strict
import Control.Monad.Trans.Either
import Data.List
import Data.Maybe
import Data.Text (Text)
import ICal.Types
-- | A parse error.
data ParseError
= ExpectedObject !Text
| ExpectedProperty !Text
| GeneralProblem !Text
deriving (Show)
-- | Parse some iCalendar thing.
parseEither
:: Monad m
=> s -> Parser m s a -> m (Either ParseError a)
parseEither s p =
evalStateT (runEitherT (runParser p))
s
-- | Parser type.
newtype Parser m s a =
Parser {runParser :: EitherT ParseError (StateT s m) a}
deriving (Monad,Applicative,Functor)
-- | Left branch failing resets the state.
instance Monad m => Alternative (Parser m s) where
Parser x <|> Parser y =
Parser (do s <- get
r <-
EitherT (do r <- runEitherT x
return (Right r))
case r of
Left{} -> do put s
y
Right ok -> return ok)
empty = parseError (GeneralProblem "empty parser")
-- | Lookup a property.
property :: Monad m
=> Text -- ^ Key
-> Parser m [Object] Text -- ^ The value of that property.
property !key =
do os <- getState
case listToMaybe
(mapMaybe (\case
Property name value
| name == key -> Just value
_ -> Nothing)
os) of
Nothing -> parseError (ExpectedProperty key)
Just x -> return x
-- | Get all values of a property.
properties :: Monad m
=> Text -- ^ Key
-> Parser m [Object] [Text] -- ^ The values of that property.
properties !key =
do os <- getState
return (mapMaybe (\case
Property name value
| name == key -> Just value
_ -> Nothing)
os)
-- | Lookup an object with this name in the current object's children,
-- then run with that object as the context.
object :: Monad m => Text -> Parser m [Object] a -> Parser m [Object] a
object !name m =
do os <- getState
case find (\case
Object name' _ -> name' == name
_ -> False) os of
Just (Object _ children) ->
local children m
_ -> parseError (ExpectedObject name)
-- | Lookup objects with this name in the current object's children,
-- then run with that object as the context.
objects :: Monad m => Text -> Parser m [Object] a -> Parser m [Object] [a]
objects !name m =
do os <- getState
case mapMaybe (\case
Object name' children
| name' == name -> Just children
_ -> Nothing)
os of
cs -> mapM (\children -> local children m) cs
-- | Require the given object name to exist and run in that context.
begin :: Monad m => Text -> Parser m [Object] a -> Parser m Object a
begin !name m =
do o <- getState
case o of
Object name' children ->
if name' == name
then local children m
else parseError (ExpectedObject name)
_ -> parseError (ExpectedObject name)
-- | Use a local state of a different type.
local :: Monad m
=> t -> Parser m t a -> Parser m s a
local temp m =
Parser (EitherT (StateT (\orig ->
do (result,_new) <-
runStateT (runEitherT (runParser m)) temp
return (result,orig))))
-- | Throw a parse error.
parseError :: Monad m => ParseError -> Parser m o a
parseError = Parser . left
-- | Get the current state.
getState :: Monad m => Parser m s s
getState = Parser get
-- | Put a new state.
putState :: Monad m => s -> Parser m s ()
putState = Parser . put