A Data Interface Scheme, or DIS, tells Green Card how to translate from a Haskell data type to a C data type, and vice versa.
The syntax of DISs is given in Figure fig:dis-syntax . It is designed to be similar to the syntax of Haskell patterns. A DIS takes one of the following forms:
int
,
float
, double
; the full set is given in
Section
sec:dis-std
. For example:
%fun foo :: This -> Int -> That
%call (this x y) (int z)
%code r = c_foo( x, y, z );
%result (that r)
In this example this
and that
are DIS functions defined
elsewhere.
newtype Age = Age Int
%fun foo :: (Age,Age) -> Age
%call (Age (int x), Age (int y))
%code r = foo(x,y);
%result (Age (int r))
As the %call
line of this example illustrates, tuples are
understood as data constructors, including their special syntax.
The labelled fields syntax is also supported, i.e.:
data Point = Point { px,py::Int }
%fun foo :: Point -> Point
%call (Point { px = int x, py = int y })
...
The use of records is also the reason for the restriction that
simple C expressions can't contain assignment. Without this
restriction examples like this would be ambiguous:
%result Foo { a = bar x, b = bar y }
Green Card does not attempt to perform type inference; it simply assumes
that any DIS starting with an upper case letter is a data constructor,
and that the number of argument DISs matches the arity of the constructor.
data Nat = Zero | Succ Nat
fromNat :: Nat -> Int
toNat :: Int -> Nat
%fun square :: T -> T
%call (< fromNat / toNat > (int x))
%code r = square(x);
%result (< fromNat / toNat > (int r))
Here the function fromNat
is applied to square
's argument,
converting it to an integer before it crosses the fence into
C. Likewise, the result coming back from C is converted back to type
Nat
by the function toNat
.
The user functions can have any name at all: in fact, the
<../..>
syntax simply encloses two fragments of
arbitrary Haskell to be applied to the succeeding arguments. One may
specify a partially applied function, or anything else (excluding the
use of the /
and >
symbols - so lambda abstractions
are unfortunately not possible.) The user-defined DIS may of course
also take more than one parameter. For example:
data Point = P Dist Vector
polarToCart :: Polar -> (Int,Int)
cartToPolar :: (Int,Int) -> Polar
%fun mirror :: Polar -> Polar
%call (< polarToCart / cartToPolar > (int x) (int y))
%code y = -y;
% x = -x;
%result (< polarToCart / cartToPolar > (int x) (int y))
Notice that all the example marshalling functions have so far been
pure functions, e.g., fromNat
has type Nat -> Int
rather
than T -> IO Int
.) Sometimes you need to write a marshalling
function that is internally stateful. When you do, you'll need to
inform Green Card of this, so that it can generate code that invokes
the marshalling functions correctly. For example:
marshallString :: String -> IO Addr
unmarshallString :: Addr -> IO
%fun setWindowTitle :: String -> IO ()
%call (<< marshallString / unmarshallString >> str)
i.e., stateful marshalling functions are enclosed by double angle
brackets.
declare {cexp} var in
dis
form can be used to do the necessary type conversion in
C. Examples:
%fun foo :: Int -> IO ()
%call (declare {unsigned int} x in (int x))
data T = MkT Int
%fun faz :: T -> IO ()
%call (declare {c_t} x in MkT (int x))
int
and
Int
respectively, and consist of the Haskell type name prefixed
by %%
(e.g., %%Int
.) Because the exact set of base DISs may
vary slightly between compilers, it is recommended that programmers
use the standard DIS macros listed in Section
sec:dis-std
instead. The base form is noted here primarily for completeness.
It would be unbearably tedious to have to write out complete DISs in
every procedure specification, so Green Card supports DIS
functions in much the same way that Haskell provides functions. (The
big difference is that DIS functions can be used in ``patterns'' --
such as %call
statements -- whereas Haskell functions cannot.)
DIS macros allow the programmer to define abbreviations for commonly-occurring DISs. For example:
newtype This = MkThis Int (Float, Float)
%dis this x y z = MkThis (int x) (float y, float z)
Along with the newtype
declaration the programmer can write a
%dis
function definition that defines the DIS function this
in
the obvious manner.
DIS macros are simply expanded out by Green Card before it generates code. So for example, if we write:
%fun f :: This -> This
%call (this p q r)
...
Green Card will expand the call to this
:
%fun f :: This -> This
%call (MkThis (int p) (float q, float r))
...
(In fact, int
and float
are also DIS macros defined in Green
Card's standard DIS prelude, so the %call
line is further expanded
to:
The expansion shown here assumes the GHC version of the standard DIS' are used.
%fun f :: This -> This
%call (MkThis (I# ({int} p)) (F# ({float} q), F# ({float} r)))
...
The fully expanded calls describe the marshalling code in full detail; you can see why it would be inconvenient to write them out literally on each occasion!)
Notice that DIS macros are automatically bidirectional; that is, they can be used to convert Haskell values to C and vice versa. For example, we can write:
%fun f :: This -> This
%call (MkThis (int p) (float q, float r))
%code int a, b, c;
% f( p, q, r, &a, &b, &c);
%result (this a b c)
The form of DIS macro definitions, given in Figure fig:dis-syntax , is very simple. The formal parameters can only be variables (not patterns), and the right hand side is simply another DIS. Only first-order DIS macros are permitted.
THe full power of DIS macros becomes apparent when mapping between a
structured Haskell type a C struct
. For example, to interface
the Haskell ColourPoint
type with the outside world:
data ColourPoint = CP Int Int Colour
data Colour = Red | Green | Blue | ... deriving ( Enum )
for which we want to map it onto the following C structure:
typedef struct CPoint {
int x;
int y;
enum colour c;
} CPoint;
It requires just two DIS macros to capture the mapping between the two:
%dis colourPoint cp =
% declare {CPoint} cp in
% CP (int {%cp.x}) (int {%cp.y}) (colour {%cp.c})
%dis colour c =
% declare {enum colour} c in
% < fromEnum / toEnum > (int x)
Using these, it is then very easy to implement the required interfaces to foreign functions that manipulate coloured points:
%fun translate :: Int -> Int -> ColourPoint -> IO ColourPoint
%call (int xrel) (int yrel) (colourPoint p)
%code p.x += xrel;
% p.y += yrel;
% render(&p);
%result (colourPoint {p})
Note that in this example, the return value is actually the same
structure as the argument value (destructively updated.) It is for
this reason that the p
on the %result
line is quoted as a C
literal - this prevents the declare
clause of the DIS macro from
generating a second (overlapping) declaration of the variable in
C.
How does Green Card use these DISs to convert between Haskell values and C values? We give an informal algorithm here, although most programmers should hopefully be able to manage without knowing the details.
To convert from Haskell values to C values, guided by a DIS, Green Card does the following:
marshall
function. Much the same happens in the other direction, except that Green Card
calls the unmarshall
function in the user-defined DIS case.
DIS Haskell type C type Comments
==============================================================
int x Int int
char c Char char
float f Float float
double d Double double
bool b Bool int 0 for False, 1 for True
addr a Addr (void *) An (immovable) external address
string s String (char *) Persistence not required
in either direction.
foreign x f ForeignObj (void *x),
((void (*)())f) f is the free routine; it
takes one parameter, namely
x, the thing to be freed.
stable x any unsigned int Makes it possible to pass a
Haskell pointer to C, and
perhaps get it back later,
without breaking the
garbage collector.
maybe dis Maybe dis type of dis Converts to and from
Maybe's, with 0 as 'Nothing'
maybeT ce d Maybe dis type of dis Converts to and from 'Maybe's