Re: Jeux de caractères, notamment UTF- 8

Pascal Pignard <[email protected]> Tue, 15 Dec 2020 22:01:16 +0100
Newsgroups gmane.comp.lang.ada.france
Message-ID <[email protected]>
Bonjour Thomas, merci pour ton message copieux :-), mes réponses ci-dessous.

> Le 8 déc. 2020 à 01:22, Thomas De Contes <[email protected]> a écrit :
> 
> Le 9 sept. 2020 à 11:36, Pascal Pignard a écrit :
> 
>> Le 8 sept. 2020 à 19:34, Thomas De Contes <[email protected]> a écrit :
> 
>>> 1
>>> quand je faisais des sites web, à un moment donné je me suis penché sur la question du "meilleur" jeu de caractères à utiliser
> 
>> Latin 1 est celui retenu par Ada pour le type Character et donc String.
> 
> ça me parait parfaitement logique, String / Wide_String / Wide_Wide_String étant construits dans la continuité, et alignés sur Unicode "brut" sans aucun encodage
> 
>> UTF-8 est plus utilisé aujourd'hui pour Internet bien que Windows et macOS utilise UTF-16.
> 
> (pour moi ce qui serais le plus logique c'est qu'on mette UTF-8 partout,
> dans quels cas UTF-16 est il mieux ?)

L'UTF-16 permet d'indexer directement chaque caractère et la longueur d'une chaine est directement déduit de la taille en mémoire (1 caractère = 2 octets) ce que ne permet pas l'UTF-8 puisque certaines représentation de caractères tiennent sur plusieurs octets.

>> Gnoga (https://sourceforge.net/p/gnoga/) par exemple utilise le Latin 1 pour tout ce qui est Ada et UTF-8 pour tout ce qui est Web / Javascript.
> 
> je ne comprend pas très bien :
> comment faites vous vos littéraux par exemple ? ils sont dans le code ada mais destinés aux interfaces ...

Il y a une conversion à l'émission des données sur la websocket Javascript et aussi à la réception.

>>> 2
>>> utilisation de gnat
>>> 
>>> j'ai un peu de mal à comprendre comment ça marche :
>>> https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gnat_ugn/Character-Set-Control.html
>>> 
>>> par exemple, si je met les options "-gnati5 -gnatWs",
>>> concrètement, comment gnat va t il lire mon code ?
>>> avec Cyrillic pour les identifiants et Shift/JIS pour les littéraux de chaines de caractères ?
>>> et si j'ai bien compris, dans les commentaires, tous les caractères non-ASCII sont autorisés tout le temps (sauf NEL) ?
>>> 
>>> (y a t il autre chose que identifiants / littéraux / commentaires, où on écrit potentiellement comme on veut ?)
>> 
>> Voir l'article sur les accentuations :
>> https://blady.pagesperso-orange.fr/a_savoir.html#accents
> 
> merci :-)
> (souhaites tu que je te rapportes les coquilles, ou c'est un document fait une fois pour toutes auquel tu ne touches plus ?)

Tes commentaires sont les bienvenus, certains articles sont anciens mais je les rafraîchis bien volontiers :-)

> ça me confirme ce que j'ai cru comprendre en lisant la doc (en gros le point 3), et que le mainteneur de rapid a eu raison de me remonter les bretelles quand j'ai fait comme dans l'exemple à ne pas faire ;-)
> 
> mais ça ne répond pas complètement à cette question (point 2), puisque ça ne parle pas de -gnati

Ne pas confondre -gnati pour changer le jeu de caractère standard et -gnatW pour définir l'encodage étendu, voir :
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gnat_ugn_unw/Character-Set-Control.html#Character-Set-Control

> 3
>>> ce que je crois avoir compris, par contre, c'est comment ada et gnat envisagent UTF-8 :
>>> 
>>> 
>>> les auteurs ont déjà fait plusieurs pas en direction d'unicode et UTF-8, notamment avec le paquetage Ada.Strings.UTF_Encoding (A.4.11), mais ne sont pas encore arrivés au bout
> 
> 
>>> du coup, si on veut bénéficier du potentiel d'unicode, avec la possibilité d'utiliser n'importe quel caractère, sans avoir besoin de revenir sur aucun réglage le jour où l'envie nous prend (ou le besoin survient),
>>> on doit utiliser l'option -gnatW8 combinée avec Standard.Wide_Wide_String
>>> 
>>> ce qui implique :
>>> 
>>> - quand on n'utilise que de l'ASCII (l'immense majorité du temps) ça prend 4 fois plus de mémoire que nécessaire
>>> 
>>> - ça dois faire 2 conversions inutiles (puisqu'elles sont symétriques) :
>>> - le compilateur doit convertir l'UTF-8 en Wide_Wide_String
>>> - le programme doit convertir le Wide_Wide_String en UTF-8, avec Ada.Strings.UTF_Encoding.Wide_Wide_Strings.Encode, pour pouvoir le passer à GtkAda
>> 
>> Essaye d'utiliser Pango comme fait dans Gnoga.
> 
> merci pour l'astuce :-)
> peux tu me guider stp ?
> 
> j'ai trouvé le code source ici :
> https://sourceforge.net/p/gnoga/code/ci/dev_1.5/tree/src/
> 
> mais, dans quels fichiers faut il regarder pour trouver comment il utilise Pango ?

Je voulais plutôt dire :
- utilise les String_XXX Ada pour la partie non graphique de ton programme
- réalise les conversions en UTF-8 lors des appels aux API de Pango.

> 4
>>> l'idéal, ça serais de pouvoir écrire un programme en UTF-8, y compris des littéraux de chaines de caractères, et que ça soit interprété directement sous la forme Ada.Strings.UTF_Encoding.UTF_8_String, sans aucune conversion
>>> (par exemple pour qu'on puisse le passer directement à GtkAda)
>>> 
>>> - est ce que dans les révisions futures, notamment la prochaine, vous continuez à vous rapprocher de ça ?
>>> 
>>> - y a t il un moyen de faire ça proprement des aujourd'hui ?
>>> (idéalement quelque chose qui pourrais être accepté dans le code de gnat lui-même :-) bon là je crois que je rêve ;-) )
>> 
>> Que penses-tu de la proposition UXString:
>> https://sourceforge.net/p/gnoga/code/ci/dev_1.6/tree/deps/uxstrings/
> 
> 
> bon, tu me poses la question "Qu'en penses-tu" et moi j'ai bien envie d'y répondre ;-)

merci :-)

> alors pour découper ma réponse, pour la rendre plus lisible (j'espère), je recommence une numérotation (sans lien avec la précédente)
> 
> je trouve que cette proposition n'est pas parfaitement appropriée en l'état, d'après mes connaissances sur Ada et autour,
> mais il y a plein de choses intéressantes :-)
> 
> (mais au fait, est ce que c'est pour l'instant seulement une proposition pour une amélioration future du langage, ou est ce que c'est quelque chose qui pourrais déjà marcher avec Ada 2012, des que l'implémentation sera terminée, avec par exemple les "aspects", que je ne maitrise pas encore ?)

La spécification utilise des fonctionnalités qui seront présentes dans Ada 202x utilisables par anticipation dans GNAT Community 2020.

> (j'ai écrit le point 1 avant de voir le début d'implémentation, au point 5,
> je croyais que tu proposais quelque chose d'"assez statique", pleinement intégré au langage, géré automatiquement par le compilateur (en quelques sortes en remplacement de UTF_8_String avec toutes les mêmes fonctionnalités que String),
> du coup je suis peut-être un peu à coté de la plaque - tant pis, je le laisse quand même)
> 
> 1
> en fait, ce que tu proposes me semble ressembler plus à ce que fait Caml avec les pointeurs, c'est à dire qu'il les gère automatiquement pour que le programmeur n'ait pas à s'en occuper
> 
> alors en fait, à l'origine j'aurais préféré qu'Ada gère les pointeurs comme Caml, puisque je ne fais que de simples applications, je n'ai jamais besoin d'aller dans le détail (jusqu'à maintenant), donc ça aurais été plus sécurisant
> 
> mais puisque programmer dans le détail est un avantage d'Ada, il faut le garder ! et je ne sais pas si ta proposition est suffisamment compatible pour être acceptée
> 
> je pense qu'au moins il faudrait que tu précises avec suffisamment de détails ce que tu attends comme comportement pour la gestion interne dans certains cas :
> - si on a une variable UXString dont la représentation interne utilise un jeu de caractères restreint (Latin-1 ou BMP), et si on ajoute (par insertion, substitution, concaténation, ...) un caractère sortant du jeu de caractères courant, comment le programme est il censé gérer cette situation ?

C''est transparent pour l'utilisateur, l'objet UXString s'adapte.

> - si à l'inverse, on supprime le dernier caractère d'un jeu de caractères "étendu", de sorte que le nouveau jeu de caractères optimal devient plus petit que le jeu de caractères courant ? est ce qu'on doit immédiatement tout reconvertir dans l'implémentation du jeu de caractères optimal, ou est ce qu'on attend un peu de voir si ça dure ou pas, par exemple ?

C'est toute la subtilité, certaines implémentations pourront être grossières d'autres plus intelligentes en s'adaptant au comportement de l'utilisateur.
C'est pourquoi je ne présente que la partie spécification, tout un chacun peut proposer son implémentation.
J'en fournis tout de même une mais pas très optimisée comme preuve du concept.

> 2
> je trouve très intéressante l'énumération des 3 possibilités que t'as faite vers le début :-)
> 
> - la 3ème, que t'as choisie, j'espère avoir correctement expliqué pourquoi ça n'est pas ma préférée
> 
> - la 2ème, comme je l'écris à Jean-Pierre, ben je trouve qu'un compromis qui fait qu'à la fois on gaspille un peu de mémoire et on n'a pas accès à tous les caractères, quand on a la possibilité de faire mieux, c'est un mauvais compromis
> (mais je crois que tu es d'accord)
> 
> - la 1ère reste pour l'instant ma préférée,
> 
> mais c'est du au fait que j'ai une quantité de petits littéraux parsemés tout au long du code, dont je ne fais rien d'autre que de les passer à GtkAda,
> donc dans ces cas là, en général on n'a pas la nécessité de compter le nombre de caractères,
> il me semble qu'à part les concaténations, on fait très peu d'opérations sur les chaines, découpages, etc. (et il me semble que GtkAda n'en fait pas plus en interne, mais je peux me tromper)
> 
> donc ce qui me parait le plus simple c'est que tout ça soit de l'UTF-8 et que le compilateur le sache, simplement, et qu'il n'y ait aucune conversion nulle part
> (gestion native de l'UTF-8 par le compilateur de bout en bout)
> 
> par contre, si je te lis bien, il y a au moins un cas de figure, avec certaines taches liées à Javascript, dans lequel t'as beaucoup plus d'opérations à faire sur les chaines, incluant des index et des longueurs,
> donc merci de m'avoir rappelé que UTF_8_String pose des problèmes dans ce cas là, puisqu'on s'attend à faire les calculs sur des caractères alors que là ça les fait sur des octets, ça avait échappé à ma réflexion jusqu'à ce moment :-)
> 
> 3
> quand tu dis "Some combinations may lead to incorrect UTF-8 representation.", peux tu préciser ?

Toute combinaison de 2 octets, par exemple, ne représente pas un caractère valide. La corruption d'un seul octet (le premier d'une combinaison à 2 ou 3 octets) dans la transmission peut invalider tout le message. L'utilisateur doit constamment s'assurer que les données manipulées représentent une chaine UTF-8 "bien formée".

> quand on découpe une chaine d'octets au milieu, à condition d'être sur que c'était une chaine UTF-8 bien formée au départ, c'est extrêmement simple de savoir si on a découpé entre 2 caractères ou au milieu d'un caractère :
> il suffit de tester si l'octet suivant commence par 2#10# (est ce une bonne façon d'écrire, pour designer les 2 1ers bits de l'octet ?)

Bien, mais dans l'hypothèse de découpe d'une chaine en deux, une fois que l'on sait être au milieu d'un caractère, que faire ? le mettre avec les caractères de gauche ? ou ceux de droite ? Le programme ne sait pas faire de choix autre qu'un choix arbitraire.

> pour ce qui est des index et des longueurs, effectivement il faut parcourir la chaine, mais il suffit de compter tous les 2#0# et 2#11#, et d'ignorer les 2#10#, c'est quand même très peu gourmand en ressources par rapport à calculer les points de code ...
> est ce que ça te parait quand même rédhibitoire ?

C'est toujours moins direct que d'obtenir la longueur avec la taille en mémoire. Surtout dans les applications graphique qui ajuste l'affichage en fonction de la taille des lignes à afficher.

> 4
> ce que tu proposes implique quand même pas mal de conversions (si c'était un type unique qui devait servir à tout),
> et sauf erreur, et bien sur avec une bonne optimisation, une conversion entre Wide_Wide_String et UTF-8 est quand même significativement plus couteuse qu'un calcul d'index ou de longueur
> 
> alors peut-être que ce qu'il faudrait, c'est avoir les 2 :
> 
> - avoir des variables codées en UTF-8, qui sachent gérer les index et longueurs etc, pour qu'il n'y ait aucun risque pour le programmeur et aucune précaution nécessaire de sa part, en cas de besoins ponctuels
> 
> - avoir ta proposition UXString à coté,
> avec largeur adaptative, mais constante sur toute la chaine à un instant donné,
> pour pouvoir basculer dessus quand on pressent qu'on va avoir une quantité suffisante de traitements à faire dessus entre 2 conversions
> 
> qu'en penses tu ?
> est ce prendre un marteau pilon pour écraser une mouche, que de proposer d'avoir les 2 mécanismes au choix ?

Ce que je tente avec UXString, c'est justement que l'utilisateur n'ai pas de choix à faire et que toute la complexité ne lui soit pas visible conformément à l'esprit Ada.
Mais cette proposition n'est surement pas assez aboutie pour le refléter.

> 5
> https://sourceforge.net/p/gnoga/code/ci/dev_1.6/tree/deps/uxstrings/src/uxstrings.ads
> 
> l 23-24
> 
> il me semble qu'au lieu de
>   subtype UTF_8_Character is Character;
> tu devrais plutôt écrire
>   subtype UTF_8_Byte is Character;
> (on a tout à gagner à bien nommer les choses des le début)

J'ai bien hésité, j'ai pensé aussi à UTF_8_Code_Point.

> (pour UTF_16_Character je pense qu'il faudrait trouver un mot qui désigne un "double octet", mais je ne sais pas quel mot employer ni en français ni en anglais)

Eh bin c'est vrai, faute de mieux...

> comme j'avais commencé à écrire ça avec ta version du 2020-09-12, je me suis aperçu que t'as changé
>   type UTF_8_Character is new Character;
> en
>   subtype UTF_8_Character is Character;
> pourquoi ?

C'est pour faciliter les conversions avec les bibliothèques Ada toujours en String.

> et si à la place tu faisais quelque chose du genre
>   type UTF_8_Byte is new Ada.Streams.Stream_Element;
> quel genre de problèmes ça poserais ?

J'ai également hésité, pour simplifier ma proposition j'ai sciemment occulté les Streams, il y a encore un travail certain de ce côté.

> l 26-28, 84-87
> 
> je ne comprend pas ce mélange entre UTF_16BE / UTF_16LE et Wide_Character :
> dans Ada.Strings.UTF_Encoding, ça utilise Encoding_Scheme avec UTF_String, mais pas avec UTF_16_Wide_String, ça n'en a pas besoin ...
> l'envisages tu autrement ?

Wide_Character est une représentation du compilateur donc il se débrouille tout seul, par contre pour sortir ceux-ci à l'extérieur, on doit préciser avec quel monde BE ou LE on veut être compatible.

> moins important, mais ça l'est quand même si on considère que ça pourrais être pleinement intégré au langage, et pas juste une bibliothèque additionnelle :
> 
> à chaque fois qu'on convertit dans un jeu de caractères restreint, tu nous imposes de choisir un caractère de substitution, pour les caractères se trouvant en dehors du jeu de caractères de destination :
> 
> je n'aime pas cette idée, j'aime beaucoup mieux que ça renvoie simplement une erreur, comme Ada.Strings.UTF_Encoding.Strings.Decode qui renvoie Encoding_Error
> (au minimum il faudrait qu'on ait le choix)

C'est une histoire de goût et des couleurs ;-)
Personnellement, je préfère avoir en retour une chaine où je peux investiguer si pb au lieu d'une exception qui ne me dit rien d'autre qu'il y a eu une erreur.
Peut-être en ajoutant un paramètre indiquant si l'on désire avoir une exécution "avec exception" ou non.

> le programmeur peut utiliser à loisir le type Ada.Strings.Wide_Wide_Maps.Wide_Wide_Character_Mapping pour transformer la variable en sorte qu'elle rentre dans le jeu de caractères souhaité

Assez pénalisant pour des occurrences d'erreur faibles ou presque nulles.

Cordialement, Pascal.
https://blady.pagesperso-orange.fr


_______________________________________________
Ada-france mailing list
[email protected]
https://mail.ada-france.org/cgi-bin/mailman/listinfo/ada-france