Le module Format des librairies standard de Caml Light et OCaml propose une méthode d'impression enjolivée. Ce module implémente un moteur d'impression qui coupe « bien » les lignes (« bien » signifie à-peu-près ici « automatiquement et quand nécessaire »).
La coupure des lignes repose sur trois concepts:
bh engendre une
nouvelle ligne à l'intérieur de la boîte b,
alors l'indentation de la nouvelle ligne est la somme de:
l'indentation courante de la boîte b
+ l'indentation additionnelle de boîte
définie par la boîte b
+
l'indication additionnelle de coupure définie par l'indication de
coupure bh.
Il y a 4 types de boîtes. (La plus communément utilisée est la boîte « hov », laissez tomber les autres types en première lecture.)
open_hbox) : dans cette boîte les
indications de coupures ne donnent pas lieu à retour à la
ligne.
open_vbox): toute indication de coupure provoque
un retour à la ligne.
open_hvbox) : si c'est possible, toute
la boîte est imprimée sur une seule ligne; sinon toute
indication de coupure provoque un retour à la ligne.
open_box ou
open_hovbox) ou boîte « tassante »: les
indications de coupure sont utilisées pour aller à la ligne
quand il n'y a plus de place sur la ligne courante. Il existe
deux espèces légèrement différentes de boîtes « hov » qui sont
décrites
plus bas. En première
approximation nous confondrons ces deux types de boîtes
« hov » et ne considérerons que celles obtenues avec la
procédure
open_box.
Donnons un exemple. Supposons que nous voulions écrire 10
caractères avant la marge droite (qui indique qu'il n'y a plus de
place sur la ligne courante). Je représente chaque caractère par une
marque -, les ouvertures et fermetures de boîtes sont
indiquées respectivement par [ et
], et b signifie une indication de
coupure (blanc ou « break »).
La sortie "--b--b--" est imprimée comme suit (le symbole b vaut la valeur de la coupure comme expliqué ci-après) :
--b--b--
--b --b --
S'il y a assez de place pour imprimer toute la boîte sur la ligne :
--b--b--
Mais si "---b---b---" ne peut tenir sur la ligne, la sortie est
---b ---b ---
S'il y a assez de place pour imprimer toute la boîte sur la ligne :
--b--b--
Mais si "---b---b---" ne peut tenir sur la ligne, la sortie est
---b---b ---
La première indication de coupure ne donne pas lieu à un retour à la ligne, puisque la ligne n'est pas pleine. La seconde indication de coupure entraîne un retour à la ligne, puisqu'il n'y a plus la place d'imprimer ce qui suit l'indication de coupure. Si la place restante sur la ligne était encore plus courte, la première indication de coupure aurait aussi donné lieu à un retour à la ligne et "---b---b---" aurait été imprimé ainsi:
---b
---b
---
Les indications de coupure sont aussi utilisées pour imprimer
des espaces (si la ligne n'est pas coupée quand l'indication de
coupure est traitée, sinon le retour à la ligne sépare
correctement les éléments à imprimer). Vous donnez une indication
de coupure en appelant
print_break sp indent, où sp est
l'entier qui indique le nombre d'espaces à imprimer.
Donc print_break sp ... signifie imprimer
sp espaces ou aller à la ligne.
Par exemple, si l'on imprime "--b--b--" (où b est
print_break 1 0, ce qui correspond à l'impression
d'un espace), on obtient la sortie suivante :
-- -- --
-- -- --
-- -- --ou, suivant la place restante sur la ligne :
-- -- --
De façon générale, un programme qui utilise
format, n'écrit pas d'espaces lui-même mais émet des
indications de coupure. (Par exemple à l'aide de
print_space () qui est synonyme
de print_break 1 0 et
écrit un espace ou déclenche une coupure de ligne.)
On dispose de deux moyens de fixer l'indentation des lignes :
open_hovbox 1 ouvre une boîte
hovbox dont les lignes seront indentées de 1 par rapport à
l'indentation initiale de la boîte.
Ainsi avec "---[--b--b--b--", on obtient :
---[--b--b
--b--
tandis qu'avec open_hovbox 2, on obtient :
---[--b--b
--b--
Note: le symbole [ n'est évidemment pas visible
sur la sortie écran, je l'écris pour matérialiser l'ouverture de la
boîte d'impression. Ainsi le dernier « écran » est en
fait :
-----b--b
--b--
print_break sp indent. L'entier
indent fixe l'indentation additionnelle de la
nouvelle ligne qui peut être émise par l'indication de
coupure. C'est-à-dire que
indent est ajouté à l'indentation par défaut de la
boîte où la coupure a lieu.
[ l'ouverture
d'une boîte hov 1 (obtenue
par open_hovbox 1),
et par b print_break
1 2,
alors la sortie de "---[--b--b--b--", sera imprimée :
---[-- --
--
--
Les boîtes « hov » se subdivisent en deux catégories au comportement légèrement différent en ce qui concerne les coupures qui interviennent après la fermeture d'une boîte dont l'indentation est différente de la boîte qui l'englobe. On distingue :
open_hovbox): les indications de coupure
sont utilisées pour aller à la ligne quand il n'y a plus de
place sur la ligne courante; il n'y a pas de passage à la ligne
s'il y a assez de place sur la ligne courante.
open_box): très similaire à la boîte
« hov » tassante, les indications de coupure sont également
utilisées pour aller à la ligne quand il n'y a plus de place
sur la ligne courante, mais de surcroît les indications de
coupures qui permettent de mettre en évidence la structure de
boîtes sont effectuées même s'il reste assez de place sur la
ligne courante.
La différence de comportement entre la boîte « hov » tassante et
la boîte « hov » structurelle (ou « box ») est mise en évidence par la
fermeture des boîtes et la fermeture des parenthèses en fin
d'impression: avec la boîte « hov » tassante les boîtes et les
parenthèses sont fermées sur la même ligne (si la place disponible le
permet), tandis qu'avec la boîte « hov » structurelle chaque
indication de coupure produira un saut de ligne. Prenons l'exemple de
la sortie de "[(---[(----[(---b)]b)]b)]" où "b" représente une
indication de coupure sans indentation supplémentaire
(print_cut ()). Ainsi,
si "[" représente l'ouverture de boîtes « hov » tassantes
(open_hovbox), "[(---[(----[(---b)]b)]b)]" est imprimé
ainsi:
(--- (---- (---)))
Si maintenant on remplace les boîtes « hov » tassantes par
des boîtes « hov » structurelles (open_box), chaque
indication de coupure placée avant chaque parenthèse fermante est
susceptible de montrer la structure de boîte et produit donc une
coupure; on obtient alors :
(--- (---- (--- ) ) )
En écrivant vos fonctions d'impression, suivez les règles simples suivantes :
open_* et à
close_box doivent être parenthésés).
print_space ()), à moins que vous ne
vouliez pas que la ligne soit coupée à cet endroit. Par exemple,
imaginez que vous vouliez imprimer une définition OCaml, disons
let rec ident = expression. Vous allez probablement
considérer les 3 premiers blancs comme des « blancs insécables »
et les inclure directement dans une chaîne de caractères, et
écrire la chaîne "let rec " avant l'identificateur
et la chaîne
= après lui; en revanche, l'espace qui suit
le caractère
= doit être une indication de coupure,
puisqu'il est d'usage (et élégant) de couper la ligne à cet
endroit pour indenter la partie expression d'une définition. En
conclusion, il est bien sûr souvent nécessaire d'imprimer des
caractères « espace », ou blancs insécables, mais la plupart du
temps un espace correspond plutôt à une indication de coupure.)
force_newline:
son usage provoque bien une coupure de ligne, mais il provoque aussi une
réinitialisation partielle du moteur d'impression qui déséquilibre tout
le reste de l'impression.
print_newline (), qui vide les tables
de l'imprimeur (et donc termine l'impression). (Notez que le
système interactif le fait également à la fin de chaque phrase
entrée.)
printf
Le module format vous propose une fonction générale de
formattage à la printf. En plus des indications de
format habituelles à la primitive
printf, on dispose dans le format de caractères qui
commandent ouvertures et fermetures de boîtes ainsi que l'émission
d'indications de coupure de ligne.
Les indications spécifiques au moteur d'impression sont toutes
introduites par le caractère @. À peu près toutes les
fonctions du module format peuvent être appelées depuis
un format de printf. Ainsi :
@[ » ouvre une boîte
(open_box
0). On peut préciser le type de la boîte en argument
supplémentaire. Par exemple @[<hov n> est
équivalent à open_hovbox n.
@] » ferme la dernière boîte ouverte
(close_box ()).
@ » émet un espace sécable
(print_space ()).
@, » émet une indication de coupure sans
espace ni indentation supplémentaire en cas de coupure
(print_cut ()).
@;<n m> » émet une indication de
coupure la plus générale, avec ses deux arguments entiers
(print_break n m).
@. » termine l'impression en fermant toutes les
boîtes encore ouvertes
(print_newline ()).
Par exemple
printf "@[<1>%s@ =@ %d@ %s@]@." "Prix TTC" 100 "Euros";;
Prix TTC = 100 Euros
- : unit = ()
Voici un exemple complet : le plus petit exemple non trivial qu'on puisse imaginer, c'est-à-dire le $\lambda-$calculus :)
Le problème est donc d'imprimer les valeurs d'un type concret qui modélise un langage d'expressions qui définissent les fonctions et leur application à des arguments.
D'abord, je donne la syntaxe abstraite des lambda-termes (nous utilisons le système interactif pour évaluer ce code) :
# type lambda = | Lambda of string * lambda | Var of string | Apply of lambda * lambda;;
type lambda = Lambda of string * lambda | Var of string | Apply of lambda * lambda
J'utilise le module format pour imprimer les lambda-termes:
# open Format;;
# let ident = print_string let kwd = print_string;;
val ident : string -> unit = <fun> val kwd : string -> unit = <fun> # let rec print_exp0 = function | Var s -> ident s | lam -> open_hovbox 1; kwd "("; print_lambda lam; kwd ")"; close_box () and print_app = function | e -> open_hovbox 2; print_other_applications e; close_box () and print_other_applications f = match f with | Apply (f, arg) -> print_app f; print_space (); print_exp0 arg | f -> print_exp0 f and print_lambda = function | Lambda (s, lam) -> open_hovbox 1; kwd "\\"; ident s; kwd "."; print_space(); print_lambda lam; close_box() | e -> print_app e;;
val print_exp0 : lambda -> unit = <fun> val print_app : lambda -> unit = <fun> val print_other_applications : lambda -> unit = <fun> val print_lambda : lambda -> unit = <fun>
En Caml Light, remplacez la première ligne par :
#open "format";;
fprintf On utilise maintenant la fonction fprintf et toutes
les fonctions d'impression prennent en argument supplémentaire le
formatteur (c'est l'argument ppf) où l'impression se
produira. Cela généralise les fonctions d'impression qui peuvent
maintenant imprimer sur n'importe quel formateur défini dans le
programme, et cela permet en outre d'utiliser la conversion
%a, celle qu'on utilise pour imprimer un argument de
fprintf avec une fonction d'impression spécialisée
qu'on a préalablement définie dans le programme (ces fonctions
d'impression de l'utilisateur prennent aussi un
formatteur en premier argument).
Voici la fonction d'impression des lambda-termes à l'aide des
formats d'impression à la fprintf.
# open Format;;
# let ident ppf s = fprintf ppf "%s" s let kwd ppf s = fprintf ppf "%s" s;;
val ident : Format.formatter -> string -> unit = <fun> val kwd : Format.formatter -> string -> unit = <fun> # let rec pr_exp0 ppf = function | Var s -> fprintf ppf "%a" ident s | lam -> fprintf ppf "@[<1>(%a)@]" pr_lambda lam and pr_app ppf e = fprintf ppf "@[<2>%a@]" pr_other_applications e and pr_other_applications ppf f = match f with | Apply (f, arg) -> fprintf ppf "%a@ %a" pr_app f pr_exp0 arg | f -> pr_exp0 ppf f and pr_lambda ppf = function | Lambda (s, lam) -> fprintf ppf "@[<1>%a%a%a@ %a@]" kwd "\\" ident s kwd "." pr_lambda lam | e -> pr_app ppf e;;
val pr_exp0 : Format.formatter -> lambda -> unit = <fun> val pr_app : Format.formatter -> lambda -> unit = <fun> val pr_other_applications : Format.formatter -> lambda -> unit = <fun> val pr_lambda : Format.formatter -> lambda -> unit = <fun>
Armés de ces fonctions d'impression générales, les procédures d'impression sur la sortie standard ou la sortie d'erreur s'obtiennent facilement par application partielle:.
# let print_lambda = pr_lambda std_formatter let eprint_lambda = pr_lambda err_formatter;;
val print_lambda : lambda -> unit = <fun> val eprint_lambda : lambda -> unit = <fun>