packages feed

tttool-1.0: src/BakedVector.hs

{-# LANGUAGE QuasiQuotes, TemplateHaskell, ScopedTypeVariables, CPP #-}

-- © 2015 Patrick Chilton

module BakedVector where

import qualified Data.ByteString.Lazy as BSL
#if __GLASGOW_HASKELL__ <= 704
import Data.ByteString.Lazy.Char8 (unpack)
#else
import Data.ByteString.Lazy (unpack)
#endif
import Data.ByteString.Builder (toLazyByteString)
import Data.ByteString.Builder.Prim
import Foreign.Storable
import qualified Data.Vector.Storable as VS
import Foreign.ForeignPtr
import GHC.Ptr
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import System.IO.Unsafe
import Data.Word
import Utils

-- Compiled-in Storable Vectors.
-- Example: [intVector| 1, 2, 4, 6, 7 |]
--          [intString|\xEF\xBE\xAD\xDE\NUL\NUL\NUL\NUL]
--          $(bakeVector [ 1, 2, 3 ])

class (Read a, Storable a) => Bakable a where
  bakeType :: proxy a -> Q Type
  bakePrim :: proxy a -> FixedPrim a

bakeString :: forall proxy a. Bakable a => proxy a -> BSL.ByteString -> Q Exp
bakeString p s =
  case fromIntegral (BSL.length s) `quotRem` sizeOf (undefined :: a) of
    ( len, 0 ) ->
      [| let ptr = unsafeDupablePerformIO $ newForeignPtr_ $ Ptr $(litE)
         in VS.unsafeFromForeignPtr ptr 0 len :: VS.Vector $(bakeType p)
      |] where litE = return $ LitE $ StringPrimL $ unpack s
    _ -> fail "Invalid string length."

bakeVector :: Bakable a => [ a ] -> Q Exp
bakeVector xs
  = bakeString xs $ toLazyByteString $ primMapListFixed (bakePrim xs) xs

quoter :: (String -> Q Exp) -> QuasiQuoter
quoter qExp = QuasiQuoter qExp unsupported unsupported unsupported
  where unsupported = fail "Baked vectors must be used as expressions."

bakedString :: Bakable a => proxy a -> QuasiQuoter
bakedString p = quoter $ \s ->
  case readMaybe ("\"" ++ s ++ "\"") of
    Nothing -> fail "Failed to parse baked vector string."
    Just s' -> bakeString p s'

bakedVector :: forall proxy a. Bakable a => proxy a -> QuasiQuoter
bakedVector _ = quoter $ \s ->
  case readMaybe ("[" ++ s ++ "]") of
    Nothing -> fail "Failed to parse baked vector literal."
    Just xs -> bakeVector (xs :: [ a ])

instance Bakable Int where
  bakeType _ = [t|Int|]
  bakePrim _ = intHost

intVector :: QuasiQuoter
intVector = bakedVector intHost

intString :: QuasiQuoter
intString = bakedString intHost

instance Bakable Float where
  bakeType _ = [t|Float|]
  bakePrim _ = floatHost

floatVector :: QuasiQuoter
floatVector = bakedVector floatHost

floatString :: QuasiQuoter
floatString = bakedString floatHost

instance Bakable Double where
  bakeType _ = [t|Double|]
  bakePrim _ = doubleHost

doubleVector :: QuasiQuoter
doubleVector = bakedVector doubleHost

doubleString :: QuasiQuoter
doubleString = bakedString doubleHost

instance Bakable Word16 where
  bakeType _ = [t|Word16|]
  bakePrim _ = word16Host

word16Vector :: QuasiQuoter
word16Vector = bakedVector word16Host

word16String :: QuasiQuoter
word16String = bakedString word16Host