Skip to content

4.2 Mélodie en chaines

Claude Roux edited this page Oct 23, 2019 · 3 revisions

Mélodie en chaines

English Version

Autant révéler ce terrible secret avant de commencer cet article. La raison d’être de Tamgu est de manipuler des chaines de caractères. Tout le reste découle de ce besoin primordial. Or, la majorité des langages de programmation semblent considérer les chaines de caractère comme un problème périphérique. Tout est toujours lourd, peu intuitif ou carrément crypté (Perl ???). En particulier, la gestion des encodages est souvent une source de souffrance aiguë pour le majorité des informaticiens.

Unicode

Si aujourd’hui, la majorité des gens s’accordent autour de l’Unicode, il n’en reste pas moins que l’Unicode peut prendre de nombreuses formes troublantes. La majorité des pages Web sont encodés en UTF8, tandis que Windows lui préfère l’UTF16. Quant à Linux ou à Mac OS, ils ont fait le choix de l’UTF32.

Ainsi le type C++ « std::wstring » vaut 16 bits sur Windows et 32 bits sous linux… Pour un type standard, cela fait beaucoup de différence.

Le résultat est d’ailleurs assez exaspérant : calculer la taille d’une chaine de caractères peut se révéler très compliqué.

Prenons un exemple simple : « 👌🏾».

A priori la taille de la chaine semble être 1. Hélas, en UTF8 sa représentation interne est en fait de 8 octets :

[240,159,145,140,240,159,143,190]

En UTF16, on se dit qu’on devrait aboutir à une représentation plus proche de 1. Hélas, ce n’est pas le cas. Les émojis ont des codes dont les valeurs dépassent 65535, et par conséquent la représentation de ce caractère en UTF16 est composée de 4 nombres sur 16 bits :

[55357, 56396, 55356, 57342]

Alors l’UTF32 va-t-il nous apporter enfin la solution ? Hélas non, ce caractère émoji est en fait constitué de deux caractères Unicode : «👌» et le caractère de sa couleur : « 🏾», soit en fait deux nombres sur 32 bits :

 [128076,127998]

Ce qui d’ailleurs explique pourquoi il faut 8 caractères UTF8 et 4 caractères UTF16 pour représenter un seul caractère à l’écran. On se dit, d’accord, tout ça c’est bien compliqué, mais heureusement les langages de programmation en tiennent compte.

Prenons l’exemple suivant dans Python:

u="👌🏾"
l = len(u) 

Hélas, « l » vaut 2… Et à l’écran, on ne voit qu’un seul caractère… Une merveilleuse source de bugs incompréhensible. Python ne comprend pas que « 🏾 » est le complément de «👌».

Tamgu est là

Tamgu vise à harmoniser les yeux et le coeur. Pardon, à harmoniser les yeux et le code. Si un seul caractère apparait à l’écran, il faut qu’un seul caractère soit compté quand on parcourt la chaine. Sinon, on passe plus de temps à s’interroger sur l’origine du « 🏾» qu’à coder son algorithme.

Or, les emojis ont envahi le monde. Il a fallu trois mille ans pour passer des hiéroglyphes à l’alphabet et il a fallu moins de dix ans pour revenir à une écriture que les Egyptiens auraient adorée. On ne peut donc plus hausser les épaules et laisser le programmeur se dépatouiller avec des longueurs de chaine bizarroïdes.

LATIN, UTF8, UTF16, UTF32

D’abord, Tamgu sait détecter automatiquement si une chaine est en UTF8. Autrement dit, s’il parcourt une chaine d’octets, il saura immédiatement détecter les octets composites de l’UTF8 des autres. De cette façon, il est possible de manipuler des chaines hybrides sans que le système ne grince des dents. La seule difficulté dans ce cas est d’identifier correctement le type d’encodage latin. Là, en revanche, l’intervention humaine est obligatoire, mais Tamgu fournit quand même les moyens de convertir n’importe quel encodage Latin en UTF8. Il sait aussi gérer l’UTF16 sous Windows, car là aussi certains caractères peuvent être composites. Enfin, il sait manipuler les emojis multiples quelque soit l’encodage initial.

string/ustring

Tamgu offre en fait deux types de variables de chaine, ce qui vous permet de choisir l’encodage sous-jacent. Si vous souhaitez manipuler des chaines en UTF8 ou en Latin, autrement dit si vous souhaitez manipuler des chaines d’octets, vous pouvez choisir « string ».

Si au contraire, vous voulez manipuler des chaines Unicode, vous pouvez utiliser le type « ustring ». En revanche, il faut noter que « ustring » est encodé en UTF16 sous Windows et en UTF32 ailleurs. Généralement, cet encodage est invisible au programmeur, mais il a un impact sur la vitesse avec laquelle on accède aux différents caractères de cette chaine.

Note: Pour accélérer le traitement, on utilise « check_large_char » implémenté avec des instructions AVX pour détecter si un accès direct est possible (Voir: Traitement accéléré des chaines de caractères).

bytes()/ord()

Si vous voulez découvrir l'encodage sous-jacent de votre plate-forme, « bytes » est la méthode qu'il vous faut. La méthode « ord » vous renvoie les codes Unicode de votre chaine de caractère.

Attention: dans le cas de Linux ou Mac OS, ces deux méthodes renvoient le même résultat.

C'est l'utilisation de la méthode "bytes" qui nous a permis d'extraire les représentation sous-jacente suivante de: « 👌🏾 »:

UTF8: [240,159,145,140,240,159,143,190] (partout)
UTF16: [55357, 56396, 55356, 57342] (Windows)
UTF32: [128076,127998] (Unix)

Indexes

Tamgu est resté fidèle à la majorité des langages de programmation de ce monde en utilisant les « [] » pour accéder aux caractères d'une chaine. Comme vous le verrez dans ce qui suit, nous nous sommes permis d'innover quelque peu.

string s = "👌🏾: Ceci est un exemple.";

Numériques

Vous pouvez évidemment utiliser des indexes numériques.

s[0] renvoie "👌🏾"
s[1] renvoie ":"
s[2] renvoie " " 
s[3] renvoie "C" 

Vous pouvez utiliser des indexes négatifs:

s[-1] renvoie "."
s[-2] renvoie "e" 
s[-3] renvoie "l" etc…

Vous pouvez aussi utiliser des indexes multiples:

s[0:2] renvoie "👌🏾:"
s[3:5] renvoie "Ce"

Vous pouvez aussi omettre l’un des indexes:

s[:2] renvoie "👌🏾:"
s[3:] renvoie "Ceci est un exemple."

Ce qui ressemble beaucoup à ce que Python peut offrir.

Alphabétiques

Sauf que Tamgu va un peu plus loin. Nous pouvons aussi utiliser des chaines de caractères comme index:

s["C":]    renvoie "Ceci est un exemple."
s["C":"x"] renvoie "Ceci est un ex"


Et on peut aussi combiner des indexes alphabétiques avec des indexes numériques. Mais dans ce cas, l’index numérique indique le nombre de caractère après la sous-chaine.

s["C":10] renvoie "Ceci est un"

On peut enfin utiliser des indexes alphabétiques négatifs. Dans ce cas, on indique à Tamgu que l’élément cherché doit être le dernier dans la chaine.

s["e":10]  renvoie "eci est un"
s[-"e":10] renvoie "e."

Expressions Régulières

Tamgu offre aussi des expressions régulières. Celles-ci viennent en deux parfums: les treg et les preg. Derrière ces noms à la sonorité barbare se cachent en fait deux types d’expressions:

les expressions régulières Tamgu (r"...") les expressions régulières Posix (p"...")

Vous pouvez utiliser ces expressions directement comme indexes.

Je ne présenterais pas ici les pregs qui sont basés sur les expressions régulières traditionnelles du monde Unix. Elles sont basées sur la bibliothèque "std::regex" en C++.

Les tregs sont des expressions régulières propre à Tamgu, compilées sous la forme d'automates à états finis. Leur formalisme est très simple et est basé sur les balises suivantes:

%a: signifie un caractère alphabétique
%c: signifie un caractère alphabétique minuscule
%C: un caractère alphabétique majuscule
%d: signifie un chiffre (un digit)
%e: signifie un emoji
%H: signifie un caractère coréen (Hangul)
%p: signifie une ponctuation
%r: signifie un retour chariot
%s: signifie un espace
%x: un chiffre hexadécimal
?: n'importe quel caractère
%?: le caractère '?' lui-même
%%: le caractère '%' lui-même
\xFFFF: code Unicode en hexadécimal
{...}: disjonction de caractère
[...]: séquence de caractère dans une disjonction
+,* : les opérateurs de Kleine
~: la négation
string s = "Yooo:10 Wesdenesday, 20 Saturday.";

s[r"%d+"]     renverra "10"
s[-r"%d+"]    renverra "20"
s[p'\w+day']  renverra "Wesdenesday"
s[-p'\w+day'] renverra "Saturday"

Mutable

Tamgu à la différence de Python considère les chaines de caractères comme étant « modifiables ». Cela va d’ailleurs plus loin. Non seulement, il est possible de modifier un caractère dans une chaine, mais on peut aussi modifier cette chaine via des indexes multiples.

string s = "👌🏾: Ceci est un exemple.";

s["C":"x"] = "000";
//Désormais « s » vaut: 👌🏾: 000emple.

//On peut aussi utiliser des expressions régulières
s = "👌🏾: Ceci est un exemple.";
s[r"%C%a+"] = "This";

//Désormais "s" vaut: 👌🏾: This est un exemple.

in

Nous terminerons avec l'opérateur "in" particulièrement utile pour détecter la présence d'une sous-chaine dans une chaine.

De la même façon que l'opérateur "[..]", "in" accepte sans broncher toutes les formes d'interrogation.

if ("est" in s) ... //est ce que la chaine 'est' se trouve dans s ?
if (r"%d+" in s) ... //a-t-on des chiffres dans la chaine ?
if (p"\w+day" in s) ... //a-t-on la présence d'un nom de semaine dans s ?

Cet opérateur peut-être aussi utilisé conjointement avec un vecteur de chaine: svector:

svector vs;

string s = "👌🏾: Ceci est un exemple.";
vs =  "est" in s;  //on cherche la position de tous les 'est' dans s
//vs contient  ["est"], pas très informatif mais possible

string s = "Yooo:10 Wesdenesday, 20 Saturday.";

vs =  r"%d+" in s;  //on cherche TOUS les chiffres dans s
// vs contient ["10","20"]

vs =  p"\w+day" in s;  //on cherche TOUS les noms de semaine dans s
// vs contient ["Wesdenesday","Saturday"]

Si on remplace svector avec ivector, on récupérera toutes les positions des sous-chaines dans s.

ivector iv;
string s = "👌🏾: Ceci est un exemple.";
iv =  "est" in s;  //on cherche la position de tous les 'est' dans s
//iv contient  [8]

s = "Yooo:10 Wesdenesday, 20 Saturday.";

iv =  r"%d+" in s;  //on cherche TOUS les chiffres dans s
//iv contient [5,7,21,23]

iv =  p"\w+day" in s;  //on cherche TOUS les noms de semaine dans s
//iv contient [8,19,24,32]

Dans les deux derniers cas, si l'on utilise une expression régulière, iv contient le début et la fin de chaque sous-chaine.

Clone this wiki locally