packages feed

fb-util-0.1.0.1: Foreign/CPP/HsStruct/HsMap.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# LANGUAGE TemplateHaskell #-}

module Foreign.CPP.HsStruct.HsMap (
  deriveHsHashMapUnsafe,
) where

import qualified Control.Monad
import Data.Char (isAlphaNum)
import qualified Data.HashMap.Strict as HashMap
import Language.Haskell.TH

import Foreign (Ptr)
import Foreign.C.Types (CSize)
import Foreign.CPP.Marshallable

-- HsHashMap -------------------------------------------------------------------

deriveHsHashMapUnsafe ::
  String -> TypeQ -> TypeQ -> Q [Dec]
deriveHsHashMapUnsafe cppType hsKeyType hsValType = do
  hashMapTypeStr <-
    map (\i -> if isAlphaNum i then i else '_') . pprint
      <$> hashMapType

  let cppConstructName = "map_construct" ++ cppName
      hsConstructName = mkName $ "c_map_construct" ++ hashMapTypeStr
  constructImport <-
    forImpD
      cCall
      unsafe
      cppConstructName
      hsConstructName
      [t|Ptr () -> CSize -> IO ()|]

  let cppAddName = "map_add" ++ cppName
      hsAddName = mkName $ "c_map_add" ++ hashMapTypeStr
  addImport <-
    forImpD
      cCall
      unsafe
      cppAddName
      hsAddName
      [t|Ptr () -> Ptr $hsKeyType -> Ptr $hsValType -> IO ()|]

  hsKT <- hsKeyType
  hsVT <- hsValType
  isKeyDestructible <- isInstance (mkName "Destructible") [hsKT]
  isValDestructible <- isInstance (mkName "Destructible") [hsVT]

  let keyWithFn = mkName $ if isKeyDestructible then "withCxxObject" else "with"
      valWithFn = mkName $ if isValDestructible then "withCxxObject" else "with"
      pN = mkName "p"
      hmN = mkName "hm"
      addChunk =
        [|
          Control.Monad.forM_ (HashMap.toList hm) $ \(key, val) ->
            $(varE keyWithFn) key $ \key_ptr ->
              $(varE valWithFn) val $ \val_ptr ->
                $(varE hsAddName) (castPtr p) key_ptr val_ptr
          |]

      -- newValue (HsHashMap _h) = error "HsHashMap cannot be made on the heap"
      newValueFn =
        funD
          (mkName "newValue")
          [ clause
              [conP (mkName "HsHashMap") [varP $ mkName "_h"]]
              (normalB [|error "HsHashMap cannot be made on the heap"|])
              []
          ]

      -- constructValue p (HsHashMap hm) = do
      --    $(varE hsConstructName) (castPtr p) (fromIntegral (length hm))
      --    Control.Monad.forM_ (toList hm) $ (\key, \val) ->
      --      $(varE withKeyFn) key $ \key_ptr ->
      --         $(varE withValFn) val $ \val_ptr ->
      --            $(varE hsAddName) (castPtr p) key_ptr val_ptr
      constructValueFn =
        funD
          (mkName "constructValue")
          [ clause
              [varP pN, conP (mkName "HsHashMap") [varP hmN]]
              ( normalB $
                  doE
                    [ noBindS
                        [|
                          $(varE hsConstructName)
                            (castPtr p)
                            (fromIntegral (length hm))
                          |]
                    , noBindS addChunk
                    ]
              )
              []
          ]

  -- instance Constructible (HsHashMap <TYPE>) where
  constructibleHashMapInst <-
    instanceD
      (cxt [])
      [t|Constructible $hashMapType|]
      [ newValueFn
      , constructValueFn
      ]

  return
    [ constructibleHashMapInst
    , constructImport
    , addImport
    ]
  where
    hashMapType = conT (mkName "HsHashMap") `appT` hsKeyType `appT` hsValType
    cppName = "HsMap" ++ cppType