Question

I'm using bindings-DSL to help deal with some boilerplate in FFI declarations. But I find myself declaring groups of related functions that differ by only a couple of textual elements, and I'd really rather declare these with a macro. CPP or CPPHS seems the ideal choice for this, but I can't find any examples to its usage in the context of Haskell.

I've put this into what I approximately expect to work from my knowledge of C macros:

#define declare_vector_funcs (t, tn, ct) \ 
    #opaque_t vector_##t \
    #ccall create_std_vector##tn , IO (Ptr <vector_##t##>) \
    #ccall carray_to_std_vector##tn , Ptr ct -> CSize -> IO (Ptr <vector_##t##>) \
    #ccall std_vector##tn##_to_carray , Ptr <vector_##t##> -> IO (Ptr ct) \
    #ccall std_vector##tn##_length , Ptr <vector_##t##> -> IO CSize

Essentially, I'd like to define a foreign (opaque) type and 4 foreign functions upon expanding this macro. However, this does not work as it reads everything subsequent to the argument list as GHC pragmas, and fails.

I've already tried a couple different iterations of this, such as messing with spacing and putting everything on one line (wrapped in parentheses to distinguish separate macro calls).

How can I fix this to work? Answers that drop the bindings-DSL usage in favor of a direct translation are fine, but I definitely don't want to write all this out by hand.

I would also very much appreciate a few examples of this kind of CPP usage.

Here's the error message I get if I remove the space between the macro name and argument list:

CPP.hsc:13:39: error: '#' is not followed by a macro parameter
compiling dist/build/Foreign/CPP_hsc_make.c failed (exit code 1)
command was: /usr/bin/g++ -c dist/build/Foreign/CPP_hsc_make.c -o dist/build/Foreign/CPP_hsc_make.o -fno-stack-protector -D__GLASGOW_HASKELL__=708 -Dlinux_BUILD_OS=1 -Dx86_64_BUILD_ARCH=1 -Dlinux_HOST_OS=1 -Dx86_64_HOST_ARCH=1 -Iinclude/ -fpermissive -std=c++11 -fPIC -Idist/build/autogen -include dist/build/autogen/cabal_macros.h -I/usr/local/lib/x86_64-linux-ghc-7.8.2/bindings-DSL-1.0.21/include -I/usr/lib/ghc-7.8.2/base-4.7.0.0/include -I/usr/lib/ghc-7.8.2/integer-gmp-0.5.1.0/include -I/usr/lib/ghc-7.8.2/include -I/usr/lib/ghc-7.8.2/include/

With the space, I get a much longer error message:

dist/build/Foreign/CPP.hs:1:16:
    unknown flag in  {-# OPTIONS_GHC #-} pragma: tn,
dist/build/Foreign/CPP.hs:1:16:
    unknown flag in  {-# OPTIONS_GHC #-} pragma: ct)
dist/build/Foreign/CPP.hs:1:16:
    unknown flag in  {-# OPTIONS_GHC #-} pragma: #opaque_t
dist/build/Foreign/CPP.hs:1:16:
    unknown flag in  {-# OPTIONS_GHC #-} pragma: vector_##t

And this continues for every token. I'm fairly certain this just means the space shouldn't be included, but I'm not really sure what's going on.


EDIT:

New info.

I swapped methodology and I'm trying to generate the final foreign imports directly. The macro (I'll paste in a sec) passes the preprocessor with a couple of warnings, but actually trying to use the macro doesn't yet work:

#define declare_vector_funcs(t, tn ,ct) \
    data C'vector_##t = C'vector_##t \
    foreign import ccall "create_std_vector##tn" c'create_std_vector##tn :: IO (Ptr C'vector_##t) \
    foreign import ccall "carray_to_std_vector##tn" c'carray_to_std_vector##tn :: Ptr ct -> CSize -> IO (Ptr vector_##t) \
    foreign import ccall "std_vector##tn##_to_carray" c'std_vector##tn##_to_carray :: Ptr vector_##t -> IO (Ptr ct) \
    foreign import ccall "std_vector##tn##_length" c'std_vector :: Ptr vector_##t -> IO CSize

At the usage site, I'm trying to use it like:

#declare_vector_funcs int , i , CInt

To match the equivalent declaration on the C side. I expect it to generate a block that looks like:

data C'vector_int = C'vector_int 
foreign import ccall "create_std_vectori" c'create_std_vectori :: IO (Ptr C'vector_int) 
foreign import ccall "carray_to_std_vectori" c'carray_to_std_vectori :: Ptr CInt -> CSize -> IO (Ptr vector_int) 
foreign import ccall "std_vectori_to_carray" c'std_vectori_to_carray :: Ptr vector_int -> IO (Ptr CInt) 
foreign import ccall "std_vectori_length" c'std_vector :: Ptr vector_int -> IO CSize

But instead I get an error:

CPP.hsc: In function ‘int main(int, char**)’:
CPP.hsc:22:31: error: expected primary-expression before ‘int’
CPP.hsc:22:37: error: ‘i’ was not declared in this scope
CPP.hsc:22:41: error: ‘CInt’ was not declared in this scope
CPP.hsc:22:45: error: ‘hsc_declare_vector_funcs’ was not declared in this scope
CPP.hsc:23:31: error: expected primary-expression before ‘float’
CPP.hsc:23:39: error: ‘f’ was not declared in this scope
CPP.hsc:23:43: error: ‘CFloat’ was not declared in this scope
CPP.hsc:24:31: error: expected primary-expression before ‘double’
CPP.hsc:24:40: error: ‘d’ was not declared in this scope
CPP.hsc:24:44: error: ‘CDouble’ was not declared in this scope

So I obviously need to add import Foreign.C to the top, but even so, there's a deeper issue -- I can't treat these tokens as meaningless like I'd like to, something is trying to actually interpret them. Anyone have any ideas?

Was it helpful?

Solution

What you are trying to do isn't quite possible, I don't think, when you define a macro in a hsc file it will get inlined into your generated .hs file, and if that macro calls .hsc macros.. well those won't be available anymore. The best you can do:

module Test where

import Foreign.Ptr
import Foreign.C.Types

data T
data Tn
data Ct

#opaque_t vector_T
#ccall create_std_vectorTn , IO (Ptr <vector_T>)
#ccall carray_to_std_vectorTn , Ptr Ct -> CSize -> IO (Ptr <vector_T>) 
#ccall std_vectorTn_to_carray , Ptr <vector_T> -> IO (Ptr Ct) 
#ccall std_vectorTn_length , Ptr <vector_T> -> IO CSize

If your C compiler knows where to find bindings.dsl.h then you should include it at the top; mine doesn't so I compile with hsc2hs --include=<path-to-bindings.dsl.h> test.hsc. This produces the following file:

{-# LINE 1 "test.hsc" #-}
module Test where
{-# LINE 2 "test.hsc" #-}

import Foreign.Ptr
import Foreign.C.Types

data T
data Tn
data Ct

data C'vector_T = C'vector_T

{-# LINE 11 "test.hsc" #-}
foreign import ccall "create_std_vectorTn" c'create_std_vectorTn
  :: IO (Ptr C'vector_T)
foreign import ccall "&create_std_vectorTn" p'create_std_vectorTn
  :: FunPtr (IO (Ptr C'vector_T))

{-# LINE 12 "test.hsc" #-}
foreign import ccall "carray_to_std_vectorTn" c'carray_to_std_vectorTn
  :: Ptr Ct -> CSize -> IO (Ptr C'vector_T)
foreign import ccall "&carray_to_std_vectorTn" p'carray_to_std_vectorTn
  :: FunPtr (Ptr Ct -> CSize -> IO (Ptr C'vector_T))

{-# LINE 13 "test.hsc" #-}
foreign import ccall "std_vectorTn_to_carray" c'std_vectorTn_to_carray
  :: Ptr C'vector_T -> IO (Ptr Ct)
foreign import ccall "&std_vectorTn_to_carray" p'std_vectorTn_to_carray
  :: FunPtr (Ptr C'vector_T -> IO (Ptr Ct))

{-# LINE 14 "test.hsc" #-}
foreign import ccall "std_vectorTn_length" c'std_vectorTn_length
  :: Ptr C'vector_T -> IO CSize
foreign import ccall "&std_vectorTn_length" p'std_vectorTn_length
  :: FunPtr (Ptr C'vector_T -> IO CSize)

{-# LINE 15 "test.hsc" #-}

So looks like defining your own macro is the way to go:

mymacro.h

#define hsc_declare_vector_funcs(t,tn,ct)\
  hsc_opaque_t(vector_##t)\
  hsc_ccall(create_std_vector##tn ,IO (Ptr <vector_##t>))\
  hsc_ccall(carray_to_std_vector##tn , Ptr ct -> CSize -> IO (Ptr <vector_##t>)) \
  hsc_ccall(std_vector##tn##_to_carray , Ptr <vector_##t> -> IO (Ptr ct)) \
  hsc_ccall(std_vector##tn##_length , Ptr <vector_##t> -> IO CSize)

test.hsc

module Test where

#include "mymacro.h"

import Foreign.Ptr
import Foreign.C.Types

#declare_vector_funcs int, i, CInt

test.hs

module Test where

import Foreign.Ptr
import Foreign.C.Types

data C'vector_int = C'vector_int
foreign import ccall "create_std_vectori" c'create_std_vectori
  :: IO (Ptr C'vector_int)
foreign import ccall "&create_std_vectori" p'create_std_vectori
  :: FunPtr (IO (Ptr C'vector_int))
foreign import ccall "carray_to_std_vectori" c'carray_to_std_vectori
  :: Ptr CInt -> CSize -> IO (Ptr C'vector_int)
foreign import ccall "&carray_to_std_vectori" p'carray_to_std_vectori
  :: FunPtr (Ptr CInt -> CSize -> IO (Ptr C'vector_int))
foreign import ccall "std_vectori_to_carray" c'std_vectori_to_carray
  :: Ptr C'vector_int -> IO (Ptr CInt)
foreign import ccall "&std_vectori_to_carray" p'std_vectori_to_carray
  :: FunPtr (Ptr C'vector_int -> IO (Ptr CInt))
foreign import ccall "std_vectori_length" c'std_vectori_length
  :: Ptr C'vector_int -> IO CSize
foreign import ccall "&std_vectori_length" p'std_vectori_length
  :: FunPtr (Ptr C'vector_int -> IO CSize)

OTHER TIPS

The following example compiles:

{-# LANGUAGE CPP #-}

#define MYMACRO 3
#define MAX(a,b) (if ((a) < (b)) \
              then (b) \
              else (a))

main = print $ show (MAX(MYMACRO,4) :: Int)

I need the parens around the code macro so that the following will (also) work:

main = print $ show MAX(MYMACRO,4)

If you want to do that with bindings-DSL, take a look at how it was done in bindings-gobject. You have to use the hsc_* macros.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top