packages feed

fb-util-0.1.0.1: Foreign/CPP/HsStruct/HsStdVariant.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.HsStdVariant
  ( deriveHsStdVariantUnsafe
  ) where

import Language.Haskell.TH

import Foreign
import Foreign.C.Types
import Foreign.CPP.Addressable
import Foreign.CPP.Marshallable

deriveHsStdVariantUnsafe
  :: String -> Int -> Int -> String -> TypeQ -> Q [Dec]
deriveHsStdVariantUnsafe cppName sizeVal alignmentVal hsName hsType = do
  typeName <- do
    v <- lookupTypeName hsName
    case v of
      Just val -> return val
      Nothing -> fail ("Unable to find type " ++ hsName)
  -- Get the constructors from the data type
  constructors <- do
    info <- reify typeName
    case info of
      -- start zip from `1` since `0` corresponds to `monostate`
      TyConI (DataD _ _ _ _ cons' _) -> return $ zip [1..] cons'
      _ -> fail ("Unsupported type for HsStdVariant " ++ hsName)

  addressableInst <- instanceD
    (cxt [])
    [t| Addressable $hsType |]
    []

  let (cppPeekValName, hsPeekValName) = importNames "peek"
  peekValImport <- forImpD cCall unsafe cppPeekValName hsPeekValName
    [t| Ptr () -> Ptr CInt -> IO (Ptr ()) |]

  let (cppPokeValName, hsPokeValName) = importNames "poke"
  pokeValImport <- forImpD cCall unsafe cppPokeValName hsPokeValName
    [t| Ptr () -> Ptr () -> CInt -> IO () |]

  (peekMatches, pokeClauses) <-
    unzip <$> mapM (getConData hsPokeValName) constructors

  -- Storable
  let
    sizeOfFn = funD (mkName "sizeOf")
      [ clause [wildP] (normalB $ litE $ integerL $ fromIntegral sizeVal) [] ]

    alignmentFn = funD (mkName "alignment")
      [ clause [wildP] (normalB $
          litE $ integerL $ fromIntegral alignmentVal) []
      ]

    pokeFn = funD (mkName "poke") pokeClauses

    idxN = mkName "idx"
    idxpN = mkName "idx_p"
    peekN = mkName "peek"

    -- Peek by calling `hsPeekValName` to extract idx and ptr
    -- Unknown returned index results in `error`
    peekFn = funD peekN
      [clause [varP pN]
        (normalB $ varE (mkName "alloca") `appE` lamE [varP idxpN] (doE
          [ bindS
            (varP valpN)
            (varE hsPeekValName `appE`
              parensE (varE castPtrN `appE` varE pN) `appE`
              varE idxpN)
          , bindS (varP idxN) (varE peekN `appE` varE idxpN)
          , noBindS (caseE (varE idxN) (peekMatches ++
              [ match wildP (normalB $
                varE (mkName "error") `appE` parensE
                  (infixApp
                    (stringE ("Unable to peek " ++ hsName ++ " with index "))
                    (varE $ mkName "++")
                    (varE (mkName "show") `appE` varE idxN)
                  )
                ) []
              ])
            )
          ])) []
      ]

  storableInst <- instanceD
    (cxt [])
    [t| Storable $hsType |]
    [sizeOfFn, alignmentFn, pokeFn, peekFn]


  -- Constructible
  constructibleInst <- instanceD
    (cxt [])
    [t| Constructible $hsType |]
    []

  return
    [ addressableInst
    , constructibleInst
    , peekValImport
    , pokeValImport
    , storableInst
    ]
  where
    pN = mkName "p"
    castPtrN = mkName "castPtr"
    valpN = mkName "__val_p"

    importNames pre = ("std_variant_" ++ base, mkName $ "c_" ++ base)
      where
        base = pre ++ cppName

    getConData hsPokeName (idx, NormalC name [(_, t)] ) = do
        isDestructible <- isInstance (mkName "Destructible") [t]

        let
          -- A match clause for the given index, using the constructor `name`
          -- after `peek`ing the result
          peekMatch = match (litP $ integerL idx) (normalB $ infixApp
              (conE name)
              (varE $ mkName "fmap")
              (varE (mkName "peek") `appE` parensE (varE castPtrN `appE`
                varE valpN))
            )
            []

        let
          valN = mkName "__val"
          withObjFn = if isDestructible then "withCxxObject" else "with"
          -- Matching against the particular constructor `name`,
          -- Using with/withCxxObject to get a pointer to the underlying data
          -- to send down
          pokeClause = clause [varP pN, conP name [varP valN]] (normalB $
            varE (mkName withObjFn) `appE` varE valN `appE` lamE [varP valpN] (
              varE hsPokeName `appE`
              parensE (varE castPtrN `appE` varE pN) `appE`
              parensE (varE castPtrN `appE` varE valpN) `appE`
              litE (integerL idx)
            ))
            []

        return (peekMatch, pokeClause)
    getConData _ _ = fail "Only take unnamed constructors with 1 type"