En informática , una gramática de expresiones de análisis ( PEG ) es un tipo de gramática formal analítica , es decir, describe un lenguaje formal en términos de un conjunto de reglas para reconocer cadenas en el lenguaje. El formalismo fue introducido por Bryan Ford en 2004 [ 1 ] y está estrechamente relacionado con la familia de lenguajes de análisis descendente introducidos a principios de la década de 1970. Sintácticamente, las PEG también se parecen a las gramáticas libres de contexto (CFG), pero tienen una interpretación diferente: el operador de elección selecciona la primera coincidencia en PEG, mientras que es ambiguo en CFG. Esto se acerca más a cómo se suele hacer el reconocimiento de cadenas en la práctica, por ejemplo, mediante un analizador descendente recursivo .
A diferencia de las CFG, las PEG no pueden ser ambiguas ; una cadena tiene exactamente un árbol de análisis válido o ninguno. Se conjetura que existen lenguajes libres de contexto que no pueden ser reconocidos por una PEG, pero esto aún no se ha demostrado. [ 1 ] Las PEG son adecuadas para analizar lenguajes de computadora (y lenguajes humanos artificiales como Lojban ) donde se pueden desambiguar localmente múltiples alternativas de interpretación, pero es menos probable que sean útiles para analizar lenguajes naturales donde la desambiguación puede tener que ser global. [ 2 ]
Definición
Una expresión de análisis sintáctico es un tipo de patrón que cada cadena puede coincidir o no . En caso de coincidencia, existe un prefijo único de la cadena (que puede ser la cadena completa, la cadena vacía o algo intermedio) que ha sido procesado por la expresión de análisis sintáctico; este prefijo es lo que normalmente se considera que coincide con la expresión. Sin embargo, la coincidencia de una cadena con una expresión de análisis sintáctico puede depender (debido a los predicados de anticipación) de las partes que siguen a la parte procesada. Un lenguaje de expresiones de análisis sintáctico es un conjunto de todas las cadenas que coinciden con una expresión de análisis sintáctico específica. [ 1 ] : Sec.3.4
Una gramática de expresiones de análisis sintáctico es una colección de expresiones de análisis sintáctico con nombre, que pueden referenciarse entre sí. El efecto de una de estas referencias en una expresión de análisis sintáctico es como si se incluyera toda la expresión referenciada en lugar de la referencia. Una gramática de expresiones de análisis sintáctico también tiene una expresión inicial designada ; una cadena coincide con la gramática si coincide con su expresión inicial.
Un elemento de una cadena que coincide con un elemento se denomina símbolo terminal , o simplemente terminal . Del mismo modo, los nombres asignados a las expresiones de análisis sintáctico se denominan símbolos no terminales , o simplemente no terminales . Estos términos serían descriptivos para las gramáticas generativas , pero en el caso de las gramáticas de expresiones de análisis sintáctico son simplemente terminología, mantenida principalmente por su uso casi omnipresente en las discusiones sobre algoritmos de análisis sintáctico .
Sintaxis
En la literatura y en este artículo se observan sintaxis tanto abstractas como concretas para el análisis de expresiones. La sintaxis abstracta es esencialmente una fórmula matemática y se utiliza principalmente en contextos teóricos, mientras que la sintaxis concreta permite controlar directamente un analizador . La sintaxis concreta principal es la definida por Ford [ 1 ] ( Fig. 1) , aunque muchas herramientas tienen su propia variante. Otras herramientas [ 3 ] se asemejan más al uso de una codificación nativa del lenguaje de programación para la sintaxis abstracta como sintaxis concreta.
Expresiones de análisis atómico
Los dos tipos principales de expresiones de análisis sintáctico que no contienen otra expresión de análisis sintáctico son los símbolos terminales individuales y los símbolos no terminales. En la sintaxis concreta, los terminales se colocan entre comillas (simples o dobles), mientras que los identificadores que no van entre comillas denotan no terminales:
"terminal" No terminal 'otra terminal'En la sintaxis abstracta no existe una distinción formalizada; en cambio, se supone que cada símbolo se define como terminal o no terminal, pero una convención común es usar mayúsculas para los no terminales y minúsculas para los terminales.
La sintaxis concreta también tiene varias formas para clases de terminales:
- Un
.(punto) es una expresión de análisis que coincide con cualquier terminal único. - Los corchetes que rodean una lista de caracteres
[abcde]forman una expresión de análisis sintáctico que coincide con uno de los caracteres numerados. Al igual que en las expresiones regulares , estas clases también pueden incluir rangos[0-9A-Za-z]escritos como un guion con los extremos del rango antes y después. (A diferencia de las expresiones regulares, las clases de caracteres entre corchetes no admiten^la negación; esta se puede obtener mediante predicados de negación). - Algunos dialectos tienen notación adicional para clases de caracteres predefinidas, como letras, dígitos, signos de puntuación o espacios; esto es similar a la situación en las expresiones regulares.
En la sintaxis abstracta, dichas formas suelen formalizarse como no terminales cuya definición exacta se omite por brevedad; en Unicode, existen decenas de miles de caracteres que son letras. Por el contrario, en las discusiones teóricas a veces se introduce una sintaxis abstracta atómica para conceptos que también pueden expresarse mediante expresiones de análisis sintáctico compuestas. Algunos ejemplos son:
- la cadena vacía ε (como expresión de análisis, coincide con cualquier cadena y no consume caracteres),
- fin de la entrada E (el equivalente sintáctico concreto es
!.), y - falla(no coincide con nada).
En la sintaxis concreta, los terminales entre comillas y corchetes tienen escapes de barra invertida , de modo que " salto de línea o retorno de carro " puede escribirse [\n\r]. La contraparte de la sintaxis abstracta de un terminal entre comillas de longitud mayor que uno sería la secuencia de esos terminales; "bar"es lo mismo que "b" "a" "r". La sintaxis concreta primaria no asigna un significado distinto a los terminales dependiendo de si usan comillas simples o dobles, pero algunos dialectos tratan uno como sensible a mayúsculas y minúsculas y el otro como insensible a mayúsculas y minúsculas.
Expresiones de análisis compuesto
Dadas cualesquiera expresiones de análisis existentes e , e1 y e2 , se puede construir una nueva expresión de análisis utilizando los siguientes operadores :
- Secuencia : e 1 e 2
- Elección ordenada : e 1 / e 2
- Cero o más : e *
- Uno o más : e +
- Opcional : e ?
- Y predicado : & e
- No predicado : ! e
- Grupo : ( e )
Las prioridades de los operadores son las siguientes, según la Tabla 1 en: [ 1 ]
Gramáticas
En la sintaxis concreta, una gramática de expresiones de análisis sintáctico es simplemente una secuencia de definiciones no terminales, cada una de las cuales tiene la forma
Identificador LEFTARROW ExpresiónEl Identifieres el no terminal que se está definiendo, y Expressiones la expresión de análisis sintáctico a la que hace referencia. El LEFTARROWvaría un poco entre dialectos, pero generalmente es una flecha que apunta hacia la izquierda o un símbolo de asignación, como <-, ←, :=, o =. Una forma de entenderlo es precisamente como hacer una asignación o definición del no terminal. Otra forma de entenderlo es como un contraste con la flecha que apunta hacia la derecha → utilizada en las reglas de una gramática libre de contexto ; con las expresiones de análisis sintáctico, el flujo de información va de la expresión al no terminal, no del no terminal a la expresión.
Como objeto matemático , una gramática de expresión de análisis sintáctico es una tupla., dóndees el conjunto de símbolos no terminales,es el conjunto de símbolos terminales,es una función deal conjunto de expresiones de análisis en, yes la expresión de análisis inicial. Algunos dialectos de sintaxis concreta dan la expresión inicial explícitamente, [ 4 ] pero la sintaxis concreta primaria tiene en cambio la regla implícita de que el primer no terminal definido es la expresión inicial.
Vale la pena señalar que el dialecto primario de las gramáticas de expresiones de análisis sintáctico concreto no tiene un terminador de definición explícito o separador entre definiciones, aunque es costumbre comenzar una nueva definición en una nueva línea; el LEFTARROWde la siguiente definición es suficiente para encontrar el límite, si se agrega la restricción de que un no terminal en un Expressionno debe ir seguido de un LEFTARROW. Sin embargo, algunos dialectos pueden permitir un terminador explícito, o directamente requerirlo [ 4 ] .
Ejemplo
Se trata de un PEG que reconoce fórmulas matemáticas que aplican las cinco operaciones básicas a números enteros no negativos.
Expr ← Suma Suma ← Producto (( '+' / '-' ) Producto ) * Producto ← Potencia (( '*' / '/' ) Potencia ) * Potencia ← Valor ( '^' Potencia ) ? Valor ← [ 0-9 ] + / '(' Expr ')'En el ejemplo anterior, los símbolos terminales son caracteres de texto, representados por caracteres entre comillas simples, como '('y ')'. El rango [0-9]es un atajo para los diez caracteres desde '0'hasta '9'. (Esta sintaxis de rango es la misma que la sintaxis utilizada por las expresiones regulares ). Los símbolos no terminales son los que se expanden a otras reglas: Valor , Potencia , Producto , Suma y Expr . Nótese que las reglas Suma y Producto no conducen a la asociatividad izquierda deseada de estas operaciones (no manejan la asociatividad en absoluto, y debe manejarse en el paso de posprocesamiento después del análisis), y la regla Potencia (al referirse a sí misma a la derecha) da como resultado la asociatividad derecha deseada del exponente. También tenga en cuenta que una regla como (con la intención de lograr la asociatividad izquierda) causaría una recursión infinita, por lo que no se puede usar en la práctica aunque se pueda expresar en la gramática.Sum←Sum(('+'/'-')Product)?
Semántica
La diferencia fundamental entre las gramáticas libres de contexto y las gramáticas de expresiones de análisis sintáctico radica en que el operador de elección de las gramáticas de expresiones de análisis sintáctico es ordenado . Si la primera alternativa tiene éxito, la segunda se ignora. Por lo tanto, la elección ordenada no es conmutativa , a diferencia de la elección no ordenada presente en las gramáticas libres de contexto. La elección ordenada es análoga a los operadores de corte suave disponibles en algunos lenguajes de programación lógica .
La consecuencia es que, si una gramática libre de contexto (GLC) se translitera directamente a una gramática de pares de expresiones (PEG), cualquier ambigüedad en la primera se resuelve seleccionando de forma determinista un árbol de análisis sintáctico entre los posibles. Al elegir cuidadosamente el orden en que se especifican las alternativas gramaticales, el programador tiene un gran control sobre qué árbol de análisis sintáctico se selecciona.
Las gramáticas de expresiones de análisis sintáctico también añaden los predicados sintácticos " y" y "no" . Dado que pueden utilizar una subexpresión arbitrariamente compleja para "anticipar" la cadena de entrada sin consumirla realmente, proporcionan una potente función de anticipación sintáctica y desambiguación, en particular cuando la reordenación de las alternativas no permite especificar el árbol de análisis sintáctico exacto deseado.
Interpretación operacional de expresiones de análisis sintáctico
Cada no terminal en una gramática de expresión de análisis representa esencialmente una función de análisis en un analizador descendente recursivo , y la expresión de análisis correspondiente representa el "código" que compone la función. Cada función de análisis conceptualmente toma una cadena de entrada como argumento y produce uno de los siguientes resultados:
- éxito , en el que la función puede opcionalmente avanzar o consumir uno o más caracteres de la cadena de entrada que se le proporciona, o
- fallo , en cuyo caso no se consume ninguna entrada.
Una expresión de análisis atómico que consta de un único terminal (es decir, un literal) se ejecuta correctamente si el primer carácter de la cadena de entrada coincide con dicho terminal; en ese caso, se consume el carácter de entrada. De lo contrario, la expresión produce un error. Una expresión de análisis atómico que consiste en una cadena vacía siempre se ejecuta correctamente sin consumir ninguna entrada.
Una expresión de análisis atómico que consiste en un no terminal A representa una llamada recursiva a la función no terminal A. Un no terminal puede tener éxito sin consumir realmente ninguna entrada, y esto se considera un resultado distinto del fallo.
El operador de secuencia e 1 e 2 primero invoca e 1 y, si e 1 tiene éxito, invoca posteriormente e 2 sobre el resto de la cadena de entrada que no fue procesada por e 1 y devuelve el resultado. Si e 1 o e 2 fallan, la expresión de secuencia e 1 e 2 falla (no consume ninguna entrada).
El operador de elección e 1 / e 2 primero invoca a e 1 y, si e 1 tiene éxito, devuelve su resultado inmediatamente. De lo contrario, si e 1 falla, el operador de elección retrocede a la posición de entrada original donde invocó a e 1 , pero luego llama a e 2 y devuelve el resultado de e 2 .
Los operadores zero-or-more , one-or-more y optional consumen cero o más, una o más, o cero o una repeticiones consecutivas de su subexpresión e , respectivamente. Sin embargo, a diferencia de las gramáticas libres de contexto y las expresiones regulares , estos operadores siempre se comportan de manera voraz , consumiendo la mayor cantidad de entrada posible y sin retroceder nunca. (Los comparadores de expresiones regulares pueden comenzar comparando de manera voraz, pero luego retrocederán e intentarán comparaciones más cortas si no coinciden). Por ejemplo, la expresión a* siempre consumirá tantas 'a' como estén disponibles consecutivamente en la cadena de entrada, y la expresión (a* a) siempre fallará porque la primera parte (a*) nunca dejará ninguna 'a' para que la segunda parte la compare.
La expresión de predicado y & e invoca la subexpresión e , y luego tiene éxito si e tiene éxito y falla si e falla, pero en ningún caso consume ninguna entrada .
La expresión de no predicado ! e tiene éxito si e falla y falla si e tiene éxito, sin consumir de nuevo ninguna entrada en ninguno de los casos.
Más ejemplos
La siguiente regla recursiva coincide con las sentencias if/then/else estándar de estilo C, de tal manera que la cláusula "else" opcional siempre se vincula al "if" más interno, debido a la priorización implícita del operador "/". (En una gramática libre de contexto , esta construcción produce la clásica ambigüedad del "else" colgante ).
S ← 'si' C 'entonces' S 'si no' S / 'si' C 'entonces' SLa siguiente regla recursiva coincide con la sintaxis de comentarios anidados al estilo Pascal, (* which can (* nest *) like this *). Recuerde que .coincide con cualquier carácter individual.
C ← Inicio N * Fin Inicio ← '(*' Fin ← '*)' N ← C / ( ! Inicio ! Fin . )La expresión de análisis coincide con el texto "foo" y lo consume, pero solo si va seguido del texto "bar". La expresión de análisis coincide con el texto "foo", pero solo si no va seguido del texto "bar". La expresión coincide con una sola "a", pero solo si no forma parte de una secuencia arbitrariamente larga de "a" seguida de una "b".foo&(bar)foo!(bar)!(a+b)a
La expresión de análisis coincide y consume una secuencia de longitud arbitraria de('a'/'b')*'arenaLa regla de producción coincide con el lenguaje libre de contexto simple .S←('a'S'b')?.
La siguiente gramática de expresiones de análisis describe el lenguaje clásico no libre de contexto.: [ 5 ]
S ← & ( A ! ( 'a' / 'b' )) 'a' * B ! . A ← ( 'a' A 'b' ) ? B ← ( 'b' B 'c' ) ?Implementación de analizadores sintácticos a partir del análisis de gramáticas de expresiones.
Cualquier gramática de expresión de análisis puede convertirse directamente en un analizador descendente recursivo . [ 6 ] Sin embargo, debido a la capacidad de anticipación ilimitada que proporciona el formalismo gramatical, el analizador resultante podría presentar un rendimiento de tiempo exponencial en el peor de los casos.
Es posible obtener un mejor rendimiento para cualquier gramática de expresión de análisis convirtiendo su analizador descendente recursivo en un analizador packrat , que siempre se ejecuta en tiempo lineal , a costa de requisitos de espacio de almacenamiento sustancialmente mayores. Un analizador packrat [ 6 ] es una forma de analizador similar a un analizador descendente recursivo en su construcción, excepto que durante el proceso de análisis memoriza los resultados intermedios de todas las invocaciones de las funciones de análisis mutuamente recursivas , asegurando que cada función de análisis se invoque como máximo una vez en una posición de entrada dada. Debido a esta memorización, un analizador packrat tiene la capacidad de analizar muchas gramáticas libres de contexto y cualquier gramática de expresión de análisis (incluidas algunas que no representan lenguajes libres de contexto) en tiempo lineal. Se conocen ejemplos de analizadores descendentes recursivos memorizados desde al menos 1993. [ 7 ] Este análisis del rendimiento de un analizador packrat supone que hay suficiente memoria disponible para almacenar todos los resultados memorizados; En la práctica, si no hay suficiente memoria, algunas funciones de análisis sintáctico podrían tener que invocarse más de una vez en la misma posición de entrada y, en consecuencia, el analizador podría tardar más de un tiempo lineal.
También es posible construir analizadores LL y LR a partir de gramáticas de expresiones sintácticas, con un rendimiento en el peor de los casos superior al de un analizador descendente recursivo sin memorización, pero se pierde la capacidad de anticipación ilimitada del formalismo gramatical. Por lo tanto, no todos los lenguajes que pueden expresarse mediante gramáticas de expresiones sintácticas pueden ser analizados por analizadores LL o LR.
Análisis PEG ascendente
Un analizador sintáctico Pika [ 8 ] utiliza programación dinámica para aplicar las reglas PEG de abajo hacia arriba y de derecha a izquierda, lo cual es lo contrario del orden de descenso recursivo normal de arriba hacia abajo y de izquierda a derecha. El análisis sintáctico en orden inverso resuelve el problema de la recursión izquierda, lo que permite que las reglas recursivas izquierdas se utilicen directamente en la gramática sin tener que reescribirlas en una forma no recursiva izquierda, y también confiere al analizador capacidades óptimas de recuperación de errores, algo que históricamente ha resultado difícil de lograr para los analizadores sintácticos de descenso recursivo.
Ventajas
No se requiere compilación
Muchos algoritmos de análisis sintáctico requieren un paso de preprocesamiento en el que la gramática se compila primero en un formato ejecutable opaco, a menudo mediante algún tipo de autómata. Las expresiones de análisis sintáctico se pueden ejecutar directamente (aunque normalmente sigue siendo recomendable transformar las expresiones de gramática sintáctica legibles por humanos que se muestran en este artículo a un formato más nativo, como las expresiones S , antes de evaluarlas).
En comparación con las expresiones regulares
En comparación con las expresiones regulares puras (es decir, describir un lenguaje reconocible mediante un autómata finito ), los PEG son muchísimo más potentes. En particular, pueden manejar recursión ilimitada y, por lo tanto, hacer coincidir paréntesis hasta una profundidad de anidamiento arbitraria; las expresiones regulares, en el mejor de los casos, pueden realizar un seguimiento del anidamiento hasta una profundidad fija, porque un autómata finito (que tiene un conjunto finito de estados internos) solo puede distinguir un número finito de profundidades de anidamiento diferentes. En términos más teóricos,(el lenguaje de todas las cadenas de cero o más's, seguido de un número igual des) no es un lenguaje regular , pero se ve fácilmente que es un lenguaje de expresiones de análisis sintáctico, que coincide con la gramática.
inicio ← AB ! . AB ← ( 'a' AB 'b' ) ?Aquí está la expresión inicial. La parte garantiza que la entrada termine después de , al decir "no hay siguiente carácter"; a diferencia de las expresiones regulares, que tienen restricciones mágicas o para esto, las expresiones de análisis pueden expresar el final de la entrada usando solo las primitivas básicas.AB !.!.AB$\Z
Las expresiones de análisis sintáctico son similares a las de las expresiones regulares, pero la diferencia radica en que operan estrictamente en modo voraz. Esto se debe, en última instancia, a que *se trata de una elección ordenada. Como consecuencia, algo puede coincidir como expresión regular pero no como expresión de análisis sintáctico:+?/
[ab]?[bc][cd]
es una expresión regular válida y una expresión de análisis válida. Como expresión regular, coincide con bc, pero como expresión de análisis no coincide, porque [ab]?coincidirá con b, luego [bc]coincidirá con c, sin dejar nada para [cd], por lo que en ese punto la coincidencia de la secuencia falla. "Intentarlo de nuevo" haciendo que [ab]?coincida con la cadena vacía va explícitamente en contra de la semántica de las expresiones de análisis; este no es un caso límite de un algoritmo de coincidencia particular, sino que es el comportamiento buscado.
Incluso las expresiones regulares que dependen del no determinismo pueden compilarse en una gramática de expresiones de análisis sintáctico, al tener un no terminal separado para cada estado del autómata finito no determinista correspondiente (NFA, por sus siglas en inglés) y codificar su función de transición en las definiciones de estos no terminales.
A ← 'x' B / 'x' C / 'y' DEn esencia, significa "transición del estado A al estado B o C si el siguiente carácter es x, o al estado D si el siguiente carácter es y". No utilizaría las variantes de expresión de análisis sintáctico de las operaciones de repetición.
Para aceptar estados del NFA, la definición del no terminal debe estar entre ?. En cuanto a los estados de entrada del NFA, todos deben estar listados, separados por /, en la definición del no terminal inicial.
Cabe señalar que, si bien es posible transformar una expresión regular en un autómata finito determinista (AFD) en lugar de un autómata finito no determinista (AFND), esto debe evitarse , ya que el equivalente en AFD de una expresión regular puede ser exponencialmente mayor. De hecho, existe una secuencia de expresiones regulares cuyos equivalentes en AFD son todos exponencialmente mayores.
En comparación con las gramáticas libres de contexto
Las PEG se pueden dar cómodamente en términos de caracteres, mientras que las gramáticas libres de contexto (GLC) generalmente se dan en términos de tokens, lo que requiere un paso adicional de tokenización antes del análisis propiamente dicho. [ 9 ] Una ventaja de no tener un tokenizador separado es que diferentes partes del lenguaje (por ejemplo, minilenguajes incrustados ) pueden tener fácilmente diferentes reglas de tokenización.
En el sentido formal estricto, las PEG son probablemente incomparables con las CFG, pero en la práctica hay muchas cosas que las PEG pueden hacer que las CFG puras no pueden, mientras que es difícil encontrar ejemplos de lo contrario. En particular, las PEG se pueden diseñar para resolver ambigüedades de forma nativa, como el problema del " else colgante " en C, C++ y Java, mientras que el análisis sintáctico basado en CFG a menudo necesita una regla externa a la gramática para resolverlas. Además, cualquier PEG se puede analizar en tiempo lineal usando un analizador packrat, como se describió anteriormente, mientras que el análisis sintáctico según una CFG general es asintóticamente equivalente [ 10 ] a la multiplicación de matrices booleanas (por lo tanto, probablemente entre tiempo cuadrático y cúbico).
Un ejemplo clásico de un lenguaje formal que se demuestra que no está libre de contexto es el lenguaje: un número arbitrario deLos 's van seguidos de un número igual de's, que a su vez van seguidas de un número igual de's. Esto también es un lenguaje de expresiones de análisis sintáctico, que coincide con la gramática: [ 5 ]
S ← & ( A ! ( 'a' / 'b' )) 'a' * B ! . A ← ( 'a' A 'b' ) ? B ← ( 'b' B 'c' ) ?Para Aque coincida, el primer tramo de's debe ir seguido exactamente del mismo número de's y nada más, y además Btiene que coincidir donde elcambio dede, lo que significa esosLos 's van seguidos de un número igual de's.
Desventajas
consumo de memoria
El análisis PEG se realiza típicamente mediante el análisis packrat , que utiliza memorización [ 11 ] [ 12 ] para eliminar pasos de análisis redundantes. El análisis packrat requiere almacenamiento interno proporcional al tamaño total de la entrada, en lugar de a la profundidad del árbol de análisis como con los analizadores LR. Si esta es una diferencia significativa depende de las circunstancias; si el análisis es un servicio proporcionado como una función , entonces el analizador habrá almacenado el árbol de análisis completo hasta que lo devuelva, y ese árbol de análisis ya será típicamente de un tamaño proporcional al tamaño total de la entrada. Si el análisis se proporciona como un generador , entonces se podría permitir mantener solo partes del árbol de análisis en memoria, pero la viabilidad de esto depende de la gramática. Una gramática de expresión de análisis puede diseñarse de manera que solo después de consumir la entrada completa el analizador descubra que necesita retroceder al principio, [ 13 ] lo que nuevamente podría requerir almacenamiento proporcional al tamaño total de la entrada.
Para gramáticas recursivas y algunas entradas, la profundidad del árbol de análisis puede ser proporcional al tamaño de la entrada, [ 14 ] por lo que tanto un analizador LR como un analizador packrat parecerán tener el mismo rendimiento asintótico en el peor de los casos. Sin embargo, en muchos dominios, por ejemplo, el código fuente escrito a mano , la profundidad de anidamiento de expresiones tiene un límite prácticamente constante, bastante independiente de la longitud del programa, porque las expresiones anidadas más allá de cierta profundidad tienden a ser refactorizadas . Cuando no es necesario mantener el árbol de análisis completo, un análisis más preciso tendría en cuenta la profundidad del árbol de análisis por separado del tamaño de la entrada. [ 15 ]
Modelo computacional
Para lograr una complejidad global lineal, el almacenamiento utilizado para la memorización debe proporcionar, además, acceso amortizado en tiempo constante a los elementos de datos memorizados. En la práctica, esto no supone ningún problema (por ejemplo, una tabla hash de tamaño dinámico lo consigue), pero esto utiliza aritmética de punteros , por lo que presupone una máquina de acceso aleatorio . Las discusiones teóricas sobre estructuras de datos y algoritmos tienden implícitamente a presuponer un modelo más restringido (posiblemente el del cálculo lambda , o quizás el de Scheme ), donde una tabla dispersa debe construirse utilizando árboles, y el acceso a los elementos de datos no es en tiempo constante. Los algoritmos de análisis sintáctico tradicionales, como el analizador LL, no se ven afectados por esto, pero se convierte en un inconveniente para la reputación de los analizadores que almacenan datos en exceso: se basan en operaciones aparentemente de mala reputación.
Visto desde otra perspectiva, esto significa que los analizadores sintácticos Packrat aprovechan la potencia computacional fácilmente disponible en los sistemas de la vida real, que los algoritmos de análisis sintáctico más antiguos no saben cómo utilizar.
Recursión izquierda indirecta
Una PEG se considera bien formada [ 1 ] si no contiene reglas recursivas por la izquierda , es decir, reglas que permiten que un no terminal se expanda a una expresión en la que el mismo no terminal aparece como el símbolo más a la izquierda. Para un analizador sintáctico descendente de izquierda a derecha, tales reglas provocan una regresión infinita: el análisis expandirá continuamente el mismo no terminal sin avanzar en la cadena. Por lo tanto, para permitir el análisis packrat, debe eliminarse la recursión por la izquierda.
Importancia práctica
La recursión directa, ya sea por la izquierda o por la derecha, es importante en las gramáticas libres de contexto, porque allí la recursión es la única forma de describir la repetición:
Suma → Término | Suma '+' Término | Suma '-' Término Argumentos → Arg | Arg ',' ArgumentosLas personas capacitadas en el uso de gramáticas libres de contexto a menudo llegan a las PEG esperando usar los mismos modismos, pero el análisis de expresiones puede realizar repeticiones sin recursión:
Suma ← Término ( '+' Término / '-' Término ) * Argumentos ← Argumento ( ',' Argumento ) *La diferencia radica en los árboles de sintaxis abstracta generados: con recursión, cada uno Sumpuede Argstener como máximo dos hijos, pero con repetición puede haber un número arbitrario de ellos. Si las etapas posteriores del procesamiento requieren que dichas listas de hijos se reformulen como árboles con grado limitado , por ejemplo, las instrucciones de suma del microprocesador normalmente solo permiten dos operandos, entonces se impondrían propiedades como la asociatividad izquierda después de la etapa de análisis sintáctico dirigida por PEG.
Por lo tanto, es prácticamente menos probable que la recursión izquierda cause problemas a un analizador sintáctico PEG packrat que, por ejemplo, a un analizador sintáctico libre de contexto LL(k), a menos que se insista en utilizar modismos libres de contexto. Sin embargo, no todos los casos de recursión se refieren a la repetición.
Recursión izquierda sin repetición
Por ejemplo, en la gramática aritmética anterior, podría parecer tentador expresar la precedencia de operadores como una cuestión de elección ordenada —lo Sum / Product / Valueque significaría intentar primero ver como Sum(ya que analizamos de arriba hacia abajo), intentar segundo ver como Product, y solo intentar tercero ver como Value—en lugar de mediante el anidamiento de definiciones. Esta gramática (no bien formada) busca mantener el orden de precedencia solo en una línea:
Valor ← [ 0-9. ] + / '(' Expr ')' Producto ← Expr (( '*' / '/' ) Expr ) + Suma ← Expr (( '+' / '-' ) Expr ) + Expr ← Suma / Producto / ValorDesafortunadamente, hacer coincidir un Exprrequiere comprobar si un Sumcoincide, mientras que hacer coincidir un Sumrequiere comprobar si un Exprcoincide. Debido a que el término aparece en la posición más a la izquierda, estas reglas conforman una definición circular que no se puede resolver. (Existen definiciones circulares que sí se pueden resolver, como en la formulación original del primer ejemplo, pero dichas definiciones deben evitar la recursión patológica). Sin embargo, las reglas recursivas por la izquierda siempre se pueden reescribir para eliminar la recursión por la izquierda. [ 2 ] [ 16 ] Por ejemplo, la siguiente regla CFG recursiva por la izquierda:
cadena-de-a ← cadena-de-a 'a' | 'a'se puede reescribir en un PEG usando el operador más:
cadena-de-a ← 'a' +El proceso de reescribir reglas recursivas indirectas por la izquierda es complejo en algunos analizadores sintácticos packrat, especialmente cuando intervienen acciones semánticas.
Con algunas modificaciones, el análisis packrat tradicional puede admitir recursión izquierda directa, [ 6 ] [ 17 ] [ 18 ] pero hacerlo resulta en una pérdida de la propiedad de análisis de tiempo lineal [ 17 ] que generalmente es la justificación para usar PEG y análisis packrat en primer lugar. Solo el algoritmo de análisis OMeta [ 17 ] admite recursión izquierda directa e indirecta completa sin complejidad adicional asociada (pero nuevamente, a una pérdida de la complejidad de tiempo lineal), mientras que todos los analizadores GLR admiten recursión izquierda.
Comportamiento inesperado
Una primera impresión común de las PEG es que se parecen a las CFG con ciertas características prácticas —operadores de repetición *+?como en las expresiones regulares y predicados de anticipación— &!además de la elección ordenada para la desambiguación. Esta comprensión puede ser suficiente cuando el objetivo es crear un analizador sintáctico para un lenguaje, pero no lo es para discusiones más teóricas sobre la capacidad computacional del análisis de expresiones. En particular, el no determinismo inherente a la elección no ordenada |de las gramáticas libres de contexto las diferencia notablemente de la elección ordenada determinista /.
El problema del punto medio
Los analizadores PEG packrat no pueden reconocer algunas reglas CFG no deterministas inequívocas, como las siguientes: [ 2 ]
S ← 'x' S 'x' | 'x'Ni los algoritmos de análisis sintáctico LL(k) ni LR(k) son capaces de reconocer este ejemplo. Sin embargo, esta gramática puede ser utilizada por un analizador sintáctico CFG general como el algoritmo CYK . No obstante, el lenguaje en cuestión puede ser reconocido por todos estos tipos de analizadores, ya que, de hecho, es un lenguaje regular (el de cadenas de un número impar de x).
Resulta instructivo averiguar exactamente qué hace un analizador PEG cuando intenta hacer coincidir
S ← 'x' S 'x' / 'x'contra la cadena xxxxxq. Como era de esperar, intenta recursivamente hacer coincidir el no terminal Sen posiciones crecientes en esta cadena, hasta que falla la coincidencia contra q, y después de eso comienza a retroceder. Esto sucede de la siguiente manera:
Puesto: 123456 Cadena: xxxxxq Resultados: ↑ Pos.6: Ninguna rama de S coincide ↑ Pos.5: La primera rama de S falla, la segunda rama tiene éxito, lo que produce una coincidencia de longitud 1. ↑ Pos.4: La primera rama de S falla, la segunda rama tiene éxito, lo que produce una coincidencia de longitud 1. ↑ Pos.3: La primera rama de S tiene éxito, produciendo una coincidencia de longitud 3. ↑ Pos.2: La primera rama de S falla, porque después de la coincidencia de S en 3 viene una q. La segunda rama tiene éxito, produciendo una coincidencia de longitud 1. ↑ Pos.1: La primera rama de S tiene éxito, produciendo una coincidencia de longitud 3.
La comparación con una expresión de análisis sintáctico es voraz , en el sentido de que solo se considera el primer éxito encontrado. Aunque localmente las opciones se ordenen de la más larga a la más larga, no hay garantía de que esta comparación voraz encuentre la coincidencia global más larga.
Detección de ambigüedades e influencia del orden de las reglas en el lenguaje coincidente.
Los generadores de analizadores LL(k) y LR(k) no se completarán cuando la gramática de entrada sea ambigua. Esta es una característica común en el caso de que la gramática pretenda ser inequívoca, pero presente defectos. Un generador de analizadores PEG resolverá las ambigüedades no intencionadas priorizando la coincidencia más temprana, lo que puede ser arbitrario y dar lugar a análisis inesperados.
El orden de las producciones en una gramática PEG afecta no solo la resolución de la ambigüedad, sino también el idioma que coincide . Por ejemplo, considérese el primer ejemplo PEG en el artículo de Ford [ 1 ] (ejemplo reescrito en la notación de pegjs.org/online y etiquetado comoy ):
- :
A = "a" "b" / "a" - :
A = "a" / "a" "b"
Ford señala que la segunda alternativa en la última regla PEG nunca tendrá éxito porque la primera opción siempre se toma si la cadena de entrada... comienza con 'a'. . [ 1 ] Específicamente , (es decir, el idioma que coincide con ) incluye la entrada "ab", peroNo lo hace. Por lo tanto, agregar una nueva opción a una gramática PEG puede eliminar cadenas del idioma coincidente, por ejemplo : es la adición de una regla a la gramática de producción única, que contiene una cadena que no coincide con A = "a" "b" . Además, construir una gramática que coincida de las gramáticas PEGy no siempre es una tarea trivial. Esto contrasta marcadamente con las gramáticas libres de contexto (GLC), en las que la adición de una nueva producción no puede eliminar cadenas (aunque puede introducir problemas en forma de ambigüedad), y una gramática (potencialmente ambigua) para Se puede construir
S → inicio ( G1 ) | inicio ( G2 )Teoría del análisis sintáctico de gramáticas de expresiones
Es un problema abierto dar un ejemplo concreto de un lenguaje libre de contexto que no pueda ser reconocido por una gramática de expresiones de análisis sintáctico. [ 1 ] En particular, es un problema abierto si una gramática de expresiones de análisis sintáctico puede reconocer el lenguaje de los palíndromos. [ 19 ]
La clase de lenguajes de expresiones de análisis sintáctico es cerrada bajo intersección y complemento de conjuntos, por lo tanto también bajo unión de conjuntos. [ 1 ] : Sec.3.4
Indecidibilidad del vacío
En marcado contraste con el caso de las gramáticas libres de contexto, no es posible generar elementos de un lenguaje de expresiones de análisis sintáctico a partir de su gramática. Además, es algorítmicamente indecidible si el lenguaje reconocido por una gramática de expresiones de análisis sintáctico es vacío. Una razón para ello es que cualquier instancia del problema de correspondencia de Post se reduce a una instancia del problema de decidir si un lenguaje de expresiones de análisis sintáctico es vacío.
Recordemos que una instancia del problema de correspondencia de Post consiste en una listade pares de cadenas (de símbolos terminales). El problema consiste en determinar si existe una secuenciade índices en el rangode tal manera quePara reducir esto a una gramática de expresión de análisis sintáctico, dejemosser arbitrarias distintas por pares cadenas igualmente largas de símbolos terminales (ya consímbolos distintos en el alfabeto de símbolos terminales, longitudes suficiente) y considere la gramática de expresión de análisis sintáctico Cualquier cadena que coincida con el no terminaltiene la formapara algunos índices. Asimismo, cualquier cadena que coincida con el no terminaltiene la formaPor lo tanto, cualquier cadena que coincida contendrá la formadónde.
Uso práctico
- La implementación de referencia de Python CPython introdujo un analizador PEG en la versión 3.9 como alternativa al analizador LL(1) y utiliza solo PEG a partir de la versión 3.10. [ 20 ]
- El lenguaje de programación jq utiliza un formalismo estrechamente relacionado con PEG.
- Los autores de Lua crearon LPeg , una biblioteca de coincidencia de patrones que utiliza PEG en lugar de expresiones regulares , [ 21 ] así como el módulo re que implementa una sintaxis similar a las expresiones regulares utilizando la biblioteca LPeg. [ 22 ]
Véase también
Referencias
- 1 2 3 4 5 6 7 8 9 10 Ford, Bryan (enero de 2004). "Sparsing Expression Grammars: A Recognition Based Syntactic Foundation" (PDF) . Actas del 31.er Simposio ACM SIGPLAN-SIGACT sobre Principios de Lenguajes de Programación . ACM . págs. 111–122 . doi : 10.1145/964001.964011 . ISBN 1-58113-729-X.
- 1 2 3 Ford, Bryan (septiembre de 2002). "Packrat parsing: simple, potente, perezoso, tiempo lineal, pereza funcional" (PDF) . ACM SIGPLAN Notices . 37 (9). doi : 10.1145/583852.581483 .
- ↑ Sirthias, Mathias. "Parboiled: Rule Construction in Java" . GitHub . Consultado el 13 de enero de 2024 .
- 1 2 Kupries, Andreas. "pt::peg_language - Tutorial del lenguaje PEG" . Código fuente de la biblioteca Tcl . Consultado el 14 de enero de 2024 .
- 1 2 Martens, Jan (23 de enero de 2017). Análisis sintáctico de gramáticas de expresiones, construcción de un analizador sintáctico de tiempo lineal (PDF) (Tesis de licenciatura). Universidad de Radboud . Recuperado el 13 de marzo de 2026 .
- 1 2 3 Ford, Bryan (septiembre de 2002). Packrat Parsing: un algoritmo práctico de tiempo lineal con retroceso (tesis). Instituto Tecnológico de Massachusetts . Recuperado el 27 de julio de 2007 .
- ↑ Merritt, Doug (noviembre de 1993). "Descenso recursivo transparente" . Grupo de Usenet comp.compilers . Recuperado el 4 de septiembre de 2009 .
- ↑ Hutchison, Luke AD (2020). "Pika parsing: parsing in reverse resolves the left recursion and error recovery problems". arXiv : 2005.06444 [ cs.PL ].
- ↑ Las gramáticas libres de contexto (GLC) se pueden usar para describir la sintaxis de los lenguajes de programación comunes hasta el nivel de caracteres, pero hacerlo es bastante engorroso, porque la regla de tokenización estándar de que un token consiste en la secuencia consecutiva más larga de caracteres del mismo tipo no encaja bien con el lado no determinista de las GLC. Para formalizar que el espacio en blanco entre dos tokens adyacentes es obligatorio si los caracteres a ambos lados del límite del token son letras, pero opcional si no son letras, una GLC necesita múltiples variantes de la mayoría de los no terminales, para llevar un registro de qué tipo de carácter debe estar en el límite. Si haydiferentes tipos de caracteres que no son espacios en blanco, lo que sumavariantes posibles por no terminal: lo que aumenta significativamente la complejidad de la gramática.
- ↑ Lee, Lillian (enero de 2002). "El análisis rápido de gramáticas libres de contexto requiere una multiplicación rápida de matrices booleanas". J. ACM . 49 (1): 1–15. arXiv : cs/0112018 . doi : 10.1145/505241.505242 .
- ↑ Ford, Bryan. "Página de gramáticas de expresiones y análisis sintáctico de Packrat" . BFord.info . Consultado el 23 de noviembre de 2010 .
- ↑ Jelliffe, Rick (10 de marzo de 2010). "¿Qué es un analizador Packrat? ¿Qué son los derivados de Brzozowski?" . Archivado del original el 28 de julio de 2011.
- ↑ Por ejemplo, al final de la entrada podría haber una directiva que diga: «En este archivo, la coma es un separador decimal , así que todas esas llamadas a funciones f(3,14*r) que creías que tenían dos argumentos, no los tienen. Ahora vuelve al principio de la entrada y analízala de nuevo». Podría decirse que sería un mal diseño del lenguaje de entrada, pero la cuestión es que las gramáticas de expresiones de análisis son lo suficientemente potentes como para manejar esto, simplemente por cuestiones de sintaxis.
- ↑ por ejemplo, la expresión LISP (x (x (x (x ....))))
- ↑ Esto es similar a una situación que surge en los algoritmos de grafos : el algoritmo de Bellman-Ford y el algoritmo de Floyd-Warshall parecen tener el mismo tiempo de ejecución () si solo se considera el número de vértices. Sin embargo, un análisis más preciso que tiene en cuenta el número de aristas como un parámetro separado asigna al algoritmo de Bellman-Ford un tiempo de, que es cuadrático para grafos dispersos con.
- ↑ Aho, AV; Sethi, R.; Ullman, JD (1986). Compiladores: Principios, técnicas y herramientas . Boston, MA, EE. UU.: Addison-Wesley Longman . ISBN 0-201-10088-6.
- 1 2 3 Warth, Alessandro; Douglass, James R.; Millstein, Todd (enero de 2008). "Los analizadores Packrat pueden admitir recursión izquierda" (PDF) . Actas del simposio ACM SIGPLAN de 2008 sobre evaluación parcial y manipulación de programas basada en semántica . PEPM '08. ACM . págs. 103–110 . doi : 10.1145/1328408.1328424 . ISBN 9781595939777. Consultado el 2 de octubre de 2008 .
- ↑ Steinmann, Ruedi (marzo de 2009). "Manejo de la recursión izquierda en analizadores Packrat" (PDF) . n.ethz.ch. Archivado del original (PDF) el 6 de julio de 2011.
- ^ Loff, Bruno; Moreira, Nelma; Reis, Rogerio (14 de febrero de 2020). "El poder computacional de analizar gramáticas de expresiones". arXiv : 1902.08272 [ cs.FL ].
- ↑ "PEP 617 – Nuevo analizador PEG para CPython" . peps.python.org . Consultado el 16 de enero de 2023 .
- ↑ Ierusalimschy, Roberto (10 de marzo de 2009). "Una herramienta de coincidencia de patrones de texto basada en gramáticas de expresiones de análisis sintáctico" . Software: Practice and Experience . 39 (3): 221– 258. doi : 10.1002/spe.892 . ISSN 0038-0644 .
- ↑ Ierusalimschy, Roberto. "LPeg.re - Sintaxis de expresiones regulares para LPEG" . inf.puc-rio.br .
Enlaces externos
- Convertir una expresión de cadena en una expresión lambda usando un analizador de expresiones.
- Página de gramáticas de expresiones y análisis sintáctico de Packrat
- El lenguaje construido Lojban posee una gramática PEG bastante extensa que permite un análisis sintáctico inequívoco del texto Lojban.
- Una implementación ilustrativa de un esquema PEG en Guile
- Lenguajes formales