L'implémentation d'une telle bibliothèque est possible en dehors du compilateur, grâce à la fonction ffi le soutien de la langue.
La bibliothèque doit être divisée en deux parties : la partie source native ocaml, et la partie runtime C. 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'ajout serait :
(** basic binary operations on long 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, le ml_float80_add
doit être définie, comme décrit dans le manuel 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 l'OCaml value
en valeurs natives du C, utilise l'opérateur binaire sur celles-ci et renvoie une nouvelle valeur OCaml. ml_float80_copy
effectue l'allocation de cette représentation d'exécution.
De même, les implémentations C de sub
, mul
y div
doivent être définies là aussi. On peut remarquer la similitude de la signature et de l'implémentation de ces fonctions, et les rendre abstraites grâce à l'utilisation de 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 existe de nombreuses possibilités quant à la manière d'encoder les float80
C en une valeur OCaml. Le choix le plus simple est d'utiliser une chaîne de caractères, et d'y stocker la valeur brute long double
.
type t = string
Du côté du C, nous définissons les fonctions permettant de convertir une valeur OCaml en une valeur C et vice-versa :
#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/misc.h>
#include <caml/memory.h>
#define FLOAT80_SIZE 10 /* 10 bytes */
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<FLOAT80_SIZE;i++)
r[i] = l[i];
}
void store_float80_val(value v,float80 f){
float80_copy_str(String_val(v), (const char *)&f);
}
CAMLprim value ml_float80_copy(value r, value l){
float80_copy_str(String_val(r),String_val(l));
return Val_unit;
}
Cependant, cette implémentation ne prend pas en charge les fonctions de comparaison polymorphes intégrées dans OCaml. Pervasive.compare
et quelques autres caractéristiques. L'utilisation de cette fonction sur le type float80 ci-dessus induira la fonction de comparaison en erreur en lui faisant croire que les valeurs sont des chaînes de caractères, et effectuera une comparaison lexicographique sur leur contenu.
La prise en charge de ces fonctions spéciales est pourtant assez simple. Nous redéfinissons le type OCaml comme abstrait, et modifions le code C pour créer et gérer des structures personnalisées pour notre float80 :
#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/misc.h>
#include <caml/memory.h>
#include <caml/custom.h>
#include <caml/intext.h>
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;
}
/* other features implementation here */
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 le tout en utilisant ocamlbuild et un petit bash script.