JavaScript est, sans quelque passe-partout personnalisé , incapable de traiter correctement les caractères/points de code Unicode en dehors du BMP , c'est-à-dire ceux dont l'encodage nécessite plus de 16 bits.
Cette limitation semble s'appliquer à PEG.js, comme le montre l'exemple ci-dessous.
En particulier, j'aimerais pouvoir spécifier des plages telles que [\u1D400-\u1D419]
(qui se transforme actuellement en [ᵀ0-ᵁ9]
) ou de manière équivalente [𝐀-𝐙]
(qui renvoie une "plage de caractères invalide" Erreur). (Et l'utilisation de la nouvelle notation ES6 [\u{1D400}-\u{1D419}]
entraîne l'erreur suivante : SyntaxError: Expected "!", "$", "&", "(", ".", character class, comment, end of line, identifier, literal, or whitespace but "[" found.
.)
Existe-t-il un moyen de faire en sorte que cela fonctionne sans modifier PEG.js ?
Exemple de code :
Cette grammaire :
//MathUpper = [𝐀-𝐙]+
MathUpperEscaped = [\u1D400-\u1D419]+
Comportement prévisible:
L'analyseur généré à partir de la grammaire donnée analyse avec succès, par exemple, "𝐀𝐁𝐂".
Comportement réel :
Une erreur d'analyse : Line 1, column 1: Expected [ᵀ0-ᵁ9] but "
(Ou, lors du dé-commentaire de l'autre règle, une erreur « Plage de caractères non valide ».)
Pour être tout à fait honnête, à part la mise à jour du support Unicode pour l' analyseur de grammaire PEG.js et l' exemple JavaScript , j'ai peu ou pas de connaissances sur Unicode, donc je suis actuellement incapable de résoudre ce problème (c'est clairement indiqué dans les deux grammaires : _Non -Les caractères BMP sont complètement ignorés_).
Pour l'instant, tout en travaillant sur des projets personnels et professionnels plus urgents (y compris _PEG.js 0.x_), je continuerai d'attendre que quelqu'un qui comprend mieux Unicode propose des relations publiques 😆, ou éventuellement s'y adapte après _PEG. js v1_, désolé mon pote.
Pour info, les paires de substitution semblent fonctionner. La grammaire
start = result:[\uD83D\uDCA9]+ {return result.join('')}
analyse qui est u+1F4A9. Notez que result.join('') rassemble la paire de substitution, sinon vous obtenez ['\uD83D','\uDCA9']
au lieu de hankey. Les plages seraient problématiques.
En savoir plus sur les paires de substitution : https://en.wikipedia.org/wiki/UTF-16#U +010000_to_U+10FFFF
Cela ne remplace en aucun cas ce que le PO a demandé.
@drewnolan Merci pour l'avertissement
Malheureusement, cette grammaire analyse également \uD83D\uD83D
.
Pour les autres qui ont rencontré ce problème : j'ai la chance de n'avoir besoin de gérer qu'un petit sous-ensemble de points de code en dehors du BMP, j'ai donc fini par les mapper dans la zone d'utilisation privée du BMP avant d'analyser et d'inverser ce mappage juste après .
Cette solution est évidemment pleine de problèmes dans le cas général, mais elle fonctionne bien dans mon domaine de problème.
@futagoza - Je ferai de mon mieux pour expliquer. Vous rencontrez ici plusieurs problèmes.
utf-16
, ucs4
, et cetera. C'est ainsi que les codepoints
, qui sont les données voulues, sont codées sous forme d'octets. utf-16-le
par exemple vous permet de coder la plupart des lettres sous forme de paires de deux octets appelées code units
, mais utilisez des groupes d'unités de code pour exprimer des caractères de grande valeur jusqu'à 0x10ffff
.Je le veux aussi, mais de façon réaliste, cela n'arrivera pas
putain de merde, quelqu'un a commis un remplacement complet de l'analyseur de chaînes il y a près d'un an , et ils ont reconnu les frais généraux, alors ils nous ont généralement laissé utiliser des chaînes JS standard
POURQUOI CE N'EST PAS FUSIONNÉ
@StoneCypher J'aime le feu dans ton cœur ! Mais pourquoi donner du fil à retordre au mainteneur actuel ? Personne n'est redevable de quoi que ce soit. Pourquoi ne pas entretenir votre propre fourche ?
Il n'y a pas de mainteneur actuel. La personne qui a repris PEG n'a jamais rien lâché. Il a travaillé sur le mineur suivant pendant trois ans, puis a dit qu'il n'aimait pas à quoi il ressemblait, qu'il jette tout peg.js
et qu'il recommence à partir de quelque chose qu'il a écrit à partir de zéro dans une langue différente, avec un AST.
L'outil a perdu la moitié de sa base d'utilisateurs en attendant trois ans que cet homme valide les correctifs d'une ligne que d'autres personnes ont écrits, comme la prise en charge du module es6, la prise en charge des scripts, la prise en charge des flèches, l'unicode étendu, etc.
Il y a une douzaine de personnes qui lui demandent de fusionner et il n'arrête pas de dire "non, c'est mon projet de loisir maintenant et je n'aime pas ce que c'est"
Beaucoup de gens ont des entreprises basées sur cet analyseur. Ils sont complètement foutus.
Cet homme a promis d'être le mainteneur d'un outil extrêmement important, et n'a fait aucun entretien. Il est temps de laisser quelqu'un d'autre garder cette bibliothèque en règle maintenant.
Pourquoi ne pas entretenir votre propre fourche ?
Je l'ai depuis trois ans maintenant. Mon peg
a presque un tiers du suivi des problèmes corrigé.
J'ai dû le cloner, le renommer et créer un nouveau fork pour résoudre le problème de taille pour essayer de le valider, car le mien a trop dérivé
Il est temps que tout le monde reçoive ces correctifs, ainsi que ceux qui sont dans le tracker depuis 2017.
Ce type ne maintient pas la cheville ; il le laisse mourir.
Il est temps de changer.
@drewnolan - donc, je ne sais pas si cela est intéressant ou non, mais les paires de substitution ne fonctionnent pas réellement. C'est juste que, par coïncidence, ils le font habituellement.
Afin de comprendre le problème sous-jacent, vous devez penser au modèle de bits au niveau de l'encodage, et non au niveau de représentation.
C'est-à-dire que si vous avez une valeur Unicode de 240, la plupart des gens penseront "Oh, il veut dire 0b1111 0000
." Mais en fait, ce n'est pas ainsi qu'Unicode représente 240 ; plus de 127 est représenté par deux octets, car le bit supérieur est un indicateur, pas un bit de valeur. Donc 240 en Unicode est en fait 0b0000 0001 0111 0000
en stockage (sauf en utf-7, qui est réel et pas une faute de frappe, où les choses deviennent super bizarres. Et oui, je sais que Wikipedia dit qu'il n'est pas utilisé. Wikipedia a tort . C'est ce sur quoi les SMS sont envoyés ; c'est peut-être l'encodage de caractères le plus courant par le trafic total.)
Voici donc le problème.
Si vous écrivez un octet STUV WXYZ, puis en utf16, à partir de données ucs4, si votre objet est réduit de moitié, vous pouvez très souvent simplement l'agrafer à nouveau.
Une fois sur 128 vous ne pouvez pas, pour des caractères natifs sur un encodage de plus de deux octets. (On dirait un nombre terriblement spécifique, n'est-ce pas ?)
Parce que lorsque ce bit supérieur dans la deuxième position d'octet est utilisé, le couper en deux ajoutera un zéro là où cela aurait dû être un un. Les agrafer côte à côte en tant que données binaires ne supprime pas à nouveau le concept de valeur. La valeur décodée n'est pas équivalente à la valeur encodée, et vous ajoutez des décodages, pas des encodages.
Il se trouve que la plupart des emoji sont en dehors de cette plage. Cependant, de gros morceaux de plusieurs langues ne le sont pas, y compris le chinois rare, la plupart des symboles mathématiques et musicaux.
Certes, votre approche est assez bonne pour presque tous les emoji, et pour chaque langage humain assez commun pour entrer par Unicode 6, ce qui est une énorme amélioration par rapport au statu quo
Mais ce PR devrait vraiment être fusionné, une fois qu'il est suffisamment testé à la fois pour son exactitude et contre les problèmes de performances inattendus (rappelez-vous, les problèmes de performances unicode sont la raison pour laquelle php est mort)
Il semble que l'expression . (dot character)
nécessite également le mode Unicode. Comparer:
const string = '-🐎-👱-';
const symbols = (string.match(/./gu));
console.log(JSON.stringify(symbols, null, ' '));
const pegResult = require('pegjs/')
.generate('root = .+')
.parse(string);
console.log(JSON.stringify(pegResult, null, ' '));
Sortir:
[
"-",
"🐎",
"-",
"👱",
"-"
]
[
"-",
"\ud83d",
"\udc0e",
"-",
"\ud83d",
"\udc71",
"-"
]
J'ai travaillé récemment dessus, en utilisant le #616 comme base et en le modifiant pour utiliser la syntaxe ES6 \u{hhhhhhh}
, je vais créer un PR dans quelques heures.
Le calcul des plages d'expressions régulières UTF-16 divisées par des substituts est un peu compliqué et j'ai utilisé https://github.com/mathiasbynens/regenerate pour cela; ce serait la première dépendance du package pegjs, j'espère que c'est possible (il existe également des polyfills pour les propriétés Unicode qui pourraient être ajoutés en tant que dépendance, voir #648). Voir Wikipedia si vous ne connaissez pas les substituts UTF-16 .
Pour rendre PEG.js compatible avec l'ensemble de l'Unicode, il existe plusieurs niveaux :
.
pour capturer 1 ou 2 unités de code.Pour la plupart des points, nous pouvons réussir à être rétrocompatibles et générer des analyseurs très similaires aux anciens, à l'exception du point 5 car le résultat d'une analyse peut dépendre si la règle des points capture une ou deux unités de code. Pour cela je propose d'ajouter une option runtime pour laisser le choix à l'utilisateur entre deux ou trois choix :
La classe Regex peut être analysée statiquement lors de la génération de l'analyseur pour vérifier si elle a une longueur fixe (en nombre d'unités de code). Il y a 3 cas : 1. seulement un BMP ou une seule unité de code, ou 2. seulement deux unités de code, ou 3. une ou deux unités de code selon le temps d'exécution. Pour l'instant, le bytecode suppose qu'une classe regex est toujours une unité de code ( voir ici ). Avec l'analyse statique, on pourrait changer ce paramètre de cette instruction bytecode à 1 ou 2 pour les deux premiers cas. Mais pour le troisième cas, je suppose qu'une nouvelle instruction bytecode doit être ajoutée pour, au moment de l'exécution, obtenir le nombre d'unités de code correspondant et augmenter le curseur en conséquence. D'autres options sans nouvelle instruction bytecode seraient : 1. de toujours calculer le nombre d'unités de code correspondant, mais cela pénalise les performances lors de l'analyse pour les analyseurs BMP uniquement, donc je n'aime pas cette option ; 2. pour calculer si l'unité de code actuelle est un substitut élevé suivi d'un substitut faible pour incrémenter de 1 ou 2, mais cela supposerait que la grammaire a toujours des substituts UTF-16 bien formés sans la possibilité d'écrire des grammaires avec des substituts seuls ( voir le point suivant) et c'est aussi une pénalité de performance pour les analyseurs BMP uniquement.
Il y a la question des mères porteuses isolées (mère porteuse élevée sans mère porteuse faible après elle, ou mère porteuse faible sans mère porteuse élevée avant elle). Mon opinion à ce sujet est qu'une classe regex devrait être exclusivement : soit avec des substituts seuls, soit avec des caractères Unicode UTF-16 bien formés (BMP ou un substitut élevé suivi d'un substitut faible), sinon il y a le danger que les auteurs de grammaire ignorent Les subtilités UTF-16 mélangent les deux et ne comprennent pas le résultat, et les auteurs de grammaire qui souhaitent gérer eux-mêmes les substituts UTF-16 peuvent le faire avec des règles PEG pour décrire les liens entre des substituts spécifiques haut et bas. Je propose d'ajouter un visiteur appliquant cette règle lors de la génération de l'analyseur.
Pour conclure, il est probablement plus facile de gérer la question des substituts isolés dans le PEG que dans les regex car l'analyseur PEG avance toujours, donc soit l'unité de code suivante est reconnue soit elle ne l'est pas, au contraire des regex où éventuellement un retour en arrière pourrait s'associer ou dissocier un substitut élevé d'un substitut faible et par conséquent modifier le nombre de caractères Unicode correspondants, etc.
La syntaxe PR pour ES6 pour le caractère Unicode astral est #651 basé sur #616 et le développement pour les classes est https://github.com/Seb35/pegjs/commit/0d33a7a4e13b0ac7c55a9cfaadc16fc0a5dd5f0c implémentant les points 2 et 3 dans mon commentaire ci-dessus, et uniquement un petit hack pour l'incrémentation du curseur (point 4) et rien pour l'instant pour le point de règle .
(point 5).
Mon développement actuel sur ce problème est en grande partie terminé, le travail le plus avancé se trouve sur https://github.com/Seb35/pegjs/tree/dev-astral-classes-final. Les cinq points mentionnés ci-dessus sont traités et le comportement global essaie d'imiter les regex JS concernant les cas limites (et il y en a beaucoup).
Le comportement global est régi par l'option unicode
similaire au drapeau unicode
dans les regex JS : le curseur est augmenté de 1 caractère Unicode (1 ou 2 unités de code) en fonction du texte réel (ex. [^a]
correspond au texte "💯" et le curseur est augmenté de 2 unités de code). Lorsque l'option unicode
est fausse le curseur est toujours augmenté d'1 unité de code.
En ce qui concerne l'entrée, je ne sais pas si nous concevons PEG.js de la même manière que les regex JS : devons-nous autoriser [\u{1F4AD}-\u{1F4AF}]
(équivalent à [\uD83D\uDCAD-\uD83D\uDCAF]
) dans la grammaire en mode non-Unicode ? On peut faire la différence entre « input Unicode » et « output Unicode » :
Personnellement, je suppose que je préférerais que nous autorisions l'entrée Unicode, soit de manière permanente, soit avec une option avec une valeur par défaut true
car il n'y a pas de surcharge significative et cela permettrait cette possibilité pour tout le monde par défaut, mais la sortie Unicode devrait rester false
car les performances des analyseurs générés sont meilleures (toujours un incrément de curseur de 1).
Concernant ce problème en général (et à propos de la sortie Unicode par défaut à false
), il faut garder à l'esprit qu'il est déjà possible d'encoder dans nos grammaires des caractères Unicode, au prix de comprendre le fonctionnement de l'UTF-16 :
// rule matching [\u{1F4AD}-\u{1F4AF}]
my_class = "\uD83D" [\uDCAD-\uDCAF]
// rule matching any Unicode character
my_strict_unicode_dot_rule = $( [\u0000-\uD7FF\uE000-\uFFFF] / [\uD800-\uDBFF] [\uDC00-\uDFFF] )
// rule matching any Unicode character or a lone surrogate
my_loose_unicode_dot_rule = $( [\uD800-\uDBFF] [\uDC00-\uDFFF]? / [\u0000-\uFFFF] )
Ainsi, un auteur de grammaire qui souhaite à la fois un analyseur rapide et être capable de reconnaître les caractères Unicode dans des parties spécifiques de sa grammaire peut utiliser une telle règle. Par conséquent, ce problème concerne simplement la simplification de la gestion Unicode sans plonger dans les éléments internes de l'UTF-16.
À propos de l'implémentation, j'ai considéré lors de ma première tentative que le texte de grammaire était encodé en caractères Unicode et que la règle « point » de l'analyseur PEG.js reconnaissait les caractères Unicode. La deuxième et dernière tentative a annulé cela (la règle de point est toujours 1 unité de code pour une analyse plus rapide) et il existe un petit algorithme dans le visiteur prepare-unicode-classes.js pour reconstruire les caractères Unicode divisés dans les classes de caractères (par exemple [\uD83D\uDCAD-\uD83D\uDCAF]
est syntaxiquement reconnu comme [ "\uD83D", [ "\uDCAD", "\uD83D" ], "\uDCAF" ]
et cet algorithme le transforme en [ [ "\uD83D\uDCAD", "\uD83D\uDCAF" ] ]
). J'avais prévu d'écrire ça dans la grammaire elle-même mais ça aurait été long et surtout il y a plusieurs façons d'encoder les caractères ("💯", "uD83DuDCAF", "u{1F4AF}"), donc c'est plus facile de l'écrire chez un visiteur.
J'ai ajouté deux opcodes lors de la deuxième tentative :
(input.charCodeAt(currPos) & 0xFC00) === 0xD800 && input.length > currPos + 1 && (input.charCodeAt(currPos+1) & 0xFC00) === 0xDC00
classes[c].test(input.substring(currPos, currPos+2)
ACCEPT_N
, et les classes de caractères sont divisées en deux regex de longueur fixe (1 ou 2 unités de code).J'ai fait quelques optimisations avec la fonctionnalité "match", en éliminant lors de la génération les chemins "dead code" selon le mode (Unicode ou non) et la classe des caractères.
Notez également que les expressions régulières sont toujours positives dans cette implémentation : les expressions régulières inversées renvoient le contraire, ce qui donne le bytecode. C'était plus facile d'éviter les cas limites autour des mères porteuses.
J'ai écrit de la documentation mais j'en ajouterai probablement d'autres (peut-être un guide pour expliquer rapidement les détails des options unicode et des extraits avec la règle de point Unicode faite maison). Je vais ajouter des tests avant de le soumettre en tant que PR.