• Aucun résultat trouvé

Syntaxe abstraite,

10.4 Programmes en plusieurs modules

Plutˆot que de mettre tout le texte d’un programme dans un seul fichier, il est pr´ef´erable de le d´ecouper en plusieurs petits fichiers, que l’on compile un par un. Non seulement l’´edition et la recompilation sont facilit´ees, mais surtout on s’autorise alors la r´eutilisation de certains morceaux du programme dans d’autres programmes. Par exemple, les fonctions sur le crayon ´electronique (avance, . . . ) sont susceptibles d’ˆetre

utilis´ees dans bien d’autres programmes que notre syst`eme mini-Logo. On appelle mod- ules ces morceaux de programme suffisamment autonomes pour ˆetre ´eventuellement r´eutilis´es plus tard et programmation modulaire le style de programmation consistant `

a d´ecouper syst´ematiquement les programmes en modules. Nous allons donc d´ecouper le mini-Logo en cinq modules :

crayon le crayon ´electronique : fonctions avance, tourne, . . .

langage le langage de commandes : type ordre, fonction ex´ecute_ordre alex l’analyseur lexical : type lex`eme, fonction lire_lex`eme, . . . asynt l’analyseur syntaxique : fonction analyse_programme, . . . logo le programme principal : la boucle d’interaction.

`

A chaque module correspond un fichier source, qui a le mˆeme nom que le module, avec l’extension .ml. Par exemple, les fonctions du module crayon sont d´efinies dans le module crayon.ml. Le contenu de ces fichiers est r´esum´e figure 10.1.

Noms ext´erieurs

La figure 10.1 montre que nous avons ajout´e `a chaque module des lignes de la forme #open plus un nom de module. Ces lignes indiquent d’o`u proviennent les noms ext´erieurs qu’on utilise dans le fichier sans les y avoir d´efinis. Grˆace `a ces indications, le compilateur sait o`u aller chercher le type et le code compil´e de ces noms ext´erieurs. Il y a deux mani`eres de faire r´ef´erence `a un identificateur ext´erieur. L’une est d’utiliser des noms « qualifi´es », de la forme : nom du module d’origine, suivi de deux caract`eres _ (soulign´e), suivi du nom de l’identificateur. Ainsi, asynt__analyse_programme signifie « l’identificateur analyse_programme d´efini dans le module asynt ».

L’autre mani`ere d’acc´eder `a des identificateurs ext´erieurs est d’introduire des direc- tives #open "module". Cette directive indique au compilateur qu’on veut « ouvrir » (to open, en anglais) le module donn´e en argument. Plus pr´ecis´ement, cette directive dit que si l’on rencontre un identificateur non qualifi´e qui n’est pas d´efini dans le fichier en cours de compilation, il faut le chercher dans le module argument de #open. Par exemple, dans le fichier asynt.ml, apr`es la ligne

#open "langage";;

on peut faire r´ef´erence au type ordre et `a ses constructeurs par des identificateurs sim- ples (Av, Re, . . . ). Sans le #open, il aurait fallu utiliser des noms qualifi´es (langage__Av, . . . ). Plusieurs directives #open dans un fichier donnent ainsi au compilateur une liste de modules o`u aller chercher les identificateurs externes.

Le choix entre ces deux mani`eres de faire r´ef´erence `a un nom ext´erieur est une pure question de style : l’emploi de #open donne des programmes plus compacts et permet de renommer les modules plus facilement ; l’emploi de noms qualifi´es montre plus clairement la structure modulaire du programme.

La biblioth`eque de modules du syst`eme

Il n’y a pas que les programmes de l’utilisateur `a ˆetre d´ecoup´es en modules : la bib- lioth`eque de fonctions pr´ed´efinies du syst`eme Caml Light se pr´esente elle aussi sous la

Programmes en plusieurs modules 185 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.ml #open "crayon";; type nombre = ...;; let flottant = ... ;; type expression = ... ;; let ajoute_nombres = ... ;; let soustrait_nombres = ... ;; let multiplie_nombres = ... ;; let divise_nombres = ... ;; let compare_nombres = ... ;; let rec valeur_expr env = ... ;; type ordre = ... ;;

type proc´edure = ...;;

let proc´edures_d´efinies = ... ;; let d´efinit_proc´edure = ... and d´efinition_de = ... ;; let valeur_enti`ere = ... ;; let rec ex´ecute_ordre env = ...;; type phrase_logo = ... ;;

type programme_logo = ... ;; let ex´ecute_phrase = ... let ex´ecute_programme = ... ;;

Fichier alex.ml type lex`eme = ... ;;

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

let rec analyseur_lexical = ...;;

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;;

forme d’un certain nombre de modules. Par exemple, la fonction sub_string provient du module de biblioth`eque string ; de mˆeme, des op´erateurs comme + et +. ne sont pas enti`erement pr´ed´efinis dans le syst`eme, mais proviennent de modules de biblioth`eque (les modules int et float, respectivement). Certains de ces modules de biblioth`eque (comme int, float et string) sont implicitement « ouverts » au lancement du compi- lateur. Tout se passe comme si on avait mis au d´ebut de tous les fichiers :

#open "int";; #open "float";; #open "string";;

C’est ce qui explique qu’on r´ef´erence directement sub_string dans n’importe quel programme, sans mettre au pr´ealable #open "string" ni devoir utiliser la forme compl`etement qualifi´ee string__sub_string.

D’autres modules de biblioth`eque, d’un emploi moins fr´equent, ne sont pas

«ouverts » automatiquement au d´ebut de chaque compilation. C’est le cas par exemple du module graphics fournissant les commandes graphiques de base. Il faut donc mettre #open "graphics" au d´ebut du fichier crayon.ml, qui fait r´ef´erence `a ces commandes graphiques.

Compilation s´epar´ee

Les modules composant un programme se compilent un par un `a l’aide de la com- mande camlc -c. $ camlc -c crayon.ml $ camlc -c langage.ml $ camlc -c alex.ml $ camlc -c asynt.ml $ camlc -c logo.ml

L’option -c indique au compilateur qu’il ne faut pas essayer de produire un fichier de code ex´ecutable. En d’autres termes, cette option pr´evient le compilateur que le fichier donn´e en argument n’est pas un programme complet, mais seulement un morceau de programme. L’ex´ecution de la commande camlc -c crayon.ml produit deux fichiers : • le fichier crayon.zo, qu’on appelle « fichier de code objet » ; il contient du code compil´e pas encore ex´ecutable, car faisant r´ef´erence `a des identificateurs ext´erieurs ;

• le fichier crayon.zi, qu’on appelle « fichier d’interface compil´ee » ; il contient des informations de typage sur les objets d´eclar´es dans le module crayon : types des identificateurs d´efinis, noms des types concrets d´eclar´es avec leurs constructeurs, etc.

Le fichier d’interface compil´ee crayon.zi sert pour la compilation des modules qui utilisent le module crayon : quand on compile un module contenant #open "crayon" ou un nom qualifi´e de la forme crayon__. . . , le compilateur lit le fichier crayon.zi et y trouve toutes les informations de typage dont il a besoin.

Ce comportement introduit une contrainte sur l’ordre dans lequel on compile les modules : lorsqu’on compile un module, il faut avoir compil´e au pr´ealable tous les mod- ules qu’il utilise, ayant ainsi produit tous les fichiers .zi n´ecessaires `a sa compilation. Dans le cas du mini-Logo, ces contraintes se r´esument par le sch´ema suivant (une fl`eche de A vers B signifie que A utilise B et donc que B doit ˆetre compil´e avant A).

Interfaces de modules 187 logo.ml

asynt.ml

langage.ml alex.ml

crayon.ml

La s´equence de compilation donn´ee ci-dessus v´erifie toutes les contraintes. On a aussi la libert´e de compiler alex.ml plus tˆot, avant crayon ou langage.

´

Edition de liens

Lorsque tous les modules composant le programme ont ´et´e compil´es, il faut lier ensemble leurs fichiers de code objet, obtenant ainsi un fichier ex´ecutable par camlrun. Cette op´eration s’appelle l’´edition de liens et s’effectue en appelant camlc avec la liste des fichiers en .zo `a lier ensemble.

$ camlc -o logo crayon.zo langage.zo alex.zo asynt.zo logo.zo

Comme pour un programme mono-fichier, l’option -o sert `a donner le nom du fichier ex´ecutable `a produire. L’ordre des fichiers .zo sur la ligne a son importance : il doit respecter la mˆeme contrainte que pour l’ordre des compilations, `a savoir qu’un module doit apparaˆıtre avant les modules qui l’utilisent.