[m-rev.] for review: add examples of the C data passing conventions

Julien Fischer jfischer at opturion.com
Mon Aug 22 13:59:34 AEST 2022


For review by anyone.

---------------------

Add examples of the C data passing conventions.

samples/c_interface/c_data_passing.m:
     Add a file that contains illustrations of the C data passing conventions
     used by the foreign language interface for various types in Mercury.

samples/c_interface/README.md:
     Include the new file.

Julien.

diff --git a/samples/c_interface/README.md b/samples/c_interface/README.md
index da35e47..03971e7 100644
--- a/samples/c_interface/README.md
+++ b/samples/c_interface/README.md
@@ -7,6 +7,9 @@ using the foreign language interface.
  * [short_example.m](short_example.m) -- A short example of Mercury code
    calling C code.

+* [c_data_passing.m](c_data_passing.m) -- Examples of the C data passing
+  conventions.
+
  * [mercury_calls_c](mercury_calls_c) -- A detailed example of Mercury code
    calling C code.

diff --git a/samples/c_interface/c_data_passing.m b/samples/c_interface/c_data_passing.m
index e69de29..7fe78f4 100644
--- a/samples/c_interface/c_data_passing.m
+++ b/samples/c_interface/c_data_passing.m
@@ -0,0 +1,858 @@
+%----------------------------------------------------------------------------%
+% vim: ts=4 et sw ft=mercury
+%----------------------------------------------------------------------------%
+%
+% This module illustrates the data passing conventions used by Mercury's
+% foreign language interface when passing data to and from C code.
+% These conventions are specified in the Mercury reference manual section
+% 'C data passing conventions'.
+%
+% CONTENTS
+%
+%   * Integers
+%   * Unsigned integers
+%   * Fixed size integers
+%   * Floats
+%   * Characters
+%   * Strings
+%   * Booleans
+%   * Lists
+%   * Data structures containing floats
+%   * Data structures containing 64-bit integers
+%   * Foreign types
+%   * Comparison results
+%   * Foreign types with user-defined equality and comparison
+%   * The I/O state
+%   * Exporting Mercury enumerations to C
+%   * Importing C enumerations into Mercury
+%   * Arrays
+%
+%----------------------------------------------------------------------------%
+
+:- module c_data_passing.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%----------------------------------------------------------------------------%
+%----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module array.
+:- import_module bool.
+:- import_module char.
+:- import_module list.
+:- import_module string.
+
+%----------------------------------------------------------------------------%
+%
+% Integers.
+%
+
+% Mercury's int type corresponds to the C type MR_Integer.
+% MR_Integer is a typedef defined by the Mercury runtime for a signed
+% word-sized integral type.
+
+    % int_add(A, B) = C:
+    %
+    % This function computes the sum of two Mercury ints using C code.
+    %
+:- func int_add(int, int) = int.
+:- pragma foreign_proc("C",
+    int_add(A::in, B::in) = (C::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    C = A + B;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Unsigned integers.
+%
+
+% Mercury's uint type corresponds to the C type MR_Unsigned.
+% MR_Unsigned is a typedef defined by the Mercury runtime for an unsigned
+% word-sized integral type.
+
+    % uint_add(A, B) = C:
+    %
+    % This function computes the sum of two Mercury uints using C code.
+    %
+:- func uint_add(int, int) = int.
+:- pragma foreign_proc("C",
+    uint_add(A::in, B::in) = (C::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    C = A + B;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Fixed sized integers.
+%
+
+% Mercury's fixed size integer types correspond to the exact size integral
+% types declared by the C standard library header stdint.h.
+%
+%    Mercury Type      C Type
+%    ------------      ------
+%    int8              int8_t
+%    int16             int16_t
+%    int32             int32_t
+%    int64             int64_t
+%
+%    uint8             uint8_t
+%    uint16            uint16_t
+%    uint32            uint32_t
+%    uint64            uint64_t
+%
+% Be aware that there are special requirements for manipulating Mercury data
+% structures that contain 64-bit integer types using C code. See the section
+% 'Data structure containing 64-bit integers' below for details.
+
+    % uint32_add(A, B) = C:
+    %
+    % This function computes the sum of two Mercury uint32s using C code.
+    %
+:- func uint32_add(uint32, uint32) = uint32.
+:- pragma foreign_proc("C",
+    uint32_add(A::in, B::in) = (C::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    C = A + B;
+").
+
+    % Return the largest uint64 value.
+    %
+:- func big_uint64 = uint64.
+:- pragma foreign_proc("C",
+    big_uint64 = (A::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    // We could also write: A = UINT64_MAX;
+    A = UINT64_C(18446744073709551615);
+").
+
+%----------------------------------------------------------------------------%
+%
+% Floats.
+%
+
+% Mercury's float type corresponds to the C type MR_Float.
+% MR_Float is a typedef defined by the Mercury runtime.
+% In spf (single-precision) float grades, it is a typedef for C's float type.
+% In other grades, it is a typedef for C's double type.
+%
+% C code can test whether the macro MR_USE_SINGLE_PREC_FLOAT is defined to
+% check if an spf grade is being used.
+
+    % add_floats(A, B) = C:
+    % This function computes the sum of two Mercury floats using C code.
+    %
+:- func add_floats(float, float) = float.
+:- pragma foreign_proc("C",
+    add_floats(A::in, B::in) = (C::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    C = A + B;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Characters.
+%
+
+% Mercury's char type corresponds to the C type MR_Char.
+% MR_Char is a typedef defined by the Mercury runtime for a signed 32-bit
+% integral type.
+%
+% A Mercury char represents a Unicode code point and valid values must be in
+% the range [0, 0x10ffff]. Mercury's foreign language interface does *not*
+% check that characters passed back to Mercury are within this range.
+
+    % Return the Unicode code point "MUSICAL SYMBOL G CLEF" (U+1D11E).
+    %
+:- func treble_clef = char.
+:- pragma foreign_proc("C",
+    treble_clef = (C::out),
+    [promise_pure, will_not_call_mercury, thread_safe],
+"
+    C = 0x1d11e;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Strings.
+%
+
+% Mercury's string type corresponds to the C type MR_String.
+% MR_String is a typedef defined by the Mercury runtime for a pointer to char
+% (i.e. char *).
+%
+% Like in C, Mercury strings are '\0'-terminated. '\0' must *not* occur at any
+% other position in the string. Mercury strings must be aligned on word
+% boundaries.
+%
+% In C grades, Mercury strings use the UTF-8 encoding of Unicode.
+% The foreign language interface does *not* check that strings passed across it
+% contain valid UTF-8. If needed, you must insert such checks yourself. There
+% are two ways to check that a string contains valid UTF-8:
+%
+%     1. In Mercury: use the predicate string.is_well_formed/1.
+%     2. In C: use the function MR_utf8_valid(), which is declared in the
+%        Mercury runtime header runtime/mercury_string.h.
+
+    % Return the length (number of UTF-8 code units) of the given string
+    % using C code.
+    %
+:- func string_length(string) = int.
+:- pragma foreign_proc("C",
+    string_length(S::in) = (L::out),
+    [promise_pure, will_not_call_mercury],
+"
+    L = strlen(S);
+").
+
+    % #include string.h and make its contents visible to foreign_procs in this
+    % module.
+    %
+:- pragma foreign_decl("C", "#include <string.h>").
+
+    % error_to_string(ErrNo) = ErrMsg:
+    %
+    % A Mercury interface to the C standard library function strerror().
+    %
+:- func error_to_string(int) = string.
+:- pragma foreign_proc("C",
+    error_to_string(ErrNo::in) = (String::out),
+    [promise_pure, will_not_call_mercury],
+"
+    char *error_msg;
+    error_msg = strerror((int) ErrNo);
+
+    // Because there is no guarantee that the string returned by strerror() is
+    // word aligned, we must create a word-aligned copy of it on the Mercury
+    // heap. The Mercury runtime provides the macro
+    // MR_make_aligned_string_copy() for doing this.
+    //
+    // When passing strings from C to Mercury, in addition to ensuring that
+    // they are word aligned, it is generally a good idea to copy them on to
+    // the Mercury heap. This is especially true if the returned strings have a
+    // unique inst. This is because some of the standard library operations
+    // might destructively update the string and that will fail if the string
+    // is stored in read-only memory.
+
+    MR_make_aligned_string_copy(String, error_msg);
+").
+
+%----------------------------------------------------------------------------%
+%
+% Booleans.
+%
+
+% Mercury's bool.bool/0 type corresponds to the C type MR_Bool.
+% The data constructors yes/0 and no/0 are available in C using the names
+% MR_YES and MR_NO respectively.
+
+    % int2bool(I) = B:
+    %
+    % B is "yes" if I is non-zero and "no" if I is zero.
+    %
+:- func int2bool(int) = bool.
+:- pragma foreign_proc("C",
+    int2bool(I::in) = (B::out),
+    [promise_pure, will_not_call_mercury],
+"
+    B = (I == 0 ? MR_NO : MR_YES);
+").
+
+%----------------------------------------------------------------------------%
+%
+% Lists.
+%
+
+% Mercury's list.list/1 type corresponds to the C type MR_Word.
+% MR_Word is a typedef declared in the Mercury runtime for an unsigned integral
+% type whose size is the same size as a pointer.
+%
+% The Mercury runtime defines the following function-like macros for
+% manipulating Mercury lists in C code:
+%
+%     MR_bool MR_list_is_empty(MR_Word list);
+%     MR_Word MR_list_head(MR_Word list);
+%     MR_Word MR_list_tail(MR_Word tail);
+%     MR_Word MR_list_empty(void);
+%     MR_Word MR_list_cons(MR_Word head, MR_Word tail);
+%
+% When an element is extracted from a list using the MR_list_head() macro, that
+% element will also have the type MR_Word. How you convert that MR_Word value
+% to the actual element type depends on what the element type is. For most
+% element types you can insert a cast to the appropriate type. If the element
+% type is float, int64 or uint64 you might need to arrange for the element to
+% be unboxed -- see the following two sections for further details. In the
+% following examples, we have lists of int, so adding a cast to MR_Integer will
+% suffice.
+
+    % sum_ints(Is) = Result:
+    %
+    % Result is the sum of the elements in Is.
+    %
+:- func sum_ints(list(int)) = int.
+:- pragma foreign_proc("C",
+    sum_ints(Is::in) = (Result::out),
+    [promise_pure, will_not_call_mercury],
+"
+    MR_Integer sum = 0;
+    MR_Word list = Is;
+
+    while (!MR_list_is_empty(list)) {
+        sum += (MR_Integer) MR_list_head(list);
+        list = MR_list_tail(list);
+    }
+
+    Result = sum;
+").
+
+    % n_triangular_numbers(N) = TNs:
+    %
+    % TNs is the list of the first N triangular numbers where the
+    % n'th triangular number T(n) is defined as:
+    %
+    %    T(n) = n(n + 1) / 2
+    %
+    % If N < 1, then the empty list is returned.
+    %
+:- func n_triangular_numbers(int) = list(int).
+:- pragma foreign_proc("C",
+    n_triangular_numbers(N::in) = (TNs::out),
+    [promise_pure, will_not_call_mercury],
+"
+    MR_Integer next_number;
+
+    // Set TNs to be the empty list.
+    TNs = MR_list_empty();
+
+    while (N > 0) {
+        next_number = N * (N + 1) / 2;
+        // Append next_number to the head of the list TNs.
+        TNs = MR_list_cons(next_number, TNs);
+        N--;
+    }
+").
+
+%----------------------------------------------------------------------------%
+%
+% Data structures containing floats.
+%
+
+% Because the size of a Mercury float might exceed a word, floats contained in
+% Mercury data structures might be boxed. That is, they are passed around as a
+% pointer to a slot on the heap where the actual float is stored.
+%
+% When manipulating Mercury data structures that contain floats in C code,
+% you must account for the possibility that floats are boxed.
+%
+% You cannot cast from values of type MR_Word to MR_Float and vice versa.
+% Instead, you must use the following function-like macros defined by the
+% Mercury runtime:
+%
+%     MR_Float MR_word_to_float(MR_Word);
+%     MR_Word  MR_float_to_word(MR_Float);
+%
+% If (un)boxing of floats is needed, then these macros take care of it.
+% If (un)boxing is not needed, then no overhead is incurred by the use of
+% these macros.
+%
+% In this example we use MR_list_head() to deconstruct the list of Mercury
+% floats. The value returned by MR_list_head() has type MR_Word.
+% Because this value might be a boxed float, we need to use MR_word_to_float()
+% to convert it into a C double or float.
+
+    % sum_float_list(List) = Result:
+    %
+    % Result is the sum of the elements of List.
+    %
+:- func sum_float_list(list(float)) = float.
+:- pragma foreign_proc("C",
+    sum_float_list(Fs::in) = (Result::out),
+    [promise_pure, will_not_call_mercury],
+"
+    MR_Float sum = 0.0;
+    MR_Word list = Fs;
+
+    while (!MR_list_is_empty(list)) {
+        sum += MR_word_to_float(MR_list_head(list));
+        list = MR_list_tail(list);
+    }
+
+    Result = sum;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Data structures containing 64-bit integers.
+%
+
+% As with floats, the size of Mercury int64 and uint64 values might exceed a
+% word. On systems where this is the case, int64s and uint64s contained in
+% Mercury data structures might be boxed.
+%
+% When manipulating Mercury data structures that contain int64s or uint64s in
+% C code you need to account for the possibility that they are boxed.
+%
+% You cannot cast from values of type MR_Word to int64_t and vice versa.
+% Instead, you must use the following function-like macros defined by the
+% Mercury runtime:
+%
+%     int64_t MR_word_to_int64(MR_Word);
+%     MR_Word MR_int64_to_word(int64_t);
+%
+% Similarly, you cannot cast from values of type MR_Word to uint64_t and vice
+% versa. Instead, you must use the following macros:
+%
+%     uint64_t MR_word_to_uint64(MR_Word);
+%     MR_Word MR_uint64_to_word(uint64_t);
+
+    % sum_int64_list(List) = Result:
+    %
+    % Result is the sum of the elements in List.
+    %
+:- func sum_int64_list(list(int64)) = int64.
+:- pragma foreign_proc("C",
+    sum_int64_list(Fs::in) = (Result::out),
+    [promise_pure, will_not_call_mercury],
+"
+    int64_t sum = 0;
+    MR_Word list = Fs;
+
+    while (!MR_list_is_empty(list)) {
+        sum += MR_word_to_int64(MR_list_head(list));
+        list = MR_list_tail(list);
+    }
+
+    Result = sum;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Foreign types.
+%
+
+% In this section we illustrate how to use a type defined in C from Mercury.
+
+    % Here is a C type that we wish to use in Mercury.
+    %
+:- pragma foreign_decl("C", "
+
+    // A C structure representing a vector in 3-dimensional space.
+    //
+    typedef struct {
+        double i;
+        double j;
+        double k;
+    } c_vector;
+").
+
+    % A declaration for the vector/0 type.
+    %
+    % The foreign_type pragma we use below does not act as a type declaration,
+    % so the following abstract type declaration serves that purpose.
+    % When using foreign types we must provide this even if the type is not
+    % exported from its defining module.
+    %
+:- type vector.
+
+    % We use a foreign_type pragma to tell the compiler that the Mercury type
+    % vector/0 is represented in C as a pointer to the c_vector structure we
+    % declared in the foreign_decl pragma above.
+    %
+    % The "can_pass_as_mercury_type" foreign type assertion promises the
+    % compiler that the C representation of the foreign type will fit inside a
+    % word. The compiler uses this information to generate more efficient code.
+    %
+:- pragma foreign_type("C", vector, "c_vector *", [can_pass_as_mercury_type]).
+
+    % vector(I, J, K) = Vector:
+    %
+    % Given the I, J, and K components create a new vector.
+    %
+:- func vector(float, float, float) = vector.
+:- pragma foreign_proc("C",
+    vector(I::in, J::in, K::in) = (Vector::out),
+    [promise_pure, will_not_call_mercury],
+"
+    // The macro MR_GC_NEW() is used to allocate memory using the garbage
+    // collector. It allocates space sufficient for an object of the type named
+    // by the argument.
+    // (See the description in Mercury runtime header runtime/mercury_memory.h
+    // for further details.)
+    Vector = MR_GC_NEW(c_vector);
+    Vector->i = I;
+    Vector->j = J;
+    Vector->k = K;
+").
+
+    % cross_product(A, B) = C:
+    %
+    % C is the cross (vector) product of the vectors A and B.
+    %
+:- func cross_product(vector, vector) = vector.
+:- pragma foreign_proc("C",
+    cross_product(U::in, V::in) = (Result::out),
+    [promise_pure, will_not_call_mercury],
+"
+    Result = MR_GC_NEW(c_vector);
+
+    Result->i = U->j * V->k - U->k * V->j;
+    Result->j = U->k * V->i - U->i * V->k;
+    Result->k = U->i * V->j - U->j * V->i;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Comparison results.
+%
+
+% The Mercury builtin type comparison_result/0 is used describe the result of
+% comparison operations. Its corresponding C type is MR_Comparison_Result.
+% In C, the constants MR_COMPARE_GREATER, MR_COMPARE_LESS and MR_COMPARE_EQUAL
+% can be used to refer to the comparison_result/0 values (>), (<) and (=)
+% respectively.
+
+    % int_compare(Result, A, B):
+    %
+    % Compare the integers A and B using C code.
+    %
+:- pred int_compare(comparison_result::uo, int::in, int::in) is det.
+
+:- pragma foreign_proc("C",
+    int_compare(Result::uo, A::in, B::in),
+    [promise_pure, will_not_call_mercury],
+"
+    if (A > B) {
+        Result = MR_COMPARE_GREATER;
+    } else if (A < B) {
+        Result = MR_COMPARE_LESS;
+    } else {
+        Result = MR_COMPARE_EQUAL;
+    }
+").
+
+%----------------------------------------------------------------------------%
+%
+% Foreign types with user-defined equality and comparison.
+%
+
+% This section builds on the examples presented in the 'Foreign types' and
+% 'Comparison results' sections above.
+
+    % We begin by declaring a new Mercury type named vector3d.
+    %
+:- type vector3d.
+
+    % As in the 'Foreign types' example, we use a foreign_type pragma to tell
+    % the compiler that the Mercury type vector3d/0 is represented in C as a
+    % pointer to a c_vector structure. However, here, we also tell the compiler
+    % that unification (equality) for this type is implemented by the predicate
+    % vector3d_equal/2 and that comparison is implemented by the predicate
+    % vector3d_compare/3.
+    %
+:- pragma foreign_type("C", vector3d, "c_vector *",
+        [can_pass_as_mercury_type])
+        where equality is vector3d_equal,
+              comparison is vector3d_compare.
+
+:- pred vector3d_equal(vector3d::in, vector3d::in) is semidet.
+:- pragma foreign_proc("C",
+    vector3d_equal(A::in, B::in),
+    [promise_pure, will_not_call_mercury],
+"
+    if (A->i == B->i && A->j == B->j && A->k == B->k) {
+        SUCCESS_INDICATOR = MR_TRUE;
+    } else {
+        SUCCESS_INDICATOR = MR_FALSE;
+    }
+").
+
+:- pred vector3d_compare(comparison_result::uo, vector3d::in, vector3d::in)
+    is det.
+
+:- pragma foreign_proc("C",
+    vector3d_compare(Result::uo, A::in, B::in),
+    [promise_pure, will_not_call_mercury],
+"
+     if (A->i < B->i) {
+        Result = MR_COMPARE_LESS;
+     } else if (A->i > B->i) {
+        Result = MR_COMPARE_GREATER;
+     } else if (A->j < B->j) {
+        Result = MR_COMPARE_LESS;
+     } else if (A->j > B->j) {
+        Result = MR_COMPARE_GREATER;
+     } else if (A->k < B->k) {
+        Result = MR_COMPARE_LESS;
+     } else if (A->k > B->k) {
+        Result = MR_COMPARE_GREATER;
+     } else {
+        Result = MR_COMPARE_EQUAL;
+    }
+").
+
+%----------------------------------------------------------------------------%
+%
+% The I/O state.
+%
+
+% The type io.state/0 is what is known as "dummy type". The Mercury compiler
+% does not generate code that passes around values of dummy types.
+% Nevertheless, foreign_proc arguments of type io.state/0 are manifested in the
+% foreign_proc bodies as local variables of type MR_Word.
+%
+% Because the Mercury compiler will emit warnings for foreign_proc arguments
+% that not referred to by the body of the foreign_proc, you must use one of
+% the following approaches to handling io.state/0 arguments.
+
+    % This example illustrates the first (and preferred) approach to dealing
+    % with arguments of type io.state/0 in foreign_procs: ignore them.
+    % The Mercury compiler does not require that foreign_proc arguments whose
+    % name begins with an underscore be referred to in the foreign_proc body.
+    %
+:- pred say_hello(io::di, io::uo) is det.
+:- pragma foreign_proc("C",
+    say_hello(_IO0::di, _IO::uo),
+    [promise_pure, will_not_call_mercury],
+"
+    puts(\"Hello!\\n\");
+").
+
+    % If foreign_proc arguments of type io.state/0 are not ignored then they
+    % will manifest in the foreign_proc body as local variables of the same
+    % names as the arguments.
+    %
+    % Any value assigned to the variable IO in the following block of code will
+    % be ignored. The convention is to assign the initial io.state/0 argument
+    % to the final io.state/0 argument at the end of the foreign_proc body.
+    %
+:- pred say_goodbye(io::di, io::uo) is det.
+:- pragma foreign_proc("C",
+    say_goodbye(IO0::di, IO::uo),
+    [promise_pure, will_not_call_mercury],
+"
+    puts(\"Goodbye!\\n\");
+    IO = IO0;
+").
+
+%----------------------------------------------------------------------------%
+%
+% Exporting Mercury enumerations to C.
+%
+
+% In Mercury, an enumeration is a discriminated union type where none of the
+% data constructors has any arguments. Mercury enumeration types correspond
+% to the C type MR_Integer. Each data constructor of the enumeration is
+% represented using a distinct integer.
+%
+% The choice of the integers assigned to represent each of an enumeration's
+% data constructors is up to the Mercury compiler.
+% To be able to reliably refer to the enumeration's data constructors from C
+% code, you need to give each of them a symbolic name in C.
+% This is done using a foreign_export_enum pragma.
+
+    % An enumeration type.
+    %
+:- type fruit
+    --->     apple
+    ;        orange
+    ;        lemon.
+
+    % This declaration exports the data constructors of the type fruit/0 to C.
+    % If the names of the data constructors are also valid C identifiers,
+    % then by default the same names can be used to refer to them in C.
+    % (We illustrate how this name mapping can be controlled by the programmer
+    % below.)
+    %
+:- pragma foreign_export_enum("C", fruit/0, [], []).
+
+    % The following function illustrates the use of the above exported
+    % enumeration in C code.
+    %
+:- func is_citrus_1(fruit) = bool.
+:- pragma foreign_proc("C",
+    is_citrus_1(F::in) = (Result::out),
+    [will_not_call_mercury, promise_pure],
+"
+    switch(F) {
+        case apple:
+            Result = MR_NO;
+            break;
+
+        case orange:
+        case lemon:
+            Result = MR_YES;
+            break;
+    }
+").
+
+    % We can change how the enumeration data constructor names are mapped to C
+    % using the third argument of the foreign_export_enum pragma. This argument
+    % is a list of foreign_export_enum attributes, each of which affects the
+    % resulting symbolic names in C.
+    %
+    % The "uppercase" attribute causes the compiler to generate C names for
+    % the data constructors by converting all alphabetic characters in their
+    % Mercury name to uppercase.
+    %
+    % The "prefix" attribute attaches the specified prefix to each C name for a
+    % data constructor. The prefix is not affected by other attributes, such as
+    % "uppercase".
+    %
+    % Multiple foreign_export_enum pragmas can be specified for a single
+    % Mercury enumeration type as long as the resulting names generated for
+    % each pragma do not overlap.
+    %
+    % foreign_export_enum pragmas can also be given for non-abstract imported
+    % types. That is, the use of the foreign_export_enum pragma is not
+    % restricted to types defined in the same module as the pragma declaration.
+    %
+:- pragma foreign_export_enum("C", fruit/0, [prefix("fruit_"), uppercase], []).
+
+:- func is_citrus_2(fruit) = bool.
+:- pragma foreign_proc("C",
+    is_citrus_2(F::in) = (Result::out),
+    [will_not_call_mercury, promise_pure],
+"
+    switch(F) {
+        case fruit_APPLE:
+            Result = MR_NO;
+            break;
+
+        case fruit_ORANGE:
+        case fruit_LEMON;:
+            Result = MR_YES;
+            break;
+    }
+").
+
+    % If we need even finer control over the mapping between C and Mercury
+    % names, then we can specify (some of) that mapping manually, as in the
+    % following example.
+    %
+    % Attributes do not affect names that are set manually: the C name
+    % for apple will be "NOT_AN_ORANGE_or_LEMON", not "NOT_AN_ORANGE_OR_LEMON".
+    % Since this mapping does not specify what should be done for the
+    % constructor lemon, its C name will be "LEMON".
+    %
+:- pragma foreign_export_enum("C", fruit/0, [uppercase], [
+    apple  - "NOT_AN_ORANGE_or_LEMON",
+    orange - "NOT_AN_APPLE_or_LEMON"
+]).
+
+%----------------------------------------------------------------------------%
+%
+% Importing C enumerations into Mercury.
+%
+
+% This example illustrates how to use Mercury's foreign_enum pragma to assign
+% the values by which each constructor of a Mercury enumeration is represented
+% in C code. The illustration takes the form of a Mercury interface to the C
+% standard library's setlocale() function.
+
+    % A Mercury enumeration type describing the possible values that can be
+    % passed as the first argument of C standard library function setlocale().
+    %
+:- type locale_category
+    --->    lc_all
+    ;       lc_collate
+    ;       lc_ctype
+    ;       lc_monetary
+    ;       lc_numeric
+    ;       lc_time.
+
+    % #include locale.h and make its contents visible to foreign_enum pragma.
+    %
+:- pragma foreign_decl("C", "#include <locale.h>").
+
+    % We use a foreign_enum pragma to pair each data constructor in the
+    % Mercury enumeration type local_category/0 with a value in C.
+    %
+:- pragma foreign_enum("C", locale_category/0, [
+    lc_all      - "LC_ALL",
+    lc_collate  - "LC_COLLATE",
+    lc_ctype    - "LC_CTYPE",
+    lc_monetary - "LC_MONETARY",
+    lc_numeric  - "LC_NUMERIC",
+    lc_time     - "LC_TIME"
+]).
+
+:- pred setlocale(locale_category::in, string::in,
+    io::di, io::uo) is det.
+
+:- pragma foreign_proc("C",
+    setlocale(Category::in, Locale::in, _IO0::di, _IO::uo),
+    [promise_pure, will_not_call_mercury],
+"
+    setlocale(Category, Locale);
+").
+
+%----------------------------------------------------------------------------%
+%
+% Arrays.
+%
+
+% Mercury's array/1 type corresponds to the C type MR_ArrayPtr.
+% MR_ArrayPtr is a typedef for a pointer to an MR_ArrayType structure.
+% Both of these types are defined in the Mercury runtime header
+% runtime/mercury_types.h.
+%
+% The MR_ArrayType structure has two fields. The first is named "size" and
+% has type MR_Integer. Its value gives the number of elements in the array.
+% The second is named "elements" and is the underlying array of elements.
+% (The actual definition of this second field varies depending on whether a C
+% compiler that supports variable-length arrays is being used or not.)
+
+    % product_array_floats(Array) = Product:
+    %
+    % Product is the product of the elements in Array.
+    %
+:- func product_array_floats(array(float)::array_ui) = (float::out) is det.
+:- pragma foreign_proc("C",
+    product_array_floats(Fs::array_ui) = (Product::out),
+    [promise_pure, will_not_call_mercury],
+"
+    size_t i;
+
+    Product = 1.0;
+    for (i = 0; i < Fs->size; i++) {
+        Product *= MR_word_to_float(Fs->elements[i]);
+    }
+").
+
+%----------------------------------------------------------------------------%
+
+main(!IO) :-
+    SumInts = sum_ints([1, 2, 3]),
+    io.format("sum_ints([1, 2, 3]) = %d\n", [i(SumInts)], !IO),
+
+    TriNums = n_triangular_numbers(5),
+    io.write_string("n_triangular_numbers(5) = ", !IO),
+    io.write_line(TriNums, !IO),
+
+    SumFloatList = sum_float_list([1.0, 2.0, 3.0]),
+    io.format("sum_float_list([1.0, 2.0, 3.0]) = %f\n", [f(SumFloatList)],
+        !IO),
+
+    SumInt64List = sum_int64_list([1i64, 2i64, 3i64]),
+    io.format("sum_int64_list([1i64, 2i64, 3i64]) = %d\n", [i64(SumInt64List)],
+        !IO),
+
+    ProductFloats = product_array_floats(array([1.0, 2.0, 3.0, 4.0])),
+    io.write_string("product_array_floats(array[1.0, 2.0, 3.0, 4.0]) = ", !IO),
+    io.write_float(ProductFloats, !IO),
+    io.nl(!IO).
+
+%----------------------------------------------------------------------------%
+:- end_module c_data_passing.
+%----------------------------------------------------------------------------%



More information about the reviews mailing list