Pegjs: Capacidad para ignorar ciertas producciones.

Creado en 8 oct. 2010  ·  29Comentarios  ·  Fuente: pegjs/pegjs

Sería bueno poder decirle al lexer / parser que ignore ciertas producciones (es decir, producciones de espacios en blanco y comentarios) para que sea innecesario ensuciar todas las demás producciones con permisos de comentarios / espacios en blanco. Sin embargo, esto puede no ser posible debido al hecho de que el lexing está integrado con el análisis sintáctico.

Gracias

feature

Comentario más útil

@atesgoral -

Así que hice lo que haría cualquier chico debilucho: utilicé expresiones regulares. (Y luego tuve dos problemas :-)

Pero funcionó, así que pude pasar al siguiente desafío. ¡Buena suerte en tu proyecto!

Todos 29 comentarios

Acordado. ¿Existe una forma limpia de hacer esto en este momento?

@benekastah : No hay una forma limpia a partir de ahora.

Esto sería difícil de hacer sin cambiar el funcionamiento de PEG.js. Las posibles soluciones incluyen:

  1. Permitir anteponer un lexer antes del analizador generado.
  2. Inserta información sobre reglas integradas en algún lugar de la gramática. Eso probablemente también significaría distinguir entre el nivel léxico y sintáctico de la gramática, algo que me gustaría evitar.

No trabajaré en esto ahora, pero es algo en lo que pensar en la función.

Necesitaría esta función.

Puede ser que pueda introducir un "saltar" -Token. Entonces, si una regla devuelve ese token, se ignorará y no obtendrá ningún nodo en el AST (también conocido como entrada en la matriz).

También estoy buscando una manera de hacer esto.

Tengo un archivo de gramática grande (analiza el formato ASN.1 para archivos SNMP MIB). No lo escribí, pero lo transformé trivialmente del formulario original para crear un analizador en PEG.js. (Esto es bueno. De hecho, es extremadamente hábil que me tomó menos de 15 minutos modificarlo para que PEG.js lo aceptara).

Desafortunadamente, la gramática se escribió con la capacidad de simplemente ignorar los espacios en blanco y los comentarios cuando los encuentra. En consecuencia, no se pueden manejar archivos MIB reales, porque el analizador se detiene en la primera aparición de espacios en blanco.

No estoy ansioso por tener que descifrar la gramática para poder insertar todos los espacios en blanco adecuados en todas las reglas (hay alrededor de 126 producciones ...) ¿Hay alguna otra forma de hacer esto?

NB: En el caso de que tuviera que modificar la gramática a mano, pedí ayuda con algunas preguntas en un ticket en la lista de Grupos de Google. http://groups.google.com/group/pegjs/browse_thread/thread/568b629f093983b7

¡Muchas gracias!

Gracias a la gente de Grupos de Google. Creo que tengo suficiente información para permitirme hacer lo que quiero.

Pero estoy ansioso por la capacidad de PEG.js para marcar espacios en blanco / comentarios como algo que debe ignorarse por completo para no tener que tomarme unas horas para modificar una gramática que de otro modo sería limpia ... ¡Gracias!

Rico

Estoy de acuerdo con la afirmación de que pegjs necesita la capacidad de omitir tokens. Puedo investigarlo, ya que si quieres escribir una gramática seria te volverás loco al poner ws entre cada token.

Dado que los analizadores generados son modulares. Como solución alternativa, cree un lexer simplista y use su salida como entrada al real, por ejemplo:

elideWS.pegjs:

s = input: (whitepaceCharacter / textCharacter) *
{
var result = "";

for (var i = 0; i <input.length; i ++) result + = input [i];
devolver resultado;
}

whitespaceCharacter = [nt] {return ""; }
textCharacter = c: [^ nt] {return c; }

pero eso causa problemas cuando el espacio en blanco es un delimitador, como para los identificadores

Tropezar con este problema con bastante frecuencia.
Pero no es fácil escribir un buen lexer (puede terminar duplicando una buena parte de la gramática inicial para tener un lexer coherente).

Lo que estaba pensando es poder definir reglas de omisión que se puedan usar como alternativas siempre que no haya coincidencias. Sin embargo, esto introduce la necesidad de una clase sin interrupciones. Ejemplo con arithmetics.pegjs usando flotantes

  = Term (("+" / "-") Term)*

Term
  = Factor (("*" / "/") Factor)*

Factor
  = "(" Expression ")"
  / Float

Float "float"
  = "-"? # [0-9]+ # ("." # [0-9]+) // # means that skip rules cannot match

// skip rule marked by "!="
// skip rules cannot match the empty string
_ "whitespace"
  != [ \t\n\r]+

Aún digiriendo esto. ¿Cualquier retroalimentación? Podría ser una idea muy estúpida.

Entonces, la diferencia es que desea distinguir cuándo el motor general está
operando en modo lexer (el espacio en blanco es significativo) y cuando no (el espacio en blanco es
ignorado).

¿Hay algún caso en el que no desee ignorar los espacios en blanco cuando esté en modo lexer?
¿como una opción? ¿O a la inversa, cuando no está dentro de una expresión regular? Creo que no.

¿Sería equivalente lo siguiente?

Flotador
"-? [0-9] + (". "[0-9] +)”

o extender la clavija para procesar las expresiones regulares típicas directamente y fuera
una cadena entre comillas (que incluye expresiones regulares) espacios en blanco se ignora.

El 19 de abril de 2014, a las 3:22 p.m., Andrei Neculau [email protected] escribió:

Tropezar con este problema con bastante frecuencia.
Pero no es fácil escribir un buen lexer (puede terminar duplicando una buena parte de la gramática inicial para tener un lexer coherente).

Lo que estaba pensando es poder definir reglas de omisión que se puedan usar como alternativas siempre que no haya coincidencias. Sin embargo, esto introduce la necesidad de una clase sin interrupciones. Ejemplo con arithmetics.pegjs usando Floats

Expresión
= Término (("+" / "-") Término) *

Término
= Factor (("_" / "/") Factor) _

Factor
= "(" Expresión ")"
/ Flotar

Flotar "flotar"
= "-"? # [0-9] + # ("." # [0-9] +) // # significa que las reglas de omisión no pueden coincidir

// salta la regla marcada con "! ="
// las reglas de omisión no pueden coincidir con la cadena vacía
_ "espacio en blanco"
! = [tnr] +
Aún digiriendo esto. ¿Cualquier retroalimentación? Podría ser una idea muy estúpida.

-
Responda a este correo electrónico directamente o véalo en GitHub.

@waTeim En realidad, no.

Tradicionalmente, el proceso de análisis se divide en lexing y análisis. Durante el lexing, todos los caracteres son importantes, incluidos los espacios en blanco. Pero estos luego se lexizan en una ficha de "descarte". El analizador, al avanzar al siguiente token, descartará los tokens de descarte. La parte importante es que puede descartar cualquier cosa, no solo los espacios en blanco. Este comportamiento es exactamente lo que describe @andreineculau .

La idea básica de cómo implementar esto es necesitar verificar adicionalmente todas las reglas de descarte al pasar de un estado al siguiente.

El 23 de abril de 2014, a las 2:54 p.m., Sean Farrell [email protected] escribió:

@waTeim En realidad, no.

Entonces estamos de acuerdo. El enfoque tradicional es suficiente. No hay necesidad de tener
la parte del analizador estricto reconoce la existencia de tokens descartados y hay
no hay razón para hacer que la parte lexer se comporte condicionalmente (de una manera sensible al contexto)
wrt reconociendo tokens.

Por lo tanto, no es necesario tener elementos de pegamento (por ejemplo, '#') en el idioma
porque basta que

1) los tokens se pueden crear únicamente a partir de expresiones regulares y no son sensibles al contexto.
2) los tokens pueden marcarse para descartarse sin excepción.

Tradicionalmente, el proceso de análisis se divide en lexing y análisis. Durante el lexing, todos los caracteres son importantes, incluidos los espacios en blanco. Pero estos luego se lexizan en una ficha de "descarte". El analizador, al avanzar al siguiente token, descartará los tokens de descarte. La parte importante es que puede descartar cualquier cosa, no solo los espacios en blanco. Este comportamiento es exactamente lo que describe @andreineculau .

La idea básica de cómo implementar esto es necesitar verificar adicionalmente todas las reglas de descarte al pasar de un estado al siguiente.

-
Responda a este correo electrónico directamente o véalo en GitHub.

Ok, entonces te entendí mal. Puede haber casos para estados lexer, pero ese es un requisito totalmente diferente y en mi humilde opinión fuera del alcance de peg.js.

@waTeim @rioki Olvídate un poco de mi sugerencia.

Manos a la obra, tome esta regla . Si desea simplificar la gramática de la regla eliminando *WS , ¿cómo instruiría a PEGjs para que no permita *WS entre field_name y : ?

@andreineculau Debido a que su gramática es sensible a los espacios en blanco, esto no es aplicable. Los tokens de descarte serían parte de la gramática, la parte de lexing para ser exactos. No sé cuál es el gran problema aquí, esto ya estaba suficientemente resuelto en los años 70. Todos y cada uno de los idiomas tienen sus propios tokens que se pueden omitir y dónde son aplicables. Los espacios en blanco y los comentarios forman parte de la definición del lenguaje y, por tanto, de la gramática. Simplemente resulta que con la mayoría de los idiomas, los tokens que se pueden omitir pueden estar entre todos y cada uno de los demás tokens, y el uso de una regla de descarte lo hace MUCHO más simple que escribir expr = WS lit WS op WS expr WS ";" para cada regla. Imagínense una gramática como la de C con manejo de whitepsace.

Entiendo que convertir las reglas de descarte en pegjs no es fácil, pero eso no significa que no sea un objetivo loable.

¡Oh hombre, sección de respuesta libre! Tengo mucho que decir, lo siento por la extensión.

1) Para la gente de TL; DR, si pudiera agregar cualquier elemento de clavija, quisiera, lo habría escrito así

header_field
= nombre_campo ":" valor_campo

espacio en blanco (IGNORAR)
= [t] +

La adición que haría es una sección de opciones que puede incluirse en cualquier producción.

El lenguaje http-bis no estaría limitado por esta reescritura (ver apéndice a).

2) Mi problema con el # propuesto

Se siente como si estuvieras intercambiando y requiriendo que el usuario complete la definición del analizador con un montón
de descartar no terminales (generalmente espacios en blanco / delimitadores) que requieren que el usuario complete
la definición del analizador con un montón de metacaracteres "aquí los caracteres no se descartan"
innecesariamente. Es cierto que habría menos casos de esto. Es el caso raro cuando
la gente realmente consume delimitadores y hace algo con ellos, y como comento en

el apéndice a, HTTP-bis no es una de esas ocurrencias, solo está mal documentado.

3) Estados del analizador definidos por el usuario

Pero puedo ver cómo sería más fácil para el definidor del analizador simplemente cortar y pegar el
especificación de idioma de la definición, por lo que si debe tener algo como esto, entonces
esto podría hacerse con estados léxicos como aludió Sean anteriormente. Creo que lo haría
de la siguiente manera.

producción1 (estado == 1)
= cosas

producción2 (estado == 2)
= cosas

producción3
= cosas {estado = 1}

producción4
= cosas {estado = 2}

En otras palabras, al igual que lex / yacc hacen posible que las producciones solo estén disponibles

si el sistema está en un estado particular, y permitir que el usuario establezca ese valor de estado.

4) Más opciones

O podría hacerlo más fácil para el usuario y más evidente para el lector con otra
opción

producción (DONTIGNORE)
= cosas

Lo que permitiría al analizador anular la acción predeterminada de descartar tokens marcados
como descarte pero solo para esa producción. Esto es realmente lo mismo que 3, solo que más fácil
leer. Esto es menos flexible que la propuesta # porque se ignora una producción

o no ignorar, pero no creo que se necesite flexibilidad adicional.

5) Agregar un parámetro a getNextToken () permite la sensibilidad al contexto

Creo que todo esto se reduce a (estoy haciendo algunas suposiciones aquí) actualmente, el
la parte del analizador llama a getNextToken (entrada), y lo que debe suceder en su lugar es agregar un

parámetro a él getNextToken (entrada, opciones).

Apéndice a) Esa especificación HTTP-bis

Ok, he leído algunos pero no he leído todo esto

Protocolo de transferencia de hipertexto (HTTP / 1.1): sintaxis y enrutamiento de mensajes
borrador-ietf-httpbis-p1-messaging-26

No me gusta la forma en que han definido su gramática. No sugiero cambiar la entrada
acepta, pero no lo habría definido como lo hicieron ellos. En particular, no me gusta por qué tienen
definió OWS y RWS y BWS que equivalen exactamente a la misma cadena de caracteres
pero en diferentes contextos. Han definido

OWS :: == (SP | HTAB) *
RWS :: == (SP | HTAB) +
BWS :: == OWS

que es solo repetición de pestañas y espacios

Sin una buena razón. Han hecho que el lenguaje sea más difícil de analizar: requieren el analizador léxico
para rastrear su contexto, y no necesitaban hacerlo.

Han definido OWS como "espacio en blanco opcional". BWS como "espacio en blanco incorrecto" u opcional.
espacios en blanco pero en el contexto "malo", donde no es necesario, y RWS requiere espacios en blanco donde es
necesario para delimitar tokens. En ninguna parte se usa este espacio en blanco, excepto quizás haya un analizador
advertencia si coincide con BWS ("se detectó un espacio en blanco final innecesario" o algo así) que es todo
los delimitadores lo hacen de todos modos.

En su especificación, el único lugar donde se usa RWS es aquí

Vía = 1 # (protocolo de recepción RWS recibido por [comentario de RWS])

 received-protocol = [ protocol-name "/" ] protocol-version
                     ; see Section 6.7
 received-by       = ( uri-host [ ":" port ] ) / pseudonym
 pseudonym         = token

pero 'versión de protocolo' son números y tal vez letras, mientras que 'recibido por' son números y letras. En otras palabras,
el analizador léxico no reconocerá correctamente estas 2 partes a menos que estén separadas por espacios en blanco
y será un error de sintaxis con o sin RWS identificado explícitamente si no hay al menos 1
carácter de espacio en blanco. Así que elimine por completo RWS de las producciones y trate los espacios en blanco
en todas partes como un delimitador y no cambia el idioma, solo cómo está documentado.

El 24 de abril de 2014, a las 13:23, Andrei Neculau [email protected] escribió:

@waTeim @rioki Olvídate un poco de mi sugerencia.

Manos a la obra, tome esta regla. Si desea simplificar la gramática de la regla eliminando el OWS, ¿cómo instruiría a PEGjs para que no permita OWS entre field_name y:?

-
Responda a este correo electrónico directamente o véalo en GitHub.

@waTeim Creo que te estás yendo por la borda con esto. He escrito bastantes analizadores y creo que los estados lexer nunca fueron realmente útiles como tales. En la mayoría de los casos, los vi fue donde el lexer consumía comentarios de bloque y era "más sencillo" poner el lexer en "modo de comentario de bloque" y escribir patrones más simples que el patrón über para consumir el comentario (y contar líneas).

Nunca he visto un uso adecuado de los estados lexer derivados del analizador sintáctico. El problema fundamental aquí es que con una mirada hacia adelante, cuando el analizador ve el token para cambiar de estado, el lexer ya ha lexado erróneamente el siguiente token. Lo que propone es casi imposible de implementar sin retroceso y esa nunca es una buena característica en un analizador.

Al escribir una gramática, básicamente define qué producciones se consideran analizadas y qué se pueden sorber. En el ejemplo de @andreineculau hay dos opciones, o maneja espacios en blanco en el analizador o define la parte final ":" del token. ( [a-zA-Z0-9!#$%&'+-.^_|~]+ ":" ).

Podría sugerir convertir el problema en especificar una lista blanca (qué partes quiero capturar y transformar) en lugar de una lista negra. Aunque el espacio en blanco es un problema con el sistema de captura actual, el anidamiento de reglas es otro. Como escribí en el número 66, el sistema LPeg de especificar lo que desea capturar directamente, a través de transformaciones o capturas de cadenas, me parece más útil que especificar un puñado de producciones para omitir y aún lidiar con el anidamiento de todas las demás producciones.

Consulte mi comentario en el número 66 para ver un ejemplo simple de LPeg frente a PEG.js con respecto a las capturas. Aunque los nombres son un poco crípticos, consulte la sección Capturas de la documentación de LPeg para conocer las diversas formas en que puede capturar o transformar una producción determinada (o parte de ella).

Hola, he creado un fragmento para ignorar algunos casos generales: null , undefined y cadenas con solo símbolos de espacio.
Puede ser requerido en el encabezado del archivo de gramática, como:

{
  var strip = require('./strip-ast');
}

Las dos formas de mejorarlo:

  • Filtro personalizable para términos: para ignorar los términos específicos que requieren cierta gramática.
  • Omitir matrices vacías anidadas: esto se puede hacer en la segunda etapa después de strip , eliminará las «pirámides» de las matrices vacías anidadas.
    Si alguien está interesado, podemos actualizarlo a un paquete.

@ richb-hanover ¿Dónde aterrizaron sus esfuerzos de analizador de definición ASN.1?

@atesgoral -

Así que hice lo que haría cualquier chico debilucho: utilicé expresiones regulares. (Y luego tuve dos problemas :-)

Pero funcionó, así que pude pasar al siguiente desafío. ¡Buena suerte en tu proyecto!

Habiendo echado un vistazo a

Con demasiada frecuencia nos encontramos escribiendo algo como esto:

Pattern = head:PatternPart tail:( WS "," WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: buildList( head, tail, 3 )
  };
}

Sería genial si pudiéramos escribir esto en su lugar:

WS "whitespace" = [ \t\n\r] { return '@<strong i="11">@skipped</strong>' }

IgnoredComma = "," { return '@<strong i="12">@skipped</strong>' }

Pattern = head:PatternPart tail:( WS IgnoredComma WS PatternPart )*
{
  return {
    type: 'pattern',
    elements: [head].concat(tail)
  };
}

@ richb-hanover, y cualquier otra persona que llegó aquí en busca de una necesidad similar, terminé escribiendo mis propios analizadores también: https://www.npmjs.com/package/asn1exp y https: //www.npmjs. com / paquete / asn1-tree

Un salto sería relativamente fácil de implementar usando es6 symbol , o tal vez de manera más duradera pasando al analizador un predicado en el momento del análisis (prefiero la última opción)

También me topé con esto.
Sin saber nada sobre las entrañas de PEG.js, déjame lanzar un hueso por ahí ...

Cuando escribimos una regla, al final de la misma podemos agregar un bloque de retorno.
En ese bloque, podemos llamar a cosas como text() y location() . Estas son funciones internas.

En algún lugar del código, el valor devuelto de ese bloque entra en el flujo de salida.

Entonces, ¿qué debería cambiar en PEG.js si quiero omitir un valor devuelto por una regla si ese valor es el retorno de llamar a una función local skip ?

por ejemplo, comment = "//" space ([^\n])* newline { return skip() }

Como se mencionó anteriormente, skip () podría devolver un símbolo, que luego es verificado por el código en algún lugar y eliminado.
Algo como lo que dijo lzhaki, pero interno a la biblioteca.

No entiendo tu pregunta. ¿Está buscando una forma de fallar una regla en algunas circunstancias? Utilice &{...} o !{...} . De lo contrario, simplemente no use el valor devuelto de la regla comment :

seq = comment r:another_rule { return r; };
choice = (comment / another_rule) { <you need to decide what to return instead of "comment" result> };

Si ayuda a alguien, ignoro los espacios en blanco haciendo que mi regla de nivel superior filtre la matriz de resultados.

Ejemplo:

    = prog:expression+ {return prog.filter(a => a)}

expression
    = float
    / number
    / whitespace

float
    = digits:(number"."number) {return parseFloat(digits.join(""),10)}

number 
    = digits:digit+ {return parseInt(digits.join(""),10)}

digit 
    = [0-9]

whitespace
    = [ \t\r\n] {return undefined}

Esto analizará felizmente la entrada mientras mantiene los espacios en blanco fuera de la matriz de resultados.
Esto también funcionará para cosas como comentarios, solo haga que la regla regrese indefinida y la regla de nivel superior la filtrará

Eso solo funciona para producciones de alto nivel. Debe filtrar manualmente todos los padres que podrían contener un hijo filtrable.

@StoneCypher Es cierto, requiere un trabajo de alto nivel, pero funciona para mí, y creo que mientras el gammar no sea demasiado complejo, uno debería poder salirse con la suya con un filtro de nivel superior.

Aparte de eso, todo lo que puedo pensar es tener una función de nivel superior que filtre los espacios en blanco de la entrada y pase todas las coincidencias a través de ella. Más lento seguro, y requiere muchas más llamadas, pero fácil si usted (como yo) pasa todo a un generador de tokens. Puede llamar a la función de filtro desde donde genera tokens, y solo tiene que preocuparse por generar sus tokens y el espacio en blanco se filtra más o menos automáticamente

Una de las cosas que me gustó del HEAD actual de pegjs es su soporte (no documentado) para seleccionar campos sin tener que crear etiquetas y hacer declaraciones de retorno. Se parece a esto:

foo = <strong i="6">@bar</strong> _ <strong i="7">@baz</strong>
bar = $"bar"i
baz = $"baz"i
_ = " "*
parse('barbaz') // returns [ 'bar', 'baz' ]

Siento que esto proporciona una sintaxis agradable, limpia y explícita para este caso de uso y muchos otros.

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

Temas relacionados

StoneCypher picture StoneCypher  ·  8Comentarios

emmenko picture emmenko  ·  15Comentarios

futagoza picture futagoza  ·  6Comentarios

doersino picture doersino  ·  15Comentarios

futagoza picture futagoza  ·  13Comentarios