{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Yaml.Parser where
import Control.Applicative
import Control.Exception (Exception)
import Control.Monad (MonadPlus (..), liftM, ap)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Resource (MonadThrow, throwM)
import Control.Monad.Trans.Writer.Strict (tell, WriterT)
import Data.ByteString (ByteString)
import Data.Conduit
import Data.Conduit.Lift (runWriterC)
import qualified Data.Map as Map
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid (..))
#endif
#if !MIN_VERSION_base(4,11,0)
import Data.Semigroup (Semigroup(..))
#endif
import Data.Text (Text, pack, unpack)
import Data.Text.Encoding (decodeUtf8)
import Data.Text.Read (signed, decimal)
import Data.Typeable (Typeable)
import Text.Libyaml
newtype YamlParser a = YamlParser
{ unYamlParser :: AnchorMap -> Either Text a
}
instance Functor YamlParser where
fmap = liftM
instance Applicative YamlParser where
pure = YamlParser . const . Right
(<*>) = ap
instance Alternative YamlParser where
empty = fail "empty"
(<|>) = mplus
instance Semigroup (YamlParser a) where
(<>) = mplus
instance Monoid (YamlParser a) where
mempty = fail "mempty"
#if !MIN_VERSION_base(4,11,0)
mappend = (<>)
#endif
instance Monad YamlParser where
return = pure
YamlParser f >>= g = YamlParser $ \am ->
case f am of
Left t -> Left t
Right x -> unYamlParser (g x) am
#if MIN_VERSION_base(4,13,0)
instance MonadFail YamlParser where
#endif
fail = YamlParser . const . Left . pack
instance MonadPlus YamlParser where
mzero = fail "mzero"
mplus a b = YamlParser $ \am ->
case unYamlParser a am of
Left _ -> unYamlParser b am
x -> x
lookupAnchor :: AnchorName -> YamlParser (Maybe YamlValue)
lookupAnchor name = YamlParser $ Right . Map.lookup name
withAnchor :: AnchorName -> Text -> (YamlValue -> YamlParser a) -> YamlParser a
withAnchor name expected f = do
mv <- lookupAnchor name
case mv of
Nothing -> fail $ unpack expected ++ ": unknown alias " ++ name
Just v -> f v
withMapping :: Text -> ([(Text, YamlValue)] -> YamlParser a) -> YamlValue -> YamlParser a
withMapping _ f (Mapping m _) = f m
withMapping expected f (Alias an) = withAnchor an expected $ withMapping expected f
withMapping expected _ v = typeMismatch expected v
withSequence :: Text -> ([YamlValue] -> YamlParser a) -> YamlValue -> YamlParser a
withSequence _ f (Sequence s _) = f s
withSequence expected f (Alias an) = withAnchor an expected $ withSequence expected f
withSequence expected _ v = typeMismatch expected v
withText :: Text -> (Text -> YamlParser a) -> YamlValue -> YamlParser a
withText _ f (Scalar s _ _ _) = f $ decodeUtf8 s
withText expected f (Alias an) = withAnchor an expected $ withText expected f
withText expected _ v = typeMismatch expected v
typeMismatch :: Text -> YamlValue -> YamlParser a
typeMismatch expected v =
fail $ concat
[ "Expected "
, unpack expected
, ", but got: "
, t
]
where
t = case v of
Mapping _ _ -> "mapping"
Sequence _ _ -> "sequence"
Scalar _ _ _ _ -> "scalar"
Alias _ -> "alias"
class FromYaml a where
fromYaml :: YamlValue -> YamlParser a
instance FromYaml YamlValue where
fromYaml = return
instance FromYaml a => FromYaml [a] where
fromYaml = withSequence "[a]" (mapM fromYaml)
instance FromYaml Text where
fromYaml = withText "Text" return
instance FromYaml Int where
fromYaml =
withText "Int" go
where
go t =
case signed decimal t of
Right (i, "") -> return i
_ -> fail $ "Invalid Int: " ++ unpack t
data YamlValue
= Mapping [(Text, YamlValue)] Anchor
| Sequence [YamlValue] Anchor
| Scalar ByteString Tag Style Anchor
| Alias AnchorName
deriving Show
type AnchorMap = Map.Map AnchorName YamlValue
data RawDoc = RawDoc YamlValue AnchorMap
deriving Show
parseRawDoc :: (FromYaml a, MonadThrow m) => RawDoc -> m a
parseRawDoc (RawDoc val am) =
case unYamlParser (fromYaml val) am of
Left t -> throwM $ FromYamlException t
Right x -> return x
(.:) :: FromYaml a => [(Text, YamlValue)] -> Text -> YamlParser a
o .: k =
case lookup k o of
Nothing -> fail $ "Key not found: " ++ unpack k
Just v -> fromYaml v
data YamlParseException
= UnexpectedEndOfEvents
| UnexpectedEvent Event
| FromYamlException Text
deriving (Show, Typeable)
instance Exception YamlParseException
sinkValue :: MonadThrow m => ConduitM Event o (WriterT AnchorMap m) YamlValue
sinkValue =
start
where
start = await >>= maybe (throwM UnexpectedEndOfEvents) go
tell' Nothing val = return val
tell' (Just name) val = do
lift $ tell $ Map.singleton name val
return val
go EventStreamStart = start
go EventDocumentStart = start
go (EventAlias a) = return $ Alias a
go (EventScalar a b c d) = tell' d $ Scalar a b c d
go (EventSequenceStart _tag _style mname) = do
vals <- goS id
let val = Sequence vals mname
tell' mname val
go (EventMappingStart _tag _style mname) = do
pairs <- goM id
let val = Mapping pairs mname
tell' mname val
go e = throwM $ UnexpectedEvent e
goS front = do
me <- await
case me of
Nothing -> throwM UnexpectedEndOfEvents
Just EventSequenceEnd -> return $ front []
Just e -> do
val <- go e
goS (front . (val:))
goM front = do
mk <- await
case mk of
Nothing -> throwM UnexpectedEndOfEvents
Just EventMappingEnd -> return $ front []
Just (EventScalar a b c d) -> do
_ <- tell' d $ Scalar a b c d
let k = decodeUtf8 a
v <- start
goM (front . ((k, v):))
Just e -> throwM $ UnexpectedEvent e
sinkRawDoc :: MonadThrow m => ConduitM Event o m RawDoc
sinkRawDoc = uncurry RawDoc <$> runWriterC sinkValue
readYamlFile :: FromYaml a => FilePath -> IO a
readYamlFile fp = runConduitRes (decodeFile fp .| sinkRawDoc) >>= parseRawDoc