Common Lisp possède un concept appelé The Printer, qui regroupe toutes les fonctions permettant d'écrire des représentations textuelles de données Lisp. L'une de ces fonctions est format, qui permet au programmeur de mettre en forme les données comme il le souhaite.
Les capacités de format, et plus généralement du Printer de Lisp, forment un vaste sujet ; on se contentera ici de décrire les bases de l'utilisation de format.
La fonction format prend au moins deux arguments : un flux vers lequel imprimer et une chaîne de contrôle qui définit quoi imprimer et comment.
Les flux sont un concept à part entière et seront décrits ailleurs. Mais pour l'instant, tout ce qu'il faut savoir, c'est que l'impression se fait vers le flux et qu'il existe deux valeurs spéciales pour cet argument : t et nil. t signifie qu'il faut imprimer vers la sortie standard (en réalité, le flux auquel la variable *standard-output* est liée), et nil signifie qu'il faut créer une chaîne et la renvoyer.
À noter que format s'évalue toujours à nil, sauf dans ce dernier cas où nil est passé comme flux. C'est une façon courante de faire simplement de l'interpolation ou de la construction de chaînes.
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
La chaîne de contrôle peut contenir des « directives de format » qui donnent à format des instructions sur ce qu'il faut imprimer et sur la manière d'interpréter les autres paramètres qu'on lui passe. D'une certaine façon, la chaîne de contrôle est un programme que format va interpréter et exécuter. Toutes les directives commencent par ~ et sont suivies d'un caractère (par exemple ~A, ~D, ~&). La casse du caractère n'a pas d'importance.
La plupart des directives de format ne font qu'interpoler dans la sortie un argument passé à format. On dit alors qu'elles « consomment » cet argument. Certaines directives ne consomment aucun argument, ou en consomment plusieurs. Une directive permet même de se déplacer dans la liste des arguments ou d'en sauter.
Une directive de format très générale est ~A. Elle imprime une version « lisible par un humain » des données.
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
Une autre directive de format utile est ~S. Elle imprime une version « lisible par une machine » des données. C'est très utile si l'on veut pouvoir interpréter la sortie produite comme des données Lisp. Pour beaucoup de choses, le résultat sera identique à celui de ~A, mais note que pour les chaînes de caractères, par exemple, la sortie inclura les guillemets (échappés lorsqu'ils sont inclus dans une autre chaîne), de sorte que la valeur puisse être relue comme la chaîne d'origine.
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
Deux autres directives très utiles sont ~% et ~&. La première produit un caractère de nouvelle ligne, tandis que la seconde produit une « ligne fraîche ». La différence est que ~% produit toujours une nouvelle ligne, alors que ~& n'en produit une que si l'on n'est pas déjà au début d'une ligne. Ni l'une ni l'autre ne consomme d'argument.
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
Enfin, ~~ est une directive simple qui produit un ~ littéral.
Comme presque tout le monde qui a un travail, Layla l'alien Lisp doit parfois rédiger des rapports pour informer les autres. Elle te demande si tu peux l'aider à formater les données dont elle dispose pour ces rapports.
La première chose dont Layla a besoin, c'est une fonction : format-quarter-value qui prend deux paramètres : le trimestre et la valeur, et qui les formate pour un rapport. Elle doit renvoyer une string.
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
Layla te remercie pour la fonction format-quarter-value et dit qu'elle sera très utile dans la prochaine fonction nécessaire.
Le rapport suivant doit montrer le trimestre et la valeur de 2 trimestres, imprimés sur deux lignes.
De plus, la sortie doit être écrite dans le flux fourni en premier argument.
Par exemple : (format-two-quarters t "last" 3.14 "this" 0) produira ce qui suit sur la sortie standard :
The value last quarter: 3.14
The value this quarter: 0
Remarque les lignes vides qui l'entourent.
Layla a oublié de te dire que les aliens Lisp aiment voir les valeurs d'une manière qui se lise comme des données Lisp. Il leur faut donc une nouvelle fonction dont le résultat puisse se lire comme des données Lisp (là encore, vers le flux indiqué) :
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"
Inscris-toi sur Exercism pour apprendre et maîtriser Common Lisp avec 27 concepts92 exercices, et un vrai mentorat humain, le tout gratuitement.