Pegjs: Soporte completo de Unicode, es decir, para puntos de código fuera del BMP

Creado en 22 oct. 2018  ·  15Comentarios  ·  Fuente: pegjs/pegjs

Tipo de problema

  • Informe de error:
  • Solicitud de función: un poco
  • Pregunta: no
  • No es un problema: no

Prerrequisitos

  • ¿Puedes reproducir el problema ?: si
  • ¿Buscaste los problemas del repositorio ?:
  • ¿Revisaste los foros ?:
  • ¿Realizó una búsqueda en la web (google, yahoo, etc.) ?:

Descripción

JavaScript es, sin un texto repetitivo personalizado , incapaz de manejar correctamente los caracteres / puntos de código Unicode fuera del BMP , es decir, aquellos cuya codificación requiere más de 16 bits.

Esta limitación parece trasladarse a PEG.js, como se muestra en el siguiente ejemplo.

En particular, me gustaría poder especificar rangos como [\u1D400-\u1D419] (que actualmente se convierte en [ᵀ0-ᵁ9] ) o equivalentemente [𝐀-𝐙] (que arroja un "rango de caracteres no válido" error). (Y usar la notación nueva de ES6 [\u{1D400}-\u{1D419}] da como resultado el siguiente error: SyntaxError: Expected "!", "$", "&", "(", ".", character class, comment, end of line, identifier, literal, or whitespace but "[" found. .)

¿Podría haber una forma de hacer que esto funcione que no requiera cambios en PEG.js?

Pasos para reproducir

  1. Genere un analizador a partir de la gramática que se proporciona a continuación.
  2. Úselo para intentar analizar algo aparentemente conforme.

Código de ejemplo:

Esta gramática:

//MathUpper = [𝐀-𝐙]+
MathUpperEscaped = [\u1D400-\u1D419]+

Comportamiento esperado:

El analizador generado a partir de la gramática dada analiza correctamente, por ejemplo, "𝐀𝐁𝐂".

Comportamiento real:

Un error de análisis: Line 1, column 1: Expected [ᵀ0-ᵁ9] but " (O, al descomentar la otra regla, un error "Rango de caracteres no válido").

Software

  • PEG.js: 0.10.0
  • Node.js: no aplicable.
  • NPM o hilo: No aplica.
  • Navegador: todos los navegadores que he probado.
  • SO: macOS Mojave.
  • Editor: Todos los editores que he probado.
feature need-help task

Todos 15 comentarios

Para ser completamente honesto, además de actualizar el soporte Unicode para el analizador gramatical PEG.js y el ejemplo de JavaScript , tengo poco o ningún conocimiento sobre Unicode, por lo que actualmente no puedo solucionar este problema (está claramente establecido en ambas gramática: _Non -Los caracteres BMP se ignoran por completo_).

Por ahora, mientras trabajo en proyectos más urgentes relacionados con el personal y el trabajo (incluido _PEG.js 0.x_), seguiré esperando a que alguien que entienda mejor Unicode ofrezca algunas relaciones públicas 😆, o eventualmente lo haga después de _PEG. js v1_, lo siento amigo.

Para su información, los pares sustitutos parecen funcionar. La gramática
start = result:[\uD83D\uDCA9]+ {return result.join('')}
analiza 💩 que es u + 1F4A9. Tenga en cuenta que result.join ('') vuelve a juntar el par sustituto, de lo contrario obtendrá ['\uD83D','\uDCA9'] lugar de hankey. Los rangos serían problemáticos.
Más sobre pares sustitutos: https://en.wikipedia.org/wiki/UTF-16#U + 010000_to_U + 10FFFF

Esto de ninguna manera reemplaza lo que pidió el OP.

@drewnolan Gracias por el aviso 👍

Desafortunadamente, esa gramática también analiza \uD83D\uD83D .

Para otros que se han encontrado con este problema: tengo la suerte de que solo necesito manejar un pequeño subconjunto de puntos de código fuera del BMP, así que terminé mapeándolos en el área de uso privado del BMP antes de analizar e invertir este mapeo inmediatamente después. .

Esta solución está obviamente plagada de problemas en el caso general, pero funciona bien en mi dominio de problemas.

@futagoza - Haré todo lo posible para explicarlo. Te enfrentas a varios problemas aquí.

  1. Unicode tiene codificaciones, notaciones y rangos

    1. Aquí, lo que importa son los "rangos", qué caracteres son compatibles, y las "notaciones", cómo se escriben.

    2. Estos cambian con el tiempo. Unicode agrega periódicamente nuevos caracteres, como cuando agregaron emoji o notas musicales

  2. Unicode tiene "notaciones". Estas son cosas como utf-16 , ucs4 , etcétera. Ésta es la forma en que codepoints , que son los datos previstos, se codifican como bytes. utf-16-le por ejemplo le permite codificar la mayoría de las letras como pares de dos bytes llamados code units , pero use grupos de unidades de código para expresar caracteres de alto valor hasta 0x10ffff .

    1. Comprensión clave: eso no es lo suficientemente alto. Un montón de cosas interesantes, como emoji, grandes trozos de chino histórico y la pregunta base de esta publicación (caracteres matemáticos de la pizarra) están por encima de esa línea.



      1. ISO y Unicode Consortium han sido claros: nunca lo arreglarán . Si desea caracteres más altos, use una codificación más grande que utf-16.



    2. Comprensión clave n. ° 2: el puto javascript es



      1. Esto significa que existen caracteres Unicode (muchos de ellos) que el tipo de cadena Javascript no puede representar.


      2. OP te pide que arregles esto


      3. Esto es posible pero no fácil: tendrías que implementar el algoritmo de análisis Unicode, que es famoso por ser un nido de ratas.



Yo también quiero esto, pero siendo realistas, esto no va a suceder.

mierda, alguien cometió un reemplazo de analizador de cadenas completo hace casi un año , y reconocieron la sobrecarga, por lo que nos dejaron usar cadenas JS estándar por lo general

POR QUÉ NO ES ESO FUSIONADO

@StoneCypher ¡Amo el fuego en tu corazón! Pero, ¿por qué hacerle pasar un mal rato al responsable de mantenimiento actual? A nadie se le debe nada. ¿Por qué no mantener tu propia bifurcación?

No hay un mantenedor actual. La persona que se hizo cargo de PEG nunca ha publicado nada. Trabajó en el siguiente menor durante tres años, luego dijo que no le gustaba cómo se veía, que estaba tirando todo peg.js y comenzando de nuevo con algo que escribió desde cero en un idioma diferente, con un idioma incompatible. AST.

La herramienta ha perdido la mitad de su base de usuarios esperando tres años a que este hombre cometiera arreglos de una línea que otras personas escribieron, como soporte para módulo es6, soporte para mecanografiado, soporte para flechas, unicode extendido, etcétera.

Hay una docena de personas que le piden que se fusione y él sigue diciendo "no, este es mi proyecto de hobby ahora y no me gusta lo que es".

Mucha gente tiene empresas basadas en este analizador. Están completamente jodidos.

Este hombre prometió ser un mantenedor de una herramienta extremadamente importante y no ha realizado ningún mantenimiento. Es hora de dejar que otra persona mantenga esta biblioteca en buen estado ahora.

¿Por qué no mantener tu propia bifurcación?

Lo tengo desde hace tres años. Mi peg tiene casi un tercio del rastreador de problemas solucionado.

Tuve que clonarlo, cambiarle el nombre y hacer una nueva bifurcación para solucionar el problema de tamaño para intentar confirmarlo, porque el mío se ha desviado demasiado.

Es hora de que todos los demás reciban estas correcciones, así como las que han estado en el rastreador desde 2017.

Este tipo no está manteniendo la vinculación; lo está dejando morir.

Es tiempo de cambiar.

@drewnolan : no estoy seguro de si esto es interesante o no, pero los pares sustitutos no funcionan en realidad. Es solo que, por casualidad, suelen hacerlo.

Para comprender el problema subyacente, debe pensar en el patrón de bits del nivel de codificación, no en el nivel de representación uno.

Es decir, si tiene un valor Unicode de 240, la mayoría de la gente pensará "Oh, se refiere a 0b1111 0000 ". Pero en realidad, no es así como Unicode representa 240; más de 127 está representado por dos bytes, porque el bit superior es un indicador, no un bit de valor. Entonces, 240 en Unicode es en realidad 0b0000 0001 0111 0000 en almacenamiento (excepto en utf-7, que es real y no es un error tipográfico, donde las cosas se ponen muy raras. Y sí, sé que Wikipedia dice que no está en uso. Wikipedia está mal . Es lo que se envía SMS; podría ser la codificación de caracteres más común por tráfico total).

Así que aquí está el problema.

Si escribe un byte STUV WXYZ, luego en utf16, a partir de los datos de ucs4, si su cosa se corta a la mitad, muy a menudo puede volver a engraparla.

Una vez en 128 no se puede, para caracteres de forma nativa con codificación de dos bytes. (Suena como un número terriblemente específico, ¿no?)

Porque cuando ese bit superior en la posición del segundo byte está en uso, cortarlo por la mitad agregará un cero donde debería haber sido uno. Volver a engraparlos uno al lado del otro como datos binarios no elimina el concepto de valor nuevamente. El valor decodificado no es equivalente al valor codificado y está agregando decodificaciones, no codificaciones.

Da la casualidad de que la mayoría de los emoji están fuera de este rango. Sin embargo, gran parte de varios idiomas no lo son, incluido el chino poco común, la mayoría de los símbolos matemáticos y musicales.

Por supuesto, su enfoque es lo suficientemente bueno para casi todos los emoji y para todos los lenguajes humanos lo suficientemente comunes como para ingresar a Unicode 6, lo cual es una gran mejora con respecto al status quo.

Pero este PR realmente debería fusionarse, una vez que se haya probado lo suficiente tanto para verificar su corrección como frente a problemas de rendimiento inesperados (recuerde, los problemas de rendimiento de Unicode son la razón por la que php murió)

Parece que la expresión . (dot character) también necesita el modo Unicode. Comparar:

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, '  '));

Producción:

[
  "-",
  "🐎",
  "-",
  "👱",
  "-"
]
[
  "-",
  "\ud83d",
  "\udc0e",
  "-",
  "\ud83d",
  "\udc71",
  "-"
]

Trabajé recientemente en esto, usando # 616 como base y modificándolo para usar la sintaxis ES6 \u{hhhhhhh} , crearé un PR en algunas horas.

Calcular los rangos de expresiones regulares UTF-16 divididos por sustitutos es un poco complicado y usé https://github.com/mathiasbynens/regenerate para esto; esta sería la primera dependencia del paquete pegjs, espero que sea posible (también hay polyfills para propiedades Unicode que podrían agregarse como dependencia, ver # 648). Consulte Wikipedia si no conoce los sustitutos UTF-16 .

Para que PEG.js sea compatible con todo Unicode, existen varios niveles:

  1. Agregue una sintaxis para codificar caracteres Unicode sobre el BMP, corregido por # 616 o mi versión de sintaxis ES6,
  2. Reconocer cadenas constantes, directamente proporcionadas por el punto anterior,
  3. Corrija el informe SyntaxError para que posiblemente muestre 1 o 2 unidades de código para mostrar el carácter Unicode real,
  4. Calcule con precisión la clase de expresiones regulares para BMP y / o puntos de código astral; esto solo no funciona, consulte el siguiente punto,
  5. Administre el incremento del cursor porque una clase de expresiones regulares ahora puede ser (1), (2) o (1 o 2 según el tiempo de ejecución), consulte los detalles a continuación,
  6. Implemente la regla dot . para capturar 1 o 2 unidades de código.

Para la mayoría de los puntos podemos lograr ser compatibles con versiones anteriores y generar analizadores muy similares a los anteriores, excepto en el punto 5 porque el resultado de un análisis puede depender de si la regla de los puntos captura una o dos unidades de código. Para esto, propongo agregar una opción de tiempo de ejecución para permitir que el usuario elija entre dos o tres opciones:

  1. La regla de los puntos solo captura una unidad de código BMP,
  2. La regla de puntos captura un punto de código Unicode (1 o 2 unidades de código),
  3. La regla de los puntos captura un punto de código Unicode o un sustituto solitario.

La clase Regex se puede analizar estáticamente durante la generación del analizador para verificar si tienen una longitud fija (en número de unidades de código). Hay 3 casos: 1. solo un BMP o una sola unidad de código, o 2. solo dos unidades de código, o 3. una o dos unidades de código dependiendo del tiempo de ejecución. Por ahora, el código de bytes asume que una clase de expresiones regulares es siempre una unidad de código ( ver aquí ). Con el análisis estático, podríamos cambiar este parámetro de esta instrucción de código de bytes a 1 o 2 para los dos primeros casos. Pero para el tercer caso, supongo que se debe agregar una nueva instrucción de código de bytes para, en tiempo de ejecución, obtener el número de unidades de código coincidentes y aumentar el cursor en consecuencia. Otras opciones sin una nueva instrucción de código de bytes serían: 1. calcular siempre el número de unidades de código coincidentes, pero esto es una penalización de rendimiento durante el análisis para analizadores solo de BMP, por lo que no me gusta esta opción; 2. para calcular si la unidad de código actual es un sustituto alto seguido de un sustituto bajo hasta un incremento de 1 o 2, pero esto supondría que la gramática siempre tiene sustitutos UTF-16 bien formados sin la posibilidad de escribir gramáticas con sustitutos solitarios ( consulte el siguiente punto) y esto también es una penalización de rendimiento para los analizadores solo de BMP.

Está la cuestión de los sustitutos solitarios (sustituto alto sin sustituto bajo después, o sustituto bajo sin sustituto alto antes). Mi opinión sobre esto es que una clase de expresiones regulares debe ser exclusivamente: ya sea con sustitutos solitarios con caracteres Unicode UTF-16 bien formados (BMP o un sustituto alto seguido de un sustituto bajo), de lo contrario, existe el peligro de que los autores gramaticales desconozcan Las sutilezas de UTF-16 mezclan ambos y no entienden el resultado, y los autores de gramática que quieran manejarse por sí mismos sustitutos de UTF-16 pueden hacerlo con las reglas de PEG para describir los vínculos entre sustitutos específicos altos y bajos. Propongo agregar un visitante que haga cumplir esta regla durante la generación del analizador.

Para concluir, probablemente sea más fácil manejar la cuestión de los sustitutos solitarios en PEG que en regex porque el analizador de PEG siempre avanza, por lo que se reconoce la siguiente unidad de código o no lo es, al contrario de las expresiones regulares donde posiblemente se podría asociar algún retroceso o disociar un sustituto alto con un sustituto bajo y, en consecuencia, cambiar el número de caracteres Unicode coincidentes, etc.

El PR para la sintaxis de ES6 para el carácter Unicode astral es # 651 basado en # 616 y el desarrollo de clases es https://github.com/Seb35/pegjs/commit/0d33a7a4e13b0ac7c55a9cfaadc16fc0a5dd5f0c implementando los puntos 2 y 3 en mi comentario anterior, y solo un truco rápido para el incremento del cursor (punto 4) y nada por ahora para la regla punto . (punto 5).

Mi desarrollo actual sobre este problema está casi terminado, el trabajo más avanzado está en https://github.com/Seb35/pegjs/tree/dev-astral-classes-final. Los cinco puntos mencionados anteriormente se tratan y el comportamiento global intenta imitar las expresiones regulares de JS con respecto a los casos extremos (y hay muchos de ellos).

El comportamiento global se rige por la opción unicode similar a la bandera unicode en expresiones regulares JS: el cursor aumenta de 1 carácter Unicode (1 o 2 unidades de código) dependiendo del texto real (p. Ej. [^a] coincide con el texto "💯" y el cursor se incrementa en 2 unidades de código). Cuando la opción unicode es falsa, el cursor siempre aumenta en 1 unidad de código.

Con respecto a la entrada, no estoy seguro de si diseñamos PEG.js de la misma manera que las expresiones regulares de JS: ¿deberíamos autorizar [\u{1F4AD}-\u{1F4AF}] (equivalente a [\uD83D\uDCAD-\uD83D\uDCAF] ) en la gramática en modo no Unicode? Podemos diferenciar entre "entrada Unicode" y "salida Unicode":

  • input Unicode se trata de autorizar todos los caracteres Unicode en las clases de caracteres (que se calcula internamente como una unidad fija de 2 códigos o una unidad fija de 1 código)
  • Unicode de salida es el incremento del cursor del analizador resultante: 1 unidad de código o 1 carácter Unicode para las reglas 'punto' y 'clase de caracteres invertidos' - las únicas reglas donde los caracteres no se enumeran explícitamente y donde necesitamos una decisión de la gramática autor

Personalmente, supongo que preferiría que autoricemos la entrada Unicode, ya sea de forma permanente o con una opción con un valor predeterminado true ya que no hay una sobrecarga significativa y esto habilitaría esta posibilidad para todos de forma predeterminada, pero el Unicode de salida debería permanecer false porque el rendimiento de los analizadores generados es mejor (siempre un incremento de cursor de 1).

Respecto a este tema en general (y sobre la salida Unicode por defecto a false ), debemos tener en cuenta que ya es posible codificar en nuestras gramáticas caracteres Unicode, al precio de comprender el funcionamiento de 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] )

Entonces, un autor de gramática que quiera un analizador rápido y sea capaz de reconocer caracteres Unicode en partes específicas de su gramática puede usar dicha regla. En consecuencia, este problema se trata simplemente de simplificar la administración de Unicode sin sumergirse en los componentes internos de UTF-16.


Sobre la implementación, consideré en mi primer intento que el texto gramatical estaba codificado en caracteres Unicode y que la regla 'punto' del analizador PEG.js reconocía caracteres Unicode. El segundo y último intento revirtió esto (la regla del punto es siempre 1 unidad de código para un análisis más rápido) y hay un pequeño algoritmo en el visitante prepare-unicode-classes.js para reconstruir caracteres Unicode divididos en clases de caracteres (por ejemplo, [\uD83D\uDCAD-\uD83D\uDCAF] se reconoce sintácticamente como [ "\uD83D", [ "\uDCAD", "\uD83D" ], "\uDCAF" ] y este algoritmo lo transforma en [ [ "\uD83D\uDCAD", "\uD83D\uDCAF" ] ] ). Tenía la intención de escribir esto en la propia gramática, pero habría sido largo y, lo que es más importante, hay varias formas de codificar los caracteres ("💯", "uD83DuDCAF", "u {1F4AF}"), por lo que es más fácil escribirlo. en un visitante.

Agregué dos códigos de operación en el segundo intento:

  • MATCH_ASTRAL similar a MATCH_ANY pero que coincide con un carácter Unicode (input.charCodeAt(currPos) & 0xFC00) === 0xD800 && input.length > currPos + 1 && (input.charCodeAt(currPos+1) & 0xFC00) === 0xDC00
  • MATCH_CLASS2 es muy similar a MATCH_CLASS pero coincide con las siguientes dos unidades de código en lugar de solo una classes[c].test(input.substring(currPos, currPos+2)
    Luego, dependiendo de si hacemos coincidir un carácter de unidad de 2 códigos o un carácter de unidad de código 1, el cursor aumenta en 1 o 2 unidades de código con el código ACCEPT_N operación

Hice algunas optimizaciones con la función "match", eliminando durante la generación las rutas de "código muerto" según el modo (Unicode o no) y la clase de caracteres.

Tenga en cuenta también que las expresiones regulares son siempre positivas en esta implementación: las expresiones regulares invertidas devuelven lo contrario, lo que da como resultado el código de bytes. Esto fue más fácil para evitar casos extremos alrededor de sustitutos.

Escribí algo de documentación, pero probablemente agregaré más (tal vez una guía para explicar rápidamente los detalles de las opciones unicode y los fragmentos con la regla de punto Unicode hecha en casa). Agregaré pruebas antes de enviarlo como PR.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

StoneCypher picture StoneCypher  ·  8Comentarios

futagoza picture futagoza  ·  13Comentarios

alanmimms picture alanmimms  ·  10Comentarios

richb-hanover picture richb-hanover  ·  7Comentarios

dmajda picture dmajda  ·  7Comentarios