Interface with C++

1.Foreign imports and exports

Mathemagix both allows the user to import functionality from C++ template languages and to export Mathemagix functions and classes back to C++. The syntax for importating and exporting functionality is as follows:

foreign cpp import import_body;
foreign cpp export export_body;

The import and export bodies essentially contain dictionaries between C++ names and Mathemagix names of various classes, functions and other variables or constants.

In the case of foreign imports, it is usually necessary to specify options which should be passed to the compiler and to the linker, as well as some include statements or macro definitions which are necessary in order to compile the imported code. This can be done using the following keywords:

cpp_flags :

flags that should be passed to the C++ compiler

cpp_libs :

flags that should be passed to the C++ linker

cpp_include :

C++ files that should be included in order to compile the imported code

cpp_preamble :

macro definitions which are necessary to compile the imported code

A typical example of the use of these keywords occurs at the start of the file basix/int.mmx:

foreign cpp import {
  cpp_flags    "`basix-config --cppflags`";
  cpp_libs     "`basix-config --libs`";
  cpp_include  "basix/int.hpp";
  cpp_preamble "#define int_literal(x) as_int (as_string (x))";
  …
}

The C++ compiler and linker flags are taken to be the results of the shell command basix-config which outputs the appropriate flags as a function of the user's environment. The header file basix/int.hpp contains various utility functions for machine integers which are imported into Mathemagix. The additional macro int_literal is also imported as the constructor of machine integers from literal integers.

2.Importing classes from C++

Inside the body of a foreign cpp import statement, the C++ class my_class can be imported into Mathemagix under the name My_Class using

class My_Class == my_class;

For instance, we may import the class integer as Integer using

class Integer == integer;

Any imported class my_class is required to provide the following standard routines:

bool operator == (const my_class&, const my_class&);
  // equality predicate
bool operator != (const my_class&, const my_class&);
  // inequality predicate
bool exact_eq (const my_class&, const my_class&);
  // predicate for syntactic equality
bool exact_neq (const my_class&, const my_class&);
  // predicate for syntactic equality
bool hard_eq (const my_class&, const my_class&);
  // predicate for hard equality (of pointers for reference counted objects)
bool hard_neq (const my_class&, const my_class&);
  // predicate for hard inequality (of pointers for reference counted objects)
nat hash (const my_class&);
  // a hash code compatible with operator ==
nat exact_hash (const my_class&);
  // a hash code compatible with exact_eq
nat hard_hash (const my_class&);
  // a hash code compatible with hard_eq
syntactic flatten (const my_class&);
  // a flattener for objects of type my_class

In practice, some of these routines can usually be derived from the others. In the file

basix/defaults.hpp

one may find various macros to this effect, such as TRUE_TO_EXACT_IDENTITY_SUGAR.

3.Importing containers from C++ and exportation of categories

In a similar way, a C++ container class my_container<param_1, …, param_n> can be imported as a Mathemagix container class with name My_Container using

class My_Container (P_1: Cat_1, …, P_n: Cat_n) ==
  my_container (P_1, …, P_n);

The parameters of imported C++ containers are necessarily type parameters, and it is required to specify the types of these parameters, which are categories. For instance, in numerix/complex.mmx, the container class complex<R> of complex numbers is imported using

class Complex (R: Ring) == complex R;

In the case when the categories of the parameters are non trivial (e.g. in the above example where Ring is the category of the parameter R), it is necessary to specify a dictionary between the members of the category and their corresponding names in C++.

Indeed, in the C++ implementation of an addition on complex numbers, we add the real and imaginary parts using the C++ operator +. At some place, we have to specify that this C++ operator + corresponds to the Mathemagix operator infix +. This can be done by exporting the category Ring to C++:

foreign cpp export {
  category Ring == {
    convert: Int -> This == keyword constructor;
    prefix -: This -> This == prefix -;
    infix +: (This, This) -> This == infix +;
    infix -: (This, This) -> This == infix -;
    infix *: (This, This) -> This == infix *;
  }
}

This example illustrates the general syntax for the exportation of categories to C++: every field

mmx_function: (S_1, …, S_n) -> D;

in the definition of the category is replaced by a declaration

mmx_function: (S_1, …, S_n) -> D == cpp_function;

where cpp_function is the C++ name corresponding to the function mmx_function.

4.Importing variables and functions from C++

Inside the body of a foreign cpp import statement, one may import a C++ function or variable with name cpp_name into Mathemagix under the name mmx_name using the syntax

mmx_name: Type == cpp_name;

The type Type of the imported function or variable should be specified on the Mathemagix side. For instance, some of the basic operations on strings can be imported using

foreign cpp import {
  prefix #: String -> Int == N;
  postfix []: (String, Int, Int) -> String == postfix ();
  infix *: (String, String) -> String == infix *;
  infix ><: (String, String) -> String == infix *;
  infix <<: (Alias String, String) -> Alias String == infix <<;
}

The standard ports mmin, mmout and mmerr for input, output and errors are imported using

foreign cpp import {
  mmin : Port == mmin;
  mmout: Port == mmout;
  mmerr: Port == mmerr;
}

5.Importing template functions from C++

Template functions can be imported from C++ into Mathemagix using the same syntax as for the importation of ordinary functions, by putting all declarations inside a forall block. For instance, the following basic imported functions on vectors were extracted from basix/vector.mmx:

foreign cpp import {
  forall (C: Type) {
    prefix #: Vector C -> Int == N;
    postfix []: (Vector C, Int) -> C == postfix [];
    postfix []: (Alias Vector C, Int) -> Alias C == postfix [];
    postfix []: (Vector C, Int, Int) -> Vector C == range;
    reverse: Vector C -> Vector C == reverse;
    infix ><: (Vector C, Vector C) -> Vector C == append;
  }
}

Inside the forall block it is possible to impose additional constraints on the parameters using the assume statement. For instance, we may extend the imported functions on vectors as follows:

foreign cpp import {
  forall (C: Type) {
    prefix #: Vector C -> Int == N;
    postfix []: (Vector C, Int) -> C == postfix [];
    postfix []: (Alias Vector C, Int) -> Alias C == postfix [];
    postfix []: (Vector C, Int, Int) -> Vector C == range;
    reverse: Vector C -> Vector C == reverse;
    infix ><: (Vector C, Vector C) -> Vector C == append;
    
    assume (C: Abelian_Group) {
      prefix -: Vector C -> Vector C == prefix -;
      infix +: (Vector C, Vector C) -> Vector C == infix +;
      infix -: (Vector C, Vector C) -> Vector C == infix -;
    }
  }
}

Here Abelian_Group stands for the following category:

foreign cpp export {
  category Abelian_Group == {
    convert: Int -> This == keyword constructor;
    prefix -: This -> This == prefix -;
    infix +: (This, This) -> This == infix +;
    infix -: (This, This) -> This == infix -;
  }
}

6.Exporting to C++

Classes, functions and variables can be exported back to C++ using the same syntax as for importations. For instance, the class Point from the chapter about user defined classes can be exported together with some basic routines as follows

foreign cpp export {
  class Point == point;
  point: (Double, Double) -> Point == keyword constructor;
  postfix .x: Point -> Double == get_x;
  postfix .y: Point -> Double == get_y;
}

In order to use C++ template functions with Point as a parameter, the required operations from the category of that parameter should be exported to C++ as well. For instance, assume that we wish to use the operations and from the previous section on vectors of points. Then we first have to define the abelian group operations on Point:

convert (i: Int): Point == point (as_double i, as_double i);
prefix - (p: Point): Point == point (-p.x, -p.y);
infix + (p: Point, q: Point): Point == point (p.x + q.x, p.y + q.y);
infix - (p: Point, q: Point): Point == point (p.x - q.x, p.y - q.y);

We next have to export these operations to C++, while making sure that the C++ names of the operations correspond to the exported names of the operations of the category Abelian_Group:

foreign cpp export {
  convert: Int -> Point == keyword constructor;
  prefix -: Point -> Point == prefix -;
  infix +: (Point, Point) -> Point == infix +;
  infix -: (Point, Point) -> Point == infix -;
}

Remark 1. In the current implementation of the compiler, the mandatory operations operator ==, operator !=, exact_eq, exact_neq, hard_eq, hard_neq, hash, exact_hash, hard_hash and flatten of Mathemagix-compatible C++ types are not exported to C++. For instance, assuming that the user redefined the default flattening routine for Point, this routine should be exported explicitly to C++ if we want vectors of points to be printed correctly.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License. If you don't have this file, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.