HsYAML-0.1.1.2: src/Data/YAML/Loader.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Trustworthy #-}
-- |
-- Copyright: © Herbert Valerio Riedel 2015-2018
-- SPDX-License-Identifier: GPL-2.0-or-later
--
module Data.YAML.Loader
( decodeLoader
, Loader(..)
, NodeId
) where
import Control.Monad.Except
import Control.Monad.State
import qualified Data.ByteString.Lazy as BS.L
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.YAML.Event (Tag)
import qualified Data.YAML.Event as YE
import Util
-- | Unique identifier for identifying nodes
--
-- This is allows to observe the alias/anchor-reference structure
type NodeId = Word
-- | Structure defining how to construct a document tree/graph
--
data Loader m n = Loader
{ yScalar :: Tag -> YE.Style -> Text -> m (Either String n)
, ySequence :: Tag -> [n] -> m (Either String n)
, yMapping :: Tag -> [(n,n)] -> m (Either String n)
, yAlias :: NodeId -> Bool -> n -> m (Either String n)
, yAnchor :: NodeId -> n -> m (Either String n)
}
-- | Generalised document tree/graph construction
--
-- This doesn't yet perform any tag resolution (thus all scalars are
-- represented as 'Text' values). See also 'decodeNode' for a more
-- convenient interface.
{-# INLINEABLE decodeLoader #-}
decodeLoader :: forall n m . MonadFix m => Loader m n -> BS.L.ByteString -> m (Either String [n])
decodeLoader Loader{..} bs0 = do
case sequence . YE.parseEvents $ bs0 of
Left (pos,err)
| YE.posCharOffset pos < 0 -> return (Left err)
| otherwise -> return (Left $ ":" ++ show (YE.posLine pos) ++ ":" ++ show (YE.posColumn pos) ++ ": " ++ err)
Right evs -> runParserT goStream evs
where
goStream :: PT n m [n]
goStream = do
_ <- satisfy (== YE.StreamStart)
ds <- manyUnless (== YE.StreamEnd) goDoc
eof
return ds
goDoc :: PT n m n
goDoc = do
_ <- satisfy isDocStart
modify $ \s0 -> s0 { sDict = mempty, sCycle = mempty }
n <- goNode
_ <- satisfy isDocEnd
return n
getNewNid :: PT n m Word
getNewNid = state $ \s0 -> let i0 = sIdCnt s0
in (i0, s0 { sIdCnt = i0+1 })
returnNode :: (Maybe YE.Anchor) -> Either String n -> PT n m n
returnNode _ (Left err) = throwError err
returnNode Nothing (Right node) = return node
returnNode (Just a) (Right node) = do
nid <- getNewNid
node0 <- lift $ yAnchor nid node
node' <- liftEither node0
modify $ \s0 -> s0 { sDict = Map.insert a (nid,node') (sDict s0) }
return node'
registerAnchor :: Maybe YE.Anchor -> PT n m n -> PT n m n
registerAnchor Nothing pn = pn
registerAnchor (Just a) pn = do
modify $ \s0 -> s0 { sCycle = Set.insert a (sCycle s0) }
nid <- getNewNid
mdo
modify $ \s0 -> s0 { sDict = Map.insert a (nid,n) (sDict s0) }
n0 <- pn
n1 <- lift $ yAnchor nid n0
n <- liftEither n1
return n
exitAnchor :: Maybe YE.Anchor -> PT n m ()
exitAnchor Nothing = return ()
exitAnchor (Just a) = modify $ \s0 -> s0 { sCycle = Set.delete a (sCycle s0) }
goNode :: PT n m n
goNode = do
n <- satisfy (const True)
case n of
YE.Scalar manc tag sty val -> do
exitAnchor manc
n' <- lift $ yScalar tag sty val
returnNode manc $! n'
YE.SequenceStart manc tag -> registerAnchor manc $ do
ns <- manyUnless (== YE.SequenceEnd) goNode
exitAnchor manc
liftEither =<< (lift $ ySequence tag ns)
YE.MappingStart manc tag -> registerAnchor manc $ do
kvs <- manyUnless (== YE.MappingEnd) (liftM2 (,) goNode goNode)
exitAnchor manc
liftEither =<< (lift $ yMapping tag kvs)
YE.Alias a -> do
d <- gets sDict
cy <- gets sCycle
case Map.lookup a d of
Nothing -> throwError ("anchor not found: " ++ show a)
Just (nid,n') -> liftEither =<< (lift $ yAlias nid (Set.member a cy) n')
_ -> throwError "goNode: unexpected event"
----------------------------------------------------------------------------
-- small parser framework
data S n = S { sEvs :: [YE.Event]
, sDict :: Map YE.Anchor (Word,n)
, sCycle :: Set YE.Anchor
, sIdCnt :: !Word
}
newtype PT n m a = PT (StateT (S n) (ExceptT String m) a)
deriving ( Functor
, Applicative
, Monad
, MonadState (S n)
, MonadError String
, MonadFix
)
instance MonadTrans (PT n) where
lift = PT . lift . lift
runParserT :: Monad m => PT n m a -> [YE.Event] -> m (Either String a)
runParserT (PT act) s0 = runExceptT $ evalStateT act (S s0 mempty mempty 0)
satisfy :: Monad m => (YE.Event -> Bool) -> PT n m YE.Event
satisfy p = do
s0 <- get
case sEvs s0 of
[] -> throwError "satisfy: premature eof"
(ev:rest)
| p ev -> do put (s0 { sEvs = rest})
return ev
| otherwise -> throwError ("satisfy: predicate failed " ++ show ev)
peek :: Monad m => PT n m (Maybe YE.Event)
peek = do
s0 <- get
case sEvs s0 of
[] -> return Nothing
(ev:_) -> return (Just ev)
peek1 :: Monad m => PT n m YE.Event
peek1 = maybe (throwError "peek1: premature eof") return =<< peek
anyEv :: Monad m => PT n m YE.Event
anyEv = satisfy (const True)
eof :: Monad m => PT n m ()
eof = do
s0 <- get
case sEvs s0 of
[] -> return ()
_ -> throwError "eof expected"
-- NB: consumes the end-event
manyUnless :: Monad m => (YE.Event -> Bool) -> PT n m a -> PT n m [a]
manyUnless p act = do
t0 <- peek1
if p t0
then anyEv >> return []
else liftM2 (:) act (manyUnless p act)
{-
tryError :: MonadError e m => m a -> m (Either e a)
tryError act = catchError (Right <$> act) (pure . Left)
-}
isDocStart :: YE.Event -> Bool
isDocStart (YE.DocumentStart _) = True
isDocStart _ = False
isDocEnd :: YE.Event -> Bool
isDocEnd (YE.DocumentEnd _) = True
isDocEnd _ = False