Format:
(loop for cons on '(a b c d)
do (format t "~a" (car cons))
when (cdr cons) do (format t ", "))
A, B, C, D
NIL affiche tous les éléments de la liste, séparés par une virgule
cons est ici une variable dont les valeurs successives sont
(A B C D), (B C D), (C D), (D)
(format t "~{~a~^, ~}" '(a b c d))
A, B, C, D
NIL même chose, en plus simple, mais moins compréhensible peut-être.
FORMAT supporte trois sortes de formatages tout à fait différents: l'impression des tables de données, l'impression sous forme agréable des s-expressions, et la génération de messages humainement lisibles avec des valeurs introduites dans le texte. Pour les tables de données, c'est un lisp qui remonte au FORTRAN: cet aspect ne sera pas envisagé ici. De même, l'impression des s-expression n'est pas utilisée couramment. On s'intéresse, ici, au 3ème cas.
La fonction FORMAT utilise deux arguments: une destination et une chaîne de caractères qui contient du texte et des directives. les autres arguments additionnels fournissent les valeurs que les directives placent dans la chaîne au bon endroit.
Le 1er argument est T, ou NIL, ou un flux ou une chaîne avec un pointeur de remplissage. T envoie le contenu de la chaîne vers la sortie standard; NIL génère la chaîne et la rend (à la fonction suivante); l'indication d'un flux envoie la chaîne vers ce flux; l'indication d'une chaîne avec pointeur envoie la chaîne formatée vers la fin de la chaîne et le pointeur est ajusté en conséquence.
Le 2ème argument est une chaîne de contrôle en langage propre à FORMAT. Ce n'est pas du lisp. Il est compact, mais peu compréhensif. La plupart des directives placent un argument dans la chaîne de sortie. Certaines n'utilisent pas d'argument (comme ~% qui permet de passer à la ligne). D'autres utilisent plusieurs arguments. Une directive permet de sauter des arguments ou d'utiliser plusieurs fois le même argument.
Les directives de FORMAT:
Une directive est de la forme ~*, où * représente un simple caractère.
Certaines directives prennent des paramètres préfixés après le ~ et séparés par des virgules (par exemple, pour contrôler le nombre de chiffres après la virgule).
(format t "~$" pi)
3.14 formate, par défaut, deux chiffres après la virgule (point décimal ici)
NIL
(format t "~5$" pi)
3.14159 5 chiffres cette fois.
NIL
(format t "~v$" 3 pi)
3.142 le paramètre v prend la valeur d'un argument passé à FORMAT (ici 3)
NIL
(format t "~#$" pi 3 4 5)
3.1416
NIL le paramètre # prend comme valeur le nombre d'arguments additionnels de FORMAT, ici: 4 (pi, 3, 4, 5). Voir plus loin des exemples plus réalistes
(format t "~3f" pi)
3.1 affichage de 3 chiffres (point décimal compris)
NIL
(format t "~,3f" pi)
3.142 affichage de 3 chiffres après la virgule
NIL
(format t "~a" 10)
10
NIL affiche 10 dans la sortie standard et rend NIL
(format nil "~a" 10)
"10" rend la chaîne "10"
(format t "~a" "foo")
foo
NIL affiche foo dans la sortie standard et rend NIL
(format nil "~a" "foo")
"foo" rend la chaîne "foo"
(format t "~a" '(1 2 3))
(1 2 3)
NIL affiche la liste (1 2 3) dans la sortie standard et rend NIL
(format nil "~a" '(1 2 3))
"(1 2 3)" rend la chaîne "(1 2 3)"
~A peut être remplacé par ~S pour générer une sortie lisible par READ. Donc:
(format t "~s" "coucou")
"coucou"
NIL "coucou" au lieu de coucou
(format t "~:s" nil)
()
NIL avec ~:s ou ~:a NIL est transformé en ()
(format t "~a ~%~a" "coucou" "Yann")
coucou
Yann
NIL mise à la ligne avec ~% ou avec ~& (mais ~& ne va pas à la ligne s'il y est déjà:)
(format t "~a ~%~%~a" "coucou" "Yann")
coucou
Yann
NIL le 2ème ~% force à sauter une ligne
(format t "~a~%~&~a" "coucou" "Yann")
coucou
Yann
NIL le 2ème ~& ne va pas à la ligne s'il y est déjà
(format t "~a~&~&~a" "coucou" "Yann")
coucou
Yann
NIL le 2ème ~& ne va pas à la ligne s'il y est déjà
par contre:
(format t "~a~2&~a" "coucou" "Yann")
coucou
Yann
NIL là, une ligne a été sautée
et, là, aussi:
(format t "~a~%~2&~a" "coucou" "Yann")
coucou
Yann
NIL car le 1er ~& est déjà à la ligne
(format t "~a~%~~~&~a" "coucou" "Yann")
coucou
~
Yann
NIL la directive ~~ émet un tilde
(format t "~a~%~6~~&~a" "coucou" "Yann")
coucou
~~~~~~
Yann
NIL la directive ~6~ émet 6 tildes
La directive ~C n'est pas différente de ~A, sauf si on utilise les modificateurs : ou @. Exemples:
(format t "erreur de syntaxe. Ce caractère est inattendu: ~:c" #\a)
erreur de syntaxe. Ce caractère est inattendu: a
NIL signale un caractère inattendu
(format t "erreur de syntaxe. Ce caractère est inattendu: ~:c" #\ )
erreur de syntaxe. Ce caractère est inattendu: Space
NIL signale que l'espace est inattendu (rem: Space est écrit en toutes lettres)
Avec @, la directive émet le caractère dans la syntaxe des caractères lisp:
(format t "erreur de syntaxe. Ce caractère est inattendu: ~@c" #\a)
erreur de syntaxe. Ce caractère est inattendu: #\a
NIL
(format t "erreur de syntaxe. Ce caractère est inattendu: ~@c" #\ )
erreur de syntaxe. Ce caractère est inattendu: #\
NIL là, le caractère espace est invisible (sauf "\" qui le suggère)
La directive ~:@C permettrait d'obtenir, dans certaines implémentations, les combinaisons de touches pour entrer le caractère au clavier (non vérifié).
~D, ~X, ~O, ~B, ~R sont des directives pour formater des nombres entiers:
(format t "~d" 1000000)
1000000 affichage simple du nombre 1000000
NIL
(format t "~:d" 1000000)
1,000,000 affichage par groupes de trois chiffres séparés par une virgule
NIL
(format t "~@d" 1000000)
+1000000 affichage avec un + devant le nombre
NIL
(format t "~:@d" 1000000)
+1,000,000 affichage avec le + et les virgules
NIL
(format nil "~12d" 1000000)
" 1000000" rend le nombre entier dans une chaîne de longueur minimum 12
par défaut, le caractère de remplissage est l'espace
(format nil "~12,'0d" 1000000)
"000001000000" le 2ème paramètre de la directive indique que 0 est le caractère de remplissage
Exemple pratique, une date:
(format nil "~2,'0d-~2,'0d-~4,'0d" 7 5 2014)
"07-05-2014" la date est bien formatée
(format nil "~12,'0,' ,3:d" 1000000)
"0001 000 000" avec le modificateur :, on peut utiliser un 3ème et un 4ème paramètre
1er paramètre: 12, longueur du champ
2ème paramètre: '0, caractère de remplissage
3ème paramètre: ' , caractère de séparation de groupement de chiffres (ici, l'espace)
4ème paramètre: 3, nombre de chiffres par groupe
(format nil "~12,,' ,3:d" 1000000)
" 1 000 000" ici, le caractère de remplissage a été sauté (pour cela: une virgule obligatoire)
(format nil "~x" 1000)
"3E8" rend 1000 en hexadécimal
(format nil "~o" 1000)
"1750" rend 1000 en octal
(format nil "~b" 1000)
"1111101000" rend 1000 en binaire
(format nil "~16r" 1000)
"3E8" la directive ~R permet, à l'aide de son 1er paramètre (ici, 16) de préciser la base
(format nil "~,,' ,4:b" 1000)
"11 1110 1000" 1000 en binaire et avec les chiffres groupés par 4
~F, ~E, ~G, ~$ sont des directives pour formater les nombres à virgule:
(format nil "~f" (* 100 pi))
"314.1592653589793" rend 100pi
(format nil "~,4f" (* 100 pi))
"314.1593" rend 100pi avec 4 chiffres après la virgule
(format nil "~10,4@f" (* 100 pi))
" +314.1593" 10 réserve un champ de 10 chiffres, @ permet d'afficher +
@ n'est pas nécessaire pour un nombre négatif
(format nil "~10,4,3,'*f" (* -100 pi))
"**********" à la place d'un nombre trop grand pour le champ défini,
on affiche des *.
(format nil "~10,4,0,,'#f" (* 100 pi))
"##314.1593" les emplacements vides, à gauche, sont remplis par des #
Remarque: les caractères ajoutés dans FORMAT sont quotés: '* et '#.
La directive F peut donc avoir 5 arguments (avec 4 virgules pour les séparer).
S'il manque un argument entre deux autres, on met 2 virgules.
Si les derniers arguments font défaut, on ne met pas les virgules.
(format nil "~e" (* 100 pi))
"3.141592653589793d+2" rend 100pi en notation scientifique
(format nil "~,4e" (* 100 pi))
"3.1416d+2" de même, avec une partie décimale de 4 chiffres
~$ est la directive monétaire:
(format nil "~$" pi)
"3.14" rend la valeur avec 2 chiffres après la virgule
(format nil "~3,4$" pi)
"0003.142" rend la valeur avec 3 chiffres après la virgule et 4 chiffres avant
(format nil "~3,4,@$" (- pi))
"-0003.142" avec le modificateur @, on affiche le signe - si le nombre est négatif (plus sinon)
ceci est valable pour les directives ~F et ~E
Directives pour la langue anglaise: ~R, ~P, ~(, ~)
(format nil "~r" 1234)
"one thousand two hundred thirty-four"
quand on ne spécifie pas une base dans ~R, les nombres sont convertis en lettres
mais en anglais
(format nil "~:r" 1234)
"one thousand two hundred thirty-fourth"
avec le modificateur :, on obtient un nombre ordinal en lettres
(format nil "~@r" 1234)
"MCCXXXIV" avec le modificateur @, on obtient un nombre en numération romaine. Mais:
(format nil "~@r" 12345) >> erreur
(format nil "~:@r" 1234)
"MCCXXXIIII" avec les deux modificateurs : et @, on obtient l'ancienne numération romaine
(format nil "file~p" 1)
"file" la directive ~P donne la marque du singulier ou du pluriel suivant le nombre passé en argument (ici: singulier)
(format nil "file~p" 5)
"files" ici: pluriel. Mais:
(format nil "file~p" 0)
"files" curiosité: je ne savais pas que zéro fichier entraînait la marque du pluriel en anglais
(format nil "~r file~:p" 1)
"one file" le modificateur : permet de réutiliser l'argument précédemment utilisé
(format nil "~r file~:p" 7)
"seven files" ~R utilise 7 pour obtenir seven et ~:P réutilise 7 pour obtenir le pluriel files
(format nil "~d fichier~:p" 7)
"7 fichiers" parfois on peut adapter pour le français. Ici, on utilise ~D pour éviter le seven
Attention: l'argument 0 donne le pluriel!
(format nil "~r famil~:@p" 1)
"one family" les 2 modificateurs : et @ permettent de traiter des cas particuliers (ici, singulier)
(format nil "~r famil~:@p" 3)
"three families" le pluriel en ies
Remarque: et pour tooth, teeth ...? Tout n'est pas possible!
(format nil "~(~a~)" "Un Joli SOUS-MARIN jaune")
"un joli sous-marin jaune" tout ce qui est entre les 2 directives ~( et ~) est mis en minuscule
c'est à dire, ici, l'argument représenté par la directive ~A
(format nil "~@(~a~)" "un Joli SOUS-MARIN jaune")
"Un joli sous-marin jaune" avec le modificateur @ l'initiale du 1er mot est en majuscule, le reste est mis en minuscule
(format nil "~:(~a~)" "un Joli SOUS-MARIN jaune")
"Un Joli Sous-Marin Jaune" avec le modificateur : l'initiale de tous les mots est en majuscule et le reste en minuscule
(format nil "~:@(~a~)" "un Joli SOUS-MARIN jaune")
"UN JOLI SOUS-MARIN JAUNE" avec les 2 modificateurs : et @ toutes les lettres de tous les mots sont en majuscule
Remarque: ~( et ~) sont utilisables en français.
Formatage conditionnel: ~[ directive crochet ouvrant et ~] directive crochet fermant.
(format nil "~[zéro~;un~;deux~]" 0)
"zéro" les trois clauses, séparées par les ~; , sont choisies en fonction du nombre passé en argument (et ça commence à zéro bien sûr)
(format nil "~[zéro~;un~;deux~]" 1)
"un"
(format nil "~[zéro~;un~;deux~]" 2)
"deux"
(format nil "~[zéro~;un~;deux~]" 3)
"" rend une chaîne vide si la clause correspondante n'existe pas
(format nil "~[zéro~;un~;deux~:;c'est trop~]" 3)
"c'est trop" on peut ajouter une clause de débordement en utilisant le modificateur : dans le dernier séparateur de clause
(format nil "~[zéro~;un~;deux~:;c'est trop~]" 50)
"c'est trop"
Un peu plus compliqué:
(defparameter *list-etc*
"~#[aucun~;~a~;~a et ~a~:;~a, ~a~]~#[~; et ~a~:;, ~a, etc~].")
*LIST-ETC* les clauses sont définies dans la chaîne de caractère *list-etc*
# est un paramètre préfixé de ~[ et représente le nombre de paramètres passés à FORMAT
ce qui donne avec FORMAT:
(format nil *list-etc*)
"aucun."
(format nil *list-etc* 'a)
"A."
(format nil *list-etc* 'a 'b)
"A et B."
(format nil *list-etc* 'a 'b 'c)
"A, B et C."
(format nil *list-etc* 'a 'b 'c 'd)
"A, B, C, etc."
(format nil *list-etc* 'a 'b 'c 'd 'e)
"A, B, C, etc."
Il faut bien comprendre la ligne de clauses:
"~#[aucun~;~a~;~a et ~a~:;~a, ~a~]~#[~; et ~a~:;, ~a, etc~].")
0 ; 1 ; 2 ;3 et + ; 3 ; 4 et + >> rang des clauses
# représente le nombre de paramètres passés dans FORMAT et ce nombre sélectionne la clause:
0 >> "aucun."
1 >> "A."
2 >> "A et B." au delà de 2*, la première directive ~[ affiche "A, B
3 >> "A, B et C." la 2ème directive ~[ affiche la suite de la 1ère: et C."
4 >> "A, B, C, etc." au delà de 3*, la 2ème directive ~[ affiche cette fois: , C, etc."
5 >> même chose.
*: avec le modificateur : du séparateur ~;
Avec le modificateur : de la directive ~[, il ne peut y avoir que 2 clauses: si l'argument est NIL, c'est la 1ère clause
qui est choisie, sinon c'est la 2ème. Rem: toute clause peut-être vide.
(format nil "~:[non~;oui~]" t)
"oui" T peut être toute valeur non NIL
(format nil "~:[non~;oui~]" nil)
"non"
Exemple:
(format nil "~:[non~;oui~]" (format nil "coucou"))
"oui" le 2ème Format rend "coucou" qui est non NIL. Donc le 1er rend "oui"
(format nil "~:[non~;oui~]" (format t "coucou"))
coucou
"non" le 2ème FORMAT envoie "coucou" sur la sortie standard (ici, l'écran), et rend NIL, donc le 1er rend "non"
(defun chat (nb)
(let ((n nb))
(format nil "~a chat~:[s sont~:; est~] sur le mur" n (< n 2))))
CHAT la fonction chat n'est là que pour faire varier le nombre n
(chat 1)
"1 chat est sur le mur" ~a prend la valeur du 1er argument, (< n 2) rend T
(chat 2)
"2 chats sont sur le mur" ou NIL
Rem: 0 chat ne se dit pas en français; on dit aucun chat n'est sur le mur.
D'où une autre définition, où FORMAT travaille sur 3 clauses et où on utilise la directive ~:* qui permet un saut arrière pour récupérer l'argument:
(defun chat* (nb)
(let ((n nb))
(format nil "~[Aucun chat n'est~;~:*~a chat est~:;~:*~a chats sont~] sur le mur." n)))
CHAT* nouvelle définition de chat* avec 3 clauses
(chat* 0)
"Aucun chat n'est sur le mur." la directive ~[ permet de choisir entre 3 clauses suivant la valeur de n
cet argument n n'est utilisé qu'une fois pour la 1ère clause
(chat* 1)
"1 chat est sur le mur." l'argument n est utilisé 2 fois: pour la 2ème clause et pour ~a
d'où la présence de ~:* devant ~a
(chat* 2)
"2 chats sont sur le mur." là aussi, l'argument est utilisé 2 fois
le séparateur ~; et son modificateur : permet d'utiliser la même clause pour
les valeurs de n supérieures à 2
Avec le modificateur @ de la directive ~[, il peut n'y avoir qu'une seule clause:
(format nil "~@[x = ~a ~]~@[y = ~a~]" 11 17)
"x = 11 y = 17"
(format nil "~@[x = ~a ~]~@[y = ~a~]" 11 nil)
"x = 11 "
(format nil "~@[x = ~a ~]~@[y = ~a~]" nil 17)
"y = 17"
(format nil "~@[x = ~a ~]~@[y = ~a~]" nil nil)
""
Itération dans FORMAT: ~{ et ~} l'argument doit être une liste.
(format nil "~{~a, ~}" '(1 2 3))
"1, 2, 3, " l'itération se fait sur chaque élément de la liste jusqu'au dernier et selon
l'expression comprise entre ~{ et ~}
(format nil "~{~a~^, ~}" '(1 2 3))
"1, 2, 3" même chose, mais, ici, la directive ~^ supprime la virgule et l'espace après
le dernier élément de la liste
(format nil "~@{~a~^, ~}" 1 2 3)
"1, 2, 3" avec le modificateur @, les arguments sont pris comme une liste
(format nil "~{~a~#[~; et ~:;, ~]~#[.~]~}" '(1 2 3))
"1, 2 et 3." # représente, ici, le nombre d'itérations
Directives de saut:
(format nil "Le 2ème argument est: ~*~a" 'a 'b)
"Le 2ème argument est: B" la directive ~* saute le 1er argument
(format nil "Le 3ème argument est: ~*~*~a" 'a 'b 'c)
"Le 3ème argument est: C" si elle est répétée 2 fois, elle saute 2 arguments. mieux:
(format nil "Le 3ème argument est: ~2*~a" 'a 'b 'c)
"Le 3ème argument est: C" la directive ~* admet un paramètre préfixé pour répéter le saut (ici: 2)
(format nil "~*~[~a: a~;~a: b~;~a: c~;~a: d~]" 1 3 0 2)
"0: d" la directive ~* semble très spéciale: elle saute le 1er argument, prend la clause correspondant au 2ème argument avec la valeur du 3ème argument et ignore les autres arguments (semble-t-il)
~* a un tout autre comportement avec le modificateur :
(format nil "Le 1er argument est: ~a, je répète ~:*~a." 'a 'b)
"Le 1er argument est: A, je répète A."
la directive ~* avec le modificateur : permet un saut en arrière
elle admet, aussi, un paramètre préfixé pour répéter le saut arrière
(format nil "~r ~:*(~d)" 1)
"one (1)" ~R rend la valeur de l'argument en anglais, puis l'argument est réutilisé avec ~D
(format nil "~:@(~r~) c'est ~:*~[~:@(zéro~)~;~:@(un~)~;~:@(deux~)~] en français." 2)
"TWO c'est DEUX en français." suivant l'argument (0, 1 ou 2), l'une des 3 clauses est choisie (cela rappelle la directive ~#[
de plus, ici, on utilise la directive ~:@( pour mettre en majuscule une partie du texte
(format nil "~{~s~*~^ ~}" '(:a 10 :b 20))
":A :B" ~{...~} permet une itération sur la liste passée en argument
la directive ~* placée à l'intérieur fait sauter l'item suivant (ici: 10, puis 20)
la directive ~^ supprime le dernier espace
(format nil "C'est le ~2@*~a argument." "1er" "2ème" "3ème")
"C'est le 3ème argument." avec le modificateur @, le paramètre préfixé indique l'index basé sur zéro de l'argument sur lequel le saut doit avoir lieu (0 pour le 1er, 1 pour le 2ème, etc)
(format nil "~? ~3,4$" "<~a ~d>" '("foo" 5) 7)
"
la chaîne est un format appliquée aux éléments de la liste qui sont pris comme arguments. ~3,4$ n'est là que pour distinguer les 2 formats.
(format nil "~? ~3,4$" "<~a ~d>" '("foo" 5 8) 7)
"
(format nil "~@? ~3,4$" "<~a ~d>" "foo" 5 7)
"
(format nil "~@? ~3,4$" "<~a ~d>" "foo" 5 8 7)
"
(format nil "~5t~a" "coucou")
" coucou" la directive ~T produit une tabulation de 1 espace
avec le paramètre 5: 5 espaces
Un petit exemple utile pour étudier les fonctions:
(defparameter *ll* '(quote atom eq car cdr cons cond))
*LL* pour ce qui suit:
(defun doc (*ll*)
"Affiche la documentation des fonctions de la liste passée en argument ou NIL s'il ne s'agit pas d'une fonction."
(dolist (x *ll*) (format t "~a: ~a~%~%" x (documentation x 'function))))
(doc *ll*)
QUOTE: QUOTE value
Return VALUE without evaluating it.
ATOM: Return true if OBJECT is an ATOM, and NIL otherwise.
EQ: Return T if OBJ1 and OBJ2 are the same object, otherwise NIL.
CAR: Return the 1st object in a list.
CDR: Return all but the first object in a list.
CONS: Return a list with SE1 as the CAR and SE2 as the CDR.
COND: NIL
NIL
Remarque: COND n'est pas une fonction, c'est une macro.
Nous avons vu, au début de ce chapitre, que le premier argument de format pouvait être un flux.
Certaines variables globales de Common Lisp sont des noms de flux.
Par exemple, *query-io*:
(documentation '*query-io* 'variable)
"query I/O stream"
Utilisons ce flux avec format:
(format *query-io* "~a: " "NOM")
NOM:
NIL
On aurait mis t à la place de *query-io*, on obtenais la même chose.
Mais on peut faire plus:
Définissons une fonction qui propose une invite:
(defun lire-invit (invit)
(format *query-io* "~a: " invit)
(read-line *query-io*))
LIRE-INVIT
Appelons cette fonction en lui donnant comme argument: "NOM".
Read-line attend une saisie. Tapez Titi par exemple.
(lire-invit "NOM")
NOM: Titi
"Titi"
NIL
Vous voilà presque prêt pour construire un formulaire demandant, par exemple:
NOM:
Prénom:
âge:
etc...
Si cela vous intéresse, voyez Pratical Common Lisp de Peter Seibel.
(Il y a un peu de travail à faire...)
Remarque: la fonction LIRE-INVIT ne fonctionne pas dans certaines implémentations.
Il faut alors ajouter la ligne:
(force-output *query-io*)
juste avant (read-line ...), ceci pour s'assurer que Lisp n'attend pas une nouvelle ligne avant l'affichage de l'invite.
Autre chose: vous pouvez créer votre propre flux et lui donner un nom.
Voyez, pour cela, le chapitre: Les FICHIERS et les ENTREES/SORTIES: (input/output:)
Prochainement: LES TYPES * Retour sur LES FERMETURES et LES BLOCKS * LES DOCUMENTATIONS (variables et fonctions)