> {-# LANGUAGE OverlappingInstances #-}
> {-# OPTIONS -XFlexibleInstances -XTypeSynonymInstances #-}
> module Foreign.Erlang.Types (
>   -- * Native Erlang data types.
>   -- ** Haskell representation.
>     ErlType(..)
>   -- ** Conversion between native Haskell types and ErlType.
>   , Erlang
>   , fromErlang
>   , toErlang
>   -- ** Easy type-safe access to tuple members.
>   , nth
>
>   -- ** Internal packing functions.
>   , getA, getC, getErl, getN, geta, getn
>   , putA, putC, putErl, putN, puta, putn
>   , tag
>   ) where
> import Control.Exception  (assert)
> import Control.Monad      (forM, liftM)
> import Data.Binary
> import Data.Binary.Get
> import Data.Binary.Put
> import Data.Char          (chr, ord)
> import qualified Data.ByteString       as B
> import qualified Data.ByteString.Char8 as C
> nth                  :: Erlang a => Int -> ErlType -> a
> nth i (ErlTuple lst) = fromErlang $ lst !! i
> data ErlType = ErlNull
>              | ErlInt Int
>              | ErlBigInt Integer
>              | ErlString String
>              | ErlAtom String
>              | ErlBinary [Word8]
>              | ErlList [ErlType]
>              | ErlTuple [ErlType]
>              | ErlPid ErlType Int Int Int     -- node id serial creation
>              | ErlPort ErlType Int Int        -- node id creation
>              | ErlRef ErlType Int Int         -- node id creation
>              | ErlNewRef ErlType Int [Word8]  -- node creation id
>              deriving (Eq, Show)
> class Erlang a where
>     toErlang   :: a -> ErlType
>     fromErlang :: ErlType -> a
> instance Erlang ErlType where
>     toErlang   = id
>     fromErlang = id
> instance Erlang Int where
>     toErlang   x             = ErlInt x
>     fromErlang (ErlInt x)    = x
>     fromErlang (ErlBigInt x) = fromIntegral x
> instance Erlang Integer where
>     toErlang   x             = ErlBigInt x
>     fromErlang (ErlInt x)    = fromIntegral x
>     fromErlang (ErlBigInt x) = x
> instance Erlang String where
>     toErlang   x             = ErlString x
>     fromErlang ErlNull       = ""
>     fromErlang (ErlString x) = x
>     fromErlang (ErlAtom x)   = x
>     fromErlang (ErlList xs)  = map (chr . fromErlang) xs
>     fromErlang x             = error $ "can't convert to string: " ++ show x
> instance Erlang Bool where
>     toErlang   True              = ErlAtom "true"
>     toErlang   False             = ErlAtom "false"
>     fromErlang (ErlAtom "true")  = True
>     fromErlang (ErlAtom "false") = False
> instance Erlang [ErlType] where
>     toErlang   []           = ErlNull
>     toErlang   xs           = ErlList xs
>     fromErlang ErlNull      = []
>     fromErlang (ErlList xs) = xs
> instance Erlang a => Erlang [a] where
>     toErlang   []           = ErlNull
>     toErlang   xs           = ErlList . map toErlang $ xs
>     fromErlang ErlNull      = []
>     fromErlang (ErlList xs) = map fromErlang xs
> instance (Erlang a, Erlang b) => Erlang (a, b) where
>     toErlang   (x, y)            = ErlTuple [toErlang x, toErlang y]
>     fromErlang (ErlTuple [x, y]) = (fromErlang x, fromErlang y)
> instance (Erlang a, Erlang b, Erlang c) => Erlang (a, b, c) where
>     toErlang   (x, y, z)            = ErlTuple [toErlang x, toErlang y, toErlang z]
>     fromErlang (ErlTuple [x, y, z]) = (fromErlang x, fromErlang y, fromErlang z)
> instance (Erlang a, Erlang b, Erlang c, Erlang d) => Erlang (a, b, c, d) where
>     toErlang   (x, y, z, w)            = ErlTuple [toErlang x, toErlang y, toErlang z, toErlang w]
>     fromErlang (ErlTuple [x, y, z, w]) = (fromErlang x, fromErlang y, fromErlang z, fromErlang w)
> instance (Erlang a, Erlang b, Erlang c, Erlang d, Erlang e) => Erlang (a, b, c, d, e) where
>     toErlang   (x, y, z, w, a)            = ErlTuple [toErlang x, toErlang y, toErlang z, toErlang w, toErlang a]
>     fromErlang (ErlTuple [x, y, z, w, a]) = (fromErlang x, fromErlang y, fromErlang z, fromErlang w, fromErlang a)
> instance Binary ErlType where
>     put = putErl
>     get = getErl
> putErl (ErlInt val)
>     | 0 <= val && val < 256 = tag 'a' >> putC val
>     | otherwise             = tag 'b' >> putN val
> putErl (ErlAtom val)        = tag 'd' >> putn (length val) >> putA val
> putErl (ErlTuple val)
>     | len < 256             = tag 'h' >> putC len >> mapM_ putErl val
>     | otherwise             = tag 'i' >> putN len >> mapM_ putErl val
>   where
>     len = length val
> putErl ErlNull              = tag 'j'
> putErl (ErlString val)      = tag 'k' >> putn (length val) >> putA val
> putErl (ErlList val)        = tag 'l' >> putN (length val) >> mapM_ putErl val >> putErl ErlNull
> putErl (ErlBinary val)      = tag 'm' >> putN (length val) >> puta val
> putErl (ErlRef node id creation) = do
>     tag 'e'
>     putErl node
>     putN id
>     putC creation
> putErl (ErlPort node id creation) = do
>     tag 'f'
>     putErl node
>     putN id
>     putC creation
> putErl (ErlPid node id serial creation) = do
>     tag 'g'
>     putErl node
>     putN id
>     putN serial
>     putC creation
> putErl (ErlNewRef node creation id) = do
>     tag 'r'
>     putn $ length id `div` 4
>     putErl node
>     putC creation
>     mapM_ putWord8 id
> getErl = do
>     tag <- liftM chr getC
>     case tag of
>       'a' -> liftM ErlInt getC
>       'b' -> liftM ErlInt getN
>       'd' -> getn >>= liftM ErlAtom . getA
>       'e' -> do
>         node <- getErl
>         id <- getN
>         creation <- getC
>         return $ ErlRef node id creation
>       'f' -> do
>         node <- getErl
>         id <- getN
>         creation <- getC
>         return $ ErlPort node id creation
>       'g' -> do
>         node <- getErl
>         id <- getN
>         serial <- getN
>         creation <- getC
>         return $ ErlPid node id serial creation
>       'h' -> getC >>= \len -> liftM ErlTuple $ forM [1..len] (const getErl)
>       'i' -> getN >>= \len -> liftM ErlTuple $ forM [1..len] (const getErl)
>       'j' -> return ErlNull
>       'k' -> getn >>= liftM ErlString . getA
>       'l' -> do
>         len <- getN
>         list <- liftM ErlList $ forM [1..len] (const getErl)
>         null <- getErl
>         assert (null == ErlNull) $ return list
>       'm' -> getN >>= liftM ErlBinary . geta
>       'r' -> do
>         len <- getn
>         node <- getErl
>         creation <- getC
>         id <- forM [1..4*len] (const getWord8)
>         return $ ErlNewRef node creation id
>       x -> error [x]
> tag = putC . ord
> putC = putWord8 . fromIntegral
> putn = putWord16be . fromIntegral
> putN = putWord32be . fromIntegral
> puta = putByteString . B.pack
> putA = putByteString . C.pack
> getC = liftM fromIntegral getWord8
> getn = liftM fromIntegral getWord16be
> getN = liftM fromIntegral getWord32be
> geta = liftM B.unpack . getByteString
> getA = liftM C.unpack . getByteString