L'implémentation d'une telle bibliothèque est possible en dehors du compilateur, grâce au support ffi du langage.
La bibliothèque doit être divisée en deux parties : la partie source native d'ocaml, et la partie C runtime. La source OCaml doit contenir la déclaration du type de données, ainsi que la déclaration de toutes les fonctions importées. Par exemple, l'opération d'addition serait :
(** opérations binaires de base sur de longs doubles *)
external add : t -> t -> t = "ml_float80_add"
external sub : t -> t -> t = "ml_float80_sub"
external mul : t -> t -> t = "ml_float80_mul"
external div : t -> t -> t = "ml_float80_div"
dans le code C, la fonction ml_float80_add
devrait être définie, comme décrit dans le manuel d'OCaml :
CAMLprim value ml_float80_add(value l, value r){
float80 rlf = Float80_val(l);
float80 rrf = Float80_val(r);
float80 llf = rlf + rrf;
value res = ml_float80_copy(llf);
return res;
}
Ici, nous convertissons les représentations de temps d'exécution OCaml valeur
en valeurs C natives, utilisons l'opérateur binaire sur elles, et renvoyons une nouvelle valeur OCaml. La fonction ml_float80_copy
fait l'allocation de cette représentation d'exécution.
De même, les implémentations C des fonctions sub
, mul
and div
doivent également être définies. On peut remarquer la similarité dans la signature et l'implémentation de ces fonctions, et les abstraire en utilisant des macros C :
#define FLOAT80_BIN_OP(OPNAME,OP) \
CAMLprim value ml_float80_##OPNAME(value l, value r){ \
float80 rlf = Float80_val(l); \
float80 rrf = Float80_val(r); \
float80 llf = rlf OP rrf; \
value res = ml_float80_copy(llf); \
return res; \
}
FLOAT80_BIN_OP(add,+);
FLOAT80_BIN_OP(sub,-);
FLOAT80_BIN_OP(mul,*);
FLOAT80_BIN_OP(div,/);
Le reste du module OCaml et C devrait suivre.
Il y a de nombreuses options pour encoder le type C float80
en une valeur OCaml. Le choix le plus simple est d'utiliser une chaîne de caractères, et de stocker le long double
brut dedans.
type t = string
Côté C, nous définissons les fonctions pour convertir une valeur OCaml d'avant en arrière en une valeur C :
#include
#include
#include
#include
#define FLOAT80_SIZE 10 /* 10 octets */
typedef long double float80;
#define Float80_val(x) *((float80 *)String_val(x))
void float80_copy_str(char *r, const char *l){
int i;
for (i=0;i
``
Cependant, cette implémentation ne prend pas en charge les fonctions de comparaison polymorphes intégrées dans OCaml Pervasive.compare
, et quelques autres fonctionnalités. Utiliser cette fonction sur le type float80 ci-dessus trompera la fonction de comparaison et fera une comparaison lexicographique de leur contenu.
Supporter ces fonctionnalités spéciales est assez simple. Nous redéfinissons le type OCaml comme abstrait, et changeons le code C pour créer et manipuler des structures personnalisées pour notre float80 :
#include
#include
#include
#include
#include
#include
typedef struct {
struct custom_operations *ops;
float80 v;
} float80_s;
#define Float80_val(x) *((float80 *)Data_custom_val(x))
inline int comp(const float80 l, const float80 r){
return l == r ? 0: (l < r ? -1: 1);
}
static int float80_compare(value l, value r){
const float80 rlf = Float80_val(l);
const float80 rrf = Float80_val(r);
const int llf = comp(rlf,rrf);
return llf;
}
/* implémentation d'autres fonctionnalités ici */
CAMLexport struct custom_operations float80_ops = {
"float80", custom_finalize_default, float80_compare, float80_hash,
float80_serialize, float80_deserialize, custom_compare_ext_default
};
CAMLprim value ml_float80_copy(long double ld){
value res = caml_alloc_custom(&float80_ops, FLOAT80_SIZE, 0, 1);
Float80_val(res) = ld;
return res;
}
Nous proposons ensuite de construire tout cela en utilisant ocamlbuild et un petit script bash.
``