module Data.Yaml.Syck (
parseYaml, emitYaml,
parseYamlFile, emitYamlFile,
parseYamlBytes, emitYamlBytes,
YamlNode(..), YamlElem(..), YamlAnchor(..),
tagNode, nilNode, mkNode, mkTagNode, mkTagStrNode, SYMID,
packBuf, unpackBuf,
) where
import Control.Exception (bracket)
import Data.IORef
import Data.Maybe (fromJust)
import Data.Generics
import Foreign.Ptr
import Foreign.StablePtr
import Foreign.C.Types
import Foreign.C.String
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import Foreign.Storable
import System.IO.Unsafe
import GHC.Ptr (Ptr(..))
import qualified Data.HashTable as Hash
import qualified Data.ByteString.Char8 as Buf
import Data.ByteString.Char8 (useAsCStringLen, useAsCString)
import Data.ByteString (packCString, packCStringLen)
type Buf = Buf.ByteString
type YamlTag = Maybe Buf
data YamlAnchor
= AAnchor !Int
| AReference !Int
| ASingleton
deriving (Show, Ord, Eq, Typeable, Data)
type SYMID = CULong
instance Data SYMID where
toConstr x = mkIntConstr (mkIntType "Foreign.C.Types.CULong") (fromIntegral x)
gunfold _ z c = case constrRep c of
(IntConstr x) -> z (fromIntegral x)
_ -> error "gunfold"
dataTypeOf _ = mkIntType "Foreign.C.Types.CULong"
data YamlNode = MkNode
{ n_id :: !SYMID
, n_elem :: !YamlElem
, n_tag :: !YamlTag
, n_anchor :: !YamlAnchor
}
deriving (Show, Ord, Eq, Typeable, Data)
data YamlElem
= EMap [(YamlNode, YamlNode)]
| ESeq [YamlNode]
| EStr !Buf
| ENil
deriving (Show, Ord, Eq, Typeable, Data)
type SyckNode = Ptr ()
type SyckParser = Ptr ()
type SyckNodeHandler = SyckParser -> SyckNode -> IO SYMID
type SyckErrorHandler = SyckParser -> CString -> IO ()
type SyckBadAnchorHandler = SyckParser -> CString -> IO SyckNodePtr
type SyckNodePtr = Ptr CString
type SyckEmitter = Ptr ()
type SyckEmitterHandler = SyckEmitter -> Ptr () -> IO ()
type SyckOutputHandler = SyckEmitter -> CString -> CLong -> IO ()
data SyckKind = SyckMap | SyckSeq | SyckStr
deriving (Show, Ord, Eq, Enum)
maxId :: SYMID
maxId = maxBound
nilNode :: YamlNode
nilNode = MkNode maxId ENil Nothing ASingleton
unpackBuf :: Buf -> String
unpackBuf = Buf.unpack
packBuf :: String -> Buf
packBuf = Buf.pack
tagNode :: YamlTag -> YamlNode -> YamlNode
tagNode _ MkNode{n_tag=Just x} = error $ "can't add tag: already tagged with" ++ unpackBuf x
tagNode tag node = node{n_tag = tag}
mkNode :: YamlElem -> YamlNode
mkNode x = MkNode maxId x Nothing ASingleton
mkTagNode :: String -> YamlElem -> YamlNode
mkTagNode tag e = MkNode maxId e (Just $! packBuf tag) ASingleton
mkTagStrNode :: String -> String -> YamlNode
mkTagStrNode tag str = mkTagNode tag (EStr $! packBuf str)
scalarNone :: CInt
scalarNone = 0
scalar1quote :: CInt
scalar1quote = 1
scalar2quote :: CInt
scalar2quote = 2
scalarFold :: CInt
scalarFold = 3
scalarLiteral :: CInt
scalarLiteral = 4
scalarPlain :: CInt
scalarPlain = 5
seqNone :: CInt
seqNone = 0
seqInline :: CInt
seqInline = 1
mapNone :: CInt
mapNone = 0
mapInline :: CInt
mapInline = 1
emitYamlBytes :: YamlNode -> IO Buf
emitYamlBytes node = do
bracket syck_new_emitter syck_free_emitter $ \emitter -> do
out <- newIORef []
(\hsc_ptr -> pokeByteOff hsc_ptr 28) emitter scalarFold
(\hsc_ptr -> pokeByteOff hsc_ptr 16) emitter (Ptr "%d"# :: CString)
marks <- Hash.new (==) (Hash.hashInt)
let freeze = freezeNode marks
syck_emitter_handler emitter =<< mkEmitterCallback (emitterCallback freeze)
syck_output_handler emitter =<< mkOutputCallback (outputCallbackPS out)
markYamlNode freeze emitter node
nodePtr <- freeze node
let nodePtr' = fromIntegral $ nodePtr `minusPtr` nullPtr
syck_emit emitter nodePtr'
syck_emitter_flush emitter 0
fmap (Buf.concat . reverse) (readIORef out)
emitYamlFile :: FilePath -> YamlNode -> IO ()
emitYamlFile file node = do
buf <- emitYamlBytes node
Buf.writeFile file buf
emitYaml :: YamlNode -> IO String
emitYaml node = fmap unpackBuf (emitYamlBytes node)
markYamlNode :: (YamlNode -> IO SyckNodePtr) -> SyckEmitter -> YamlNode -> IO ()
markYamlNode freeze emitter node = do
nodePtr <- freeze node
rv <- syck_emitter_mark_node emitter nodePtr
if rv == 0 then return () else do
case n_elem node of
EMap xs -> sequence_ [ mark x >> mark y | (x, y) <- xs ]
ESeq xs -> mapM_ mark xs
_ -> return ()
where
mark = markYamlNode freeze emitter
outputCallbackPS :: IORef [Buf] -> SyckEmitter -> CString -> CLong -> IO ()
outputCallbackPS out emitter buf len = do
str <- packCStringLen (buf, fromEnum len)
str `seq` modifyIORef out (str:)
outputCallback :: SyckEmitter -> CString -> CLong -> IO ()
outputCallback emitter buf len = do
outPtr <- (\hsc_ptr -> peekByteOff hsc_ptr 96) emitter
out <- deRefStablePtr (castPtrToStablePtr outPtr)
str <- peekCStringLen (buf, fromIntegral len)
modifyIORef out (++ str)
emitterCallback :: (YamlNode -> IO SyckNodePtr) -> SyckEmitter -> Ptr () -> IO ()
emitterCallback f e vp = emitNode f e =<< thawNode vp
emitNode :: (YamlNode -> IO SyckNodePtr) -> SyckEmitter -> YamlNode -> IO ()
emitNode _ e n | n_elem n == ENil = do
withTag n (Ptr "string"#) $ \tag ->
syck_emit_scalar e tag scalarNone 0 0 0 (Ptr "~"#) 1
emitNode _ e n | EStr s <- n_elem n, Buf.length s == 1, Buf.head s == '~' = do
withTag n (Ptr "string"#) $ \tag ->
syck_emit_scalar e tag scalar1quote 0 0 0 (Ptr "~"#) 1
emitNode _ e n | EStr s <- n_elem n = do
withTag n (Ptr "string"#) $ \tag ->
useAsCStringLen s $ \(cs, l) ->
syck_emit_scalar e tag scalarFold 0 0 0 cs (toEnum l)
emitNode freeze e n | ESeq sq <- n_elem n = do
withTag n (Ptr "array"#) $ \tag ->
syck_emit_seq e tag seqNone
mapM_ (syck_emit_item e) =<< mapM freeze sq
syck_emit_end e
emitNode freeze e n | EMap m <- n_elem n = do
withTag n (Ptr "map"#) $ \tag ->
syck_emit_map e tag mapNone
flip mapM_ m (\(k,v) -> do
syck_emit_item e =<< freeze k
syck_emit_item e =<< freeze v)
syck_emit_end e
withTag :: YamlNode -> CString -> (CString -> IO a) -> IO a
withTag node def f = maybe (f def) (`useAsCString` f) (n_tag node)
parseYaml :: String -> IO YamlNode
parseYaml = (`withCString` parseYamlCStr)
parseYamlFile :: String -> IO YamlNode
parseYamlFile file = parseYamlBytes =<< Buf.readFile file
parseYamlBytes :: Buf -> IO YamlNode
parseYamlBytes = (`useAsCString` parseYamlCStr)
parseYamlCStr :: CString -> IO YamlNode
parseYamlCStr cstr = do
bracket syck_new_parser syck_free_parser $ \parser -> do
err <- newIORef Nothing
badancs <- Hash.new (==) Hash.hashInt
syck_parser_str_auto parser cstr nullFunPtr
syck_parser_handler parser =<< mkNodeCallback (nodeCallback badancs)
syck_parser_error_handler parser =<< mkErrorCallback (errorCallback err)
syck_parser_bad_anchor_handler parser =<< mkBadHandlerCallback (badAnchorHandlerCallback badancs)
syck_parser_implicit_typing parser 0
syck_parser_taguri_expansion parser 0
symId <- syck_parse parser
if symId /= 0 then readNode parser symId else do
rv <- readIORef err
case rv of
Nothing -> return nilNode
Just e -> fail e
type BadAnchorTable = Hash.HashTable Int YamlNode
nodeCallback :: BadAnchorTable -> SyckParser -> SyckNode -> IO SYMID
nodeCallback badancs parser syckNode = mdo
nodeId <- (\hsc_ptr -> peekByteOff hsc_ptr 0) syckNode
kind <- syckNodeKind syckNode
let makeRegularNode = do
len <- syckNodeLength kind syckNode
parseNode kind parser syckNode len symId
node <- case kind of
SyckMap -> do
rv <- Hash.lookup badancs (syckNode `minusPtr` nullPtr)
case rv of
Just{} -> do
unsafeInterleaveIO (fmap fromJust (Hash.lookup badancs (nodePtr `minusPtr` nullPtr)))
_ -> makeRegularNode
_ -> makeRegularNode
nodePtr <- writeNode node
case nodeId :: SYMID of
0 -> return False
_ -> alloca $ \origPtr -> do
syck_lookup_sym parser nodeId origPtr
ptr <- peek origPtr
Hash.update badancs (ptr `minusPtr` nullPtr) node
symId <- fmap fromIntegral (syck_add_sym parser nodePtr)
return symId
badAnchorHandlerCallback :: BadAnchorTable -> SyckBadAnchorHandler
badAnchorHandlerCallback badancs parser cstr = do
syckNode <- syck_alloc_map
Hash.insert badancs (syckNode `minusPtr` nullPtr) (error "unhandled bad anchor")
return syckNode
errorCallback :: IORef (Maybe String) -> SyckErrorHandler
errorCallback err parser cstr = do
msg <- peekCString cstr
lineptr <- (\hsc_ptr -> peekByteOff hsc_ptr 48) parser :: IO CChar
cursor <- (\hsc_ptr -> peekByteOff hsc_ptr 60) parser :: IO CChar
linect <- (\hsc_ptr -> peekByteOff hsc_ptr 72) parser :: IO CChar
writeIORef err . Just $ concat
[ msg
, ": line ", show (1 + fromEnum linect)
, ", column ", show (cursor lineptr)
]
freezeNode :: Hash.HashTable Int (Ptr a) -> YamlNode -> IO (Ptr a)
freezeNode nodes MkNode{ n_anchor = AReference n } = do
rv <- Hash.lookup nodes n
case rv of
Just ptr -> return ptr
_ -> fail $ "Failed to resolve reference: " ++ show n
freezeNode nodes node = do
ptr <- newStablePtr node
case n_anchor node of
AAnchor n -> do
rv <- Hash.lookup nodes n
case rv of
Just ptr -> return ptr
_ -> do
ptr <- newStablePtr node
let ptr' = castPtr (castStablePtrToPtr ptr)
Hash.insert nodes n ptr'
return $! ptr'
_ -> do
ptr <- newStablePtr node
return $! castPtr (castStablePtrToPtr ptr)
thawNode :: Ptr () -> IO YamlNode
thawNode nodePtr = deRefStablePtr (castPtrToStablePtr nodePtr)
writeNode :: YamlNode -> IO SyckNodePtr
writeNode node = do
ptr <- newStablePtr node
new (castPtr $ castStablePtrToPtr ptr)
readNode :: SyckParser -> SYMID -> IO YamlNode
readNode parser symId = alloca $ \nodePtr -> do
syck_lookup_sym parser symId nodePtr
ptr <- peek . castPtr =<< peek nodePtr
deRefStablePtr (castPtrToStablePtr ptr)
_tagLiteral, _colonLiteral :: Buf
_tagLiteral = packBuf "tag:"
_colonLiteral = packBuf ":"
syckNodeTag :: SyckNode -> IO (Maybe Buf)
syckNodeTag syckNode = do
tag <- (\hsc_ptr -> peekByteOff hsc_ptr 8) syckNode
if (tag == nullPtr) then (return Nothing) else do
p <- packCString tag
return $! case Buf.elemIndex '/' p of
Just n -> let { pre = Buf.take n p; post = Buf.drop (n+1) p } in
Just $ Buf.concat [_tagLiteral, pre, _colonLiteral, post]
Nothing -> Nothing
syckNodeKind :: SyckNode -> IO SyckKind
syckNodeKind syckNode = do
kind <- (\hsc_ptr -> peekByteOff hsc_ptr 4) syckNode
return $ toEnum (kind `mod` 4)
syckNodeLength :: SyckKind -> SyckNode -> IO CLong
syckNodeLength SyckMap = ((\hsc_ptr -> peekByteOff hsc_ptr 16) =<<) . (\hsc_ptr -> peekByteOff hsc_ptr 16)
syckNodeLength SyckSeq = ((\hsc_ptr -> peekByteOff hsc_ptr 12) =<<) . (\hsc_ptr -> peekByteOff hsc_ptr 16)
syckNodeLength SyckStr = ((\hsc_ptr -> peekByteOff hsc_ptr 8) =<<) . (\hsc_ptr -> peekByteOff hsc_ptr 16)
parseNode :: SyckKind -> SyckParser -> SyckNode -> CLong -> SYMID -> IO YamlNode
parseNode SyckMap parser syckNode len nid = do
tag <- syckNodeTag syckNode
pairs <- (`mapM` [0..len1]) $ \idx -> do
keyId <- syck_map_read syckNode 0 idx
key <- readNode parser keyId
valId <- syck_map_read syckNode 1 idx
val <- readNode parser valId
return (key, val)
return $ nilNode{ n_elem = EMap pairs, n_tag = tag, n_id = nid }
parseNode SyckSeq parser syckNode len nid = do
tag <- syckNodeTag syckNode
nodes <- (`mapM` [0..len1]) $ \idx -> do
symId <- syck_seq_read syckNode idx
readNode parser symId
return $ nilNode{ n_elem = ESeq nodes, n_tag = tag, n_id = nid }
parseNode SyckStr _ syckNode len nid = do
tag <- syckNodeTag syckNode
cstr <- syck_str_read syckNode
buf <- packCStringLen (cstr, fromEnum len)
let node = nilNode{ n_elem = EStr buf, n_tag = tag, n_id = nid }
if tag == Nothing && Buf.length buf == 1 && Buf.index buf 0 == '~'
then do
style <- syck_str_style syckNode
if style == scalarPlain
then return nilNode
else return node
else return node
foreign import ccall "wrapper"
mkNodeCallback :: SyckNodeHandler -> IO (FunPtr SyckNodeHandler)
foreign import ccall "wrapper"
mkErrorCallback :: SyckErrorHandler -> IO (FunPtr SyckErrorHandler)
foreign import ccall "wrapper"
mkBadHandlerCallback :: SyckBadAnchorHandler -> IO (FunPtr SyckBadAnchorHandler)
foreign import ccall "wrapper"
mkOutputCallback :: SyckOutputHandler -> IO (FunPtr SyckOutputHandler)
foreign import ccall "wrapper"
mkEmitterCallback :: SyckEmitterHandler -> IO (FunPtr SyckEmitterHandler)
foreign import ccall
syck_new_parser :: IO SyckParser
foreign import ccall
syck_alloc_map :: IO SyckNodePtr
foreign import ccall
syck_parser_str_auto :: SyckParser -> CString -> FunPtr () -> IO ()
foreign import ccall
syck_parser_error_handler :: SyckParser -> FunPtr SyckErrorHandler -> IO ()
foreign import ccall
syck_parser_bad_anchor_handler :: SyckParser -> FunPtr SyckBadAnchorHandler -> IO ()
foreign import ccall
syck_parser_implicit_typing :: SyckParser -> CInt -> IO ()
foreign import ccall
syck_parser_taguri_expansion :: SyckParser -> CInt -> IO ()
foreign import ccall
syck_parser_handler :: SyckParser -> FunPtr SyckNodeHandler -> IO ()
foreign import ccall
syck_add_sym :: SyckParser -> SyckNodePtr -> IO CInt
foreign import ccall
syck_parse :: SyckParser -> IO SYMID
foreign import ccall
syck_free_parser :: SyckParser -> IO ()
foreign import ccall
syck_lookup_sym :: SyckParser -> SYMID -> Ptr SyckNodePtr -> IO CInt
foreign import ccall
syck_str_read :: SyckNode -> IO CString
foreign import ccall
syck_str_style :: SyckNode -> IO CInt
foreign import ccall
syck_seq_read :: SyckNode -> CLong -> IO SYMID
foreign import ccall
syck_map_read :: SyckNode -> CInt -> CLong -> IO SYMID
foreign import ccall
syck_new_emitter :: IO SyckEmitter
foreign import ccall
syck_free_emitter :: SyckEmitter -> IO ()
foreign import ccall
syck_emitter_handler :: SyckEmitter -> FunPtr SyckEmitterHandler -> IO ()
foreign import ccall
syck_output_handler :: SyckEmitter -> FunPtr SyckOutputHandler -> IO ()
foreign import ccall
syck_emit :: SyckEmitter -> CLong -> IO ()
foreign import ccall
syck_emitter_flush :: SyckEmitter -> CLong -> IO ()
foreign import ccall
syck_emitter_mark_node :: SyckEmitter -> SyckNodePtr -> IO SYMID
foreign import ccall
syck_emit_scalar :: SyckEmitter -> CString -> CInt -> CInt -> CInt -> CInt -> CString -> CInt -> IO ()
foreign import ccall
syck_emit_seq :: SyckEmitter -> CString -> CInt -> IO ()
foreign import ccall
syck_emit_item :: SyckEmitter -> SyckNodePtr -> IO ()
foreign import ccall
syck_emit_end :: SyckEmitter -> IO ()
foreign import ccall
syck_emit_map :: SyckEmitter -> CString -> CInt -> IO ()