Le module format 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'').
Table des matières:
printf
La coupure des lignes repose sur deux concepts:
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 bien (selon la place restante sur la ligne)
-- -- --
De façon général, 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.---[--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:
----- -- -- --
print_break sp
indent
. L'entier indent
fixe l'indentation 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.
Par exemple, en indiquant par [
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:
---[-- -- -- --
print_cut
,
print_as
, force_newline
,
print_flush
, printf
.
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:
(--- (---- (--- ) ) )
format
En écrivant vos fonctions d'impression, suivez les règles simples suivantes:
open_*
et à
close_box
doivent être parenthésés).
print_space ()
). (Il est bien sûr
quelquefois 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.)
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 ()
).
@.
'' termine l'impression en fermant toutes les
boîtes encore ouvertes (print_newline ()
).
@;<n m>
'' émet une indication de coupure la
plus générale, avec ses deux arguments entiers (print_break n m
).
@?
'' vide la queue d'impression (print_flush ()
).
printf "@[<1>%s@ =@ %d@ %s@]@." "Prix TTC" 100 "Euros";; Prix TTC = 100 Euros - : unit = ()
Un exemple plus réaliste est donné plus bas.
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, puis un analyseur lexical et un analyseur syntaxique pour ce langage:
type lambda = | Lambda of string * lambda | Var of string | Apply of lambda * lambda;; (* L'analyseur lexical utilise le module genlex module de la bibliothèque *) #open "genlex";; let lexer = make_lexer ["."; "\\"; "("; ")"];; (* L'analyseur syntaxique, à l'aide de streams *) let rec exp0 = function | [< 'Ident s >] -> Var s | [< 'Kwd "("; lambda lam; 'Kwd ")" >] -> lam and app = function | [< exp0 e; (other_applications e) lam >] -> lam and other_applications f = function | [< exp0 arg; stream >] -> other_applications (Apply (f, arg)) stream | [<>] -> f and lambda = function | [< 'Kwd "\\"; 'Ident s; 'Kwd "."; lambda lam >] -> Lambda (s, lam) | [< app e >] -> e;;
Essayons l'analyseur dans le système interactif:
#let parse_lambda s = lambda (lexer (stream_of_string s));; parse_lambda : string -> lambda = <fun> #parse_lambda "(\x.x)";; - : lambda = Lambda ("x", Var "x")Maintenant, j'utilise le module format pour imprimer les lambda-termes: je suis le squelette récursif de l'analyseur précédent pour écrire l'imprimeur, en insérant ça et là des indications de coupures et des ordres d'ouverture (et de fermeture) de boîtes:
#open "format";; let ident = print_string;; let kwd = print_string;; 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;;On obtient:
print_lambda (parse_lambda "(\x.x)");; \x. x- : unit = ()
(Notez que les parenthèses sont traitées correctement par l'imprimeur
print_lambda
, qui émet le nombre minimum de parenthèses compatible
avec une relecture correcte par l'analyseur syntaxique.)
print_lambda (parse_lambda "(x y) z");; x y z- : unit = () print_lambda (parse_lambda "x y z");; x y z- : unit = ()
Si vous utilisez cet imprimeur pour déboguer avec le système
interactif, déclarez cet imprimeur avec install_printer
,
de telle manière que le système Caml l'utilise pour imprimer les
valeurs de type lambda
:
install_printer "print_lambda";; - : unit = () parse_lambda "(\x. (\y. x y))";; - : lambda = \x. \y. x y parse_lambda "((\x. (\y. x y)) (\z.z))";; - : lambda = (\x. \y. x y) (\z. z)
C'est une bonne méthodologie à suivre quand on utilise la trace du système interactif (en fait, dès que les données manipulées sont un peu complexe, je considère qu'il est indispensable de définir un imprimeur pour obtenir une trace lisible):
trace"lambda";; La fonction lambda est dorénavant tracée. - : unit = () parse_lambda "((\ident. (\autre_ident. ident autre_ident)) \ (\Truc.Truc Truc)) (\machin. (machin machin) machin)";; lambda <-- <abstr> lambda <-- <abstr> lambda <-- <abstr> lambda <-- <abstr> lambda <-- <abstr> lambda <-- <abstr> lambda --> ident autre_ident lambda --> \autre_ident. ident autre_ident lambda --> \autre_ident. ident autre_ident lambda --> \ident. \autre_ident. ident autre_ident lambda <-- <abstr> lambda <-- <abstr> lambda --> Truc Truc lambda --> \Truc. Truc Truc lambda --> (\ident. \autre_ident. ident autre_ident) (\Truc. Truc Truc) lambda <-- <abstr> lambda <-- <abstr> lambda <-- <abstr> lambda --> machin machin lambda --> machin machin machin lambda --> \machin. machin machin machin lambda --> (\ident. \autre_ident. ident autre_ident) (\Truc. Truc Truc) (\machin. machin machin machin) - : lambda = (\ident. \autre_ident. ident autre_ident) (\Truc. Truc Truc) (\machin. machin machin machin)
printf
On utilise la fonction fprintf
et toutes les fonctions
d'impression prennent en argument supplémentaire le formatteur
(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 permet en
outre d'utiliser le format %a
, celui qu'on utilise pour
imprimer un argument de printf
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 doivent
impérativement prendre un formatteur en premier argument). Par exemple
fprintf ppf "(%a)" pr_lambda lampermet d'imprimer l'argument
lam
à l'aide de la fonction
pr_lambda
(et l'on doit avoir pr_lambda :
formatter -> lambda -> unit
).
Voici la fonction d'impression des
lambda-termes à l'aide des formats d'impression à la printf
.
#open "format";; let ident ppf s = fprintf ppf "%s" s;; let kwd ppf s = fprintf ppf "%s" s;; let rec pr_exp0 ppf = function | Var s -> ident ppf s | lam -> fprintf ppf "@[<1>(%a)@]" pr_lambda lam and pr_app ppf = function | 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;; let print_lambda = pr_lambda std_formatter;;On obtient:
print_lambda (parse_lambda "(\x.x)");; \x. x- : unit = ()
Contacter l'auteur Pierre.Weis@inria.fr