• Aucun résultat trouvé

Syntaxe abstraite,

10.5 Interfaces de modules

Souvent, un module contient des d´efinitions `a usage interne, qui ne sont pas cens´ees ˆetre employ´ees `a l’ext´erieur du module. Dans le module crayon, par exemple, l’enregistrement `a champs mutables qui contient l’´etat courant de la tortue n’est pas cens´e ˆetre modifi´e directement par les utilisateurs de ce module ; les utilisateurs sont suppos´es passer par l’interm´ediaire des fonctions avance, tourne, . . . On peut s’imposer de respecter cette convention soi-mˆeme ; mais le syst`eme est capable de la garantir, si on lui demande explicitement de « cacher » certains des identificateurs d´efinis par le module, ce qui les rend ainsi inaccessibles depuis l’ext´erieur du module.

Pour ce faire, il faut ´ecrire une interface au module. L’interface d’un module contient des d´eclarations pour tous les identificateurs du module que l’on veut rendre visibles de l’ext´erieur ; les identificateurs d´efinis dans le module mais non d´eclar´es dans l’interface seront automatiquement cach´es. L’interface d’un module r´eside dans un fichier ayant le mˆeme nom que le module mais avec l’extension .mli. Par opposition, le fichier avec l’extension .ml qui contient les d´efinitions du module s’appelle l’impl´ementation du module. Par exemple, voici le fichier d’interface du module crayon :

value vide_´ecran: unit -> unit and fixe_crayon: bool -> unit and tourne: float -> unit and avance: float -> unit;;

Comme on le voit, les d´eclarations d’identificateurs sont introduites par le mot-cl´e value et consistent en le nom de l’identificateur suivi de son type. Les interfaces peuvent aussi

contenir des d´efinitions de types et d’exceptions. Par exemple, l’interface du module alex rend public le type des lex`emes, en plus de la fonction d’analyse lexicale.

Fichier alex.mli type lex`eme =

| Mot of string | Symbole of char | Entier of int | Flottant of float;;

value analyseur_lexical: char stream -> lex`eme stream;;

On trouvera en figure 10.2 la nouvelle structure du mini-Logo, une fois qu’on a ajout´e des interfaces `a tous les modules (sauf le module principal logo, qui ne d´efinit rien de toute fa¸con). Remarquez que si un type est d´efini dans l’interface d’un module, il est automatiquement d´efini dans l’impl´ementation du module ; il ne faut donc pas recopier sa d´efinition dans cette impl´ementation.

Compilation des interfaces

Les fichiers d’interface se compilent exactement comme les fichiers d’impl´ementation, par la commande camlc -c. Exemple :

$ camlc -c crayon.mli

L’ex´ecution de cette commande cr´ee un fichier crayon.zi contenant les d´eclarations de crayon.mli sous une forme compil´ee. Comme dans le cas des modules sans interface (section 10.4), le fichier crayon.zi est consult´e par le compilateur lors de la compi- lation des modules qui font r´ef´erence au module crayon. De plus, lorsqu’on compile l’impl´ementation crayon.ml,

$ camlc -c crayon.ml

le compilateur v´erifie la coh´erence de l’impl´ementation avec l’interface compil´ee crayon.zi, c’est-`a-dire qu’il v´erifie que tous les identificateurs d´eclar´es dans l’interface sont bien d´efinis dans l’impl´ementation et qu’ils ont bien le type annonc´e dans l’interface. C’est en cela que la compilation d’un module avec interface explicite diff`ere de la compilation d’un module sans interface : si l’interface .mli existe alors le .zi est construit par compilation du .mli et la compilation de l’impl´ementation .ml consulte le .zi pour v´erifier la coh´erence ; si l’interface .mli n’existe pas, alors la compilation de l’impl´ementation .ml cr´e´e un .zi qui rend public tout ce que l’impl´ementation .ml d´efinit. Il en d´ecoule deux contraintes sur l’ordre dans lequel on effectue les compilations : d’une part, l’interface explicite mod.mli doit ˆetre compil´ee avant tous les fichiers (.ml et .mli) qui font r´ef´erence au module mod ; d’autre part, l’interface explicite mod.mli doit ˆetre compil´ee avant l’impl´ementation mod.ml. Dans le cas du mini-Logo, il en d´ecoule les contraintes suivantes :

crayon.ml langage.ml asynt.ml logo.ml alex.ml

Interfaces de modules 189

Fichier crayon.mli value vide_´ecran: unit -> unit

and fixe_crayon: bool -> unit and tourne: float -> unit and avance: float -> unit;;

Fichier crayon.ml #open "graphics";; let round x = ... ;; type ´etat = ... ;; let crayon = ... ;; let avance d = ... ;; let pi_sur_180 = ... ;; let tourne angle = ... ;; let avance d = ... ;;

let couleur_du_trac´e = ... ;; let couleur_du_fond = ... ;; let z´ero_x = ... ;;

let z´ero_y = ... ;;

let vide_´ecran () = ... ;; Fichier langage.mli type nombre = ... ;;

type expression = ... ;; type ordre = ... ;; type proc´edure = ...;; type phrase_logo = ... ;; type programme_logo = ... ;; value ex´ecute_phrase:

phrase_logo -> unit and ex´ecute_programme:

programme_logo -> unit;; Fichier langage.ml #open "crayon";; let flottant = ... ;; let ajoute_nombres = ... ;; (* ... *)

let rec valeur_expr env = ... ;; let proc´edures_d´efinies = ... ;; let d´efinit_proc´edure = ... and d´efinition_de = ... ;;

let rec ex´ecute_ordre env = ...;; let ex´ecute_phrase = ...

let ex´ecute_programme = ... ;;

Fichier alex.mli type lex`eme = ... ;; value analyseur_lexical:

char stream -> lex`eme stream;; Fichier alex.ml

let rec saute_blancs = ... ;; let rec lire_entier = ... ;; let rec lire_d´ecimales = ... ;; let rec lire_mot position = ...;; let lire_lex`eme = ... ;;

let rec analyseur_lexical = ...;; Fichier asynt.mli

value analyse_phrase:

alex__lex`eme stream -> langage__phrase_logo and analyse_programme:

alex__lex`eme stream -> langage__programme_logo;;

Fichier asynt.ml #open "langage";;

#open "alex";;

let rec analyse_programme = ... and analyse_phrase = ...

and param`etres = ... and ordre = ... and liste_d’ordres = ... and suite_d’ordres = ... and nombre = ... and expression_simple = ... and expression = ... and reste_de_l’expression = ... and liste_d’expressions = ... ;; Fichier logo.ml #open "langage";; #open "alex";; #open "asynt";; let flux_d’entr´ee = ... in let flux_lex`emes = ... in while true do

... done;;

Remarquez que les fichiers d’impl´ementation (.ml) sont compilables dans n’importe quel ordre : si un module A utilise un module B, on peut tr`es bien compiler l’impl´ementation de A avant l’impl´ementation de B ; il suffit que l’interface de B ait d´ej`a ´et´e compil´ee. C’est le cas dans la s´equence de compilation ci-dessous.

$ camlc -c langage.mli $ camlc -c crayon.mli $ camlc -c langage.ml $ camlc -c alex.mli $ camlc -c asynt.mli $ camlc -c logo.ml $ camlc -c asynt.ml $ camlc -c crayon.ml $ camlc -c alex.ml

On a choisi d’´ecrire et de compiler d’abord les impl´ementations de langage et de logo, qui repr´esentent le cœur du syst`eme, et de repousser `a plus tard l’´ecriture de asynt.ml, alex.mlet crayon.ml. Plus g´en´eralement, l’introduction d’interfaces explicites pour les modules permet de se lib´erer du style d’´ecriture des programmes strictement ascendant (bottom-up, en anglais) que nous avons utilis´e jusqu’ici. Par la suite, nous utiliserons des interfaces `a chaque fois que nous avons besoin d’un module de fonctions auxiliaires, dont nous pr´ef´erons cependant repousser l’impl´ementation `a plus tard.