En programación informática , un combinador de analizadores sintácticos es una función de orden superior que acepta varios analizadores como entrada y devuelve un nuevo analizador como salida. En este contexto, un analizador sintáctico es una función que acepta cadenas como entrada y devuelve alguna estructura como salida, normalmente un árbol de análisis sintáctico o un conjunto de índices que representan las posiciones en la cadena donde el análisis se detuvo correctamente. Los combinadores de analizadores sintácticos permiten una estrategia de análisis sintáctico descendente recursivo que facilita la construcción y prueba modular por partes. Esta técnica de análisis sintáctico se denomina análisis sintáctico combinatorio .
Los analizadores sintácticos que utilizan combinadores se han utilizado ampliamente en la creación de prototipos de compiladores y procesadores para lenguajes específicos de dominio, como las interfaces de usuario en lenguaje natural para bases de datos, donde las acciones semánticas complejas y variadas están estrechamente integradas con el procesamiento sintáctico. En 1989, Richard Frost y John Launchbury demostraron [ 1 ] el uso de combinadores de analizadores sintácticos para construir intérpretes de lenguaje natural . Graham Hutton también utilizó funciones de orden superior para el análisis sintáctico básico en 1992 [ 2 ] y el análisis sintáctico monádico en 1996. [ 3 ] SD Swierstra también mostró los aspectos prácticos de los combinadores de analizadores sintácticos en 2001. [ 4 ] En 2008, Frost, Hafiz y Callaghan [ 5 ] describieron un conjunto de combinadores de analizadores sintácticos en el lenguaje de programación funcional Haskell que resuelven el problema de larga data de acomodar la recursión izquierda y funcionan como una herramienta completa de análisis sintáctico descendente en tiempo y espacio polinomiales .
Idea básica
En cualquier lenguaje de programación con funciones de primera clase , los combinadores de analizadores sintácticos permiten combinar analizadores básicos para construir analizadores que respondan a reglas más complejas. Por ejemplo, una regla de producción de una gramática libre de contexto (GLC) puede tener una o más alternativas, y cada alternativa puede consistir en una secuencia de no terminales y/o terminales, o bien en un único no terminal, terminal o la cadena vacía. Si se dispone de un analizador simple para cada una de estas alternativas, se puede usar un combinador para combinarlos, obteniendo así un nuevo analizador capaz de reconocer cualquiera o todas las alternativas.
En los lenguajes que admiten sobrecarga de operadores , un combinador de analizadores sintácticos puede adoptar la forma de un operador infijo , utilizado para unir diferentes analizadores y formar una regla completa. De este modo, los combinadores de analizadores sintácticos permiten definirlos en un estilo integrado, en un código cuya estructura es similar a la de las reglas de la gramática formal. Así, las implementaciones pueden considerarse especificaciones ejecutables con todas las ventajas asociadas, como la legibilidad.
Los combinadores
Para mantener la discusión relativamente sencilla, hablaremos de los combinadores de analizadores en términos de reconocedores únicamente. Si la cadena de entrada tiene una longitud #inputy se accede a sus miembros a través de un índice j, un reconocedor es un analizador que devuelve, como salida, un conjunto de índices que representan los índices en los que el analizador terminó de reconocer con éxito una secuencia de tokens que comienza en el índice j. Un conjunto de resultados vacío indica que el reconocedor no pudo reconocer ninguna secuencia que comience en el índice j.
- El
emptyreconocedor reconoce la cadena vacía. Este analizador siempre tiene éxito, devolviendo un conjunto unitario que contiene el índice de entrada:
- Un reconocedor reconoce el terminal . Si el token en el índice de la cadena de entrada es , este analizador devuelve un conjunto unitario que contiene ; de lo contrario, devuelve el conjunto vacío.
term xxjxj + 1
Dados dos reconocedores py q, podemos definir dos combinadores de analizadores principales, uno para hacer coincidir reglas alternativas y otro para secuenciar reglas:
- El combinador de analizadores 'alternativos', ⊕, aplica cada uno de los reconocedores al mismo índice
jy devuelve la unión de los índices finales de los reconocedores:
- El combinador 'secuencia', ⊛, aplica el primer reconocedor
pal índice de entradajy, para cada índice final, aplica el segundo reconocedorqcon ese como índice inicial. Devuelve la unión de los índices finales devueltos por todas las invocaciones deq:
Puede haber varias formas distintas de analizar una cadena, aunque el resultado final se encuentre en el mismo índice, lo que indica una gramática ambigua . Los reconocedores simples no reconocen estas ambigüedades; cada posible índice final se muestra solo una vez en el conjunto de resultados. Para obtener un conjunto de resultados más completo, se debe devolver un objeto más complejo, como un árbol de análisis sintáctico .
Ejemplos
Consideremos una gramática libre de contexto altamente ambigua , . Utilizando los combinadores definidos anteriormente, podemos definir de forma modular notaciones ejecutables de esta gramática en un lenguaje de programación funcional moderno (por ejemplo, Haskell ) como . Cuando el reconocedor se aplica en el índice de la secuencia de entrada, devolvería un conjunto de resultados , lo que indica que hubo coincidencias comenzando en el índice 2 y terminando en cualquier índice entre 2 y 5 inclusive.s ::= ‘x’ s s | εs = term ‘x’ <*> s <*> s <+> emptys2x x x x x{2,3,4,5}
Deficiencias y soluciones
Los combinadores de analizadores, como todos los analizadores descendentes recursivos , no se limitan a las gramáticas libres de contexto y, por lo tanto, no realizan una búsqueda global de ambigüedades en los conjuntos First k y Follow k del análisis LL( k ) . Así, las ambigüedades no se conocen hasta el tiempo de ejecución, si es que la entrada las activa. En tales casos, el analizador descendente recursivo puede optar por defecto (quizás sin que el diseñador de la gramática lo sepa) por una de las posibles rutas ambiguas, lo que genera confusión semántica (aliasing) en el uso del lenguaje. Esto provoca errores en los usuarios de lenguajes de programación ambiguos, que no se notifican en tiempo de compilación y que no se deben a errores humanos, sino a la ambigüedad de la gramática. La única solución para eliminar estos errores es eliminar las ambigüedades y utilizar una gramática libre de contexto.
Las implementaciones simples de combinadores de analizadores sintácticos tienen algunas deficiencias, comunes en el análisis sintáctico descendente. El análisis sintáctico combinatorio ingenuo requiere tiempo y espacio exponenciales al analizar una gramática libre de contexto ambigua. En 1996, Frost y Szydlowski demostraron cómo se puede usar la memorización con combinadores de analizadores sintácticos para reducir la complejidad temporal a polinómica. [ 6 ] Posteriormente, Frost usó mónadas para construir los combinadores para el encadenamiento sistemático y correcto de la tabla de memorización a lo largo del cálculo. [ 7 ]
Como cualquier análisis descendente recursivo de arriba hacia abajo , los combinadores de analizadores convencionales (como los combinadores descritos anteriormente) no terminarán mientras procesan una gramática recursiva izquierda (por ejemplo, ). Frost y Hafiz describieron en 2006 un algoritmo de reconocimiento que admite gramáticas ambiguas con reglas recursivas izquierdas directas. [ 8 ] El algoritmo limita el análisis recursivo izquierdo, que de otro modo crecería continuamente, imponiendo restricciones de profundidad. Frost, Hafiz y Callaghan extendieron ese algoritmo a un algoritmo de análisis completo para admitir tanto la recursión izquierda indirecta como la directa en tiempo polinomial , y para generar representaciones compactas de tamaño polinomial del número potencialmente exponencial de árboles de análisis para gramáticas altamente ambiguas. [ 9 ] Este algoritmo extendido admite la recursión izquierda indirecta comparando su "contexto calculado" con el "contexto actual". Los mismos autores también describieron su implementación de un conjunto de combinadores de analizadores escritos en el lenguaje Haskell basados en el mismo algoritmo. [ 5 ] [ 10 ]s ::= s <*> term ‘x’|empty
Notas
- ↑ Frost y Launchbury 1989 .
- ↑ Hutton 1992 .
- ↑ Hutton, Graham; Meijer, Erik. Combinadores de analizadores sintácticos monádicos (PDF) (Informe). Universidad de Nottingham . Consultado el 13 de febrero de 2023 .
- ↑ Swierstra 2001 .
- 1 2 Frost, Hafiz y Callaghan 2008 .
- ↑ Frost y Szydlowski 1996 .
- ↑ Frost 2003 .
- ↑ Frost y Hafiz 2006 .
- ↑ Frost, Hafiz y Callaghan 2007 .
- ↑ cf.X- SAIGA — especificaciones ejecutablesdegramáticas
Referencias
- Burge, William H. (1975). Técnicas de programación recursiva . Serie de programación de sistemas. Addison-Wesley. ISBN 978-0201144505.
- Frost, Richard; Launchbury, John (1989). «Construcción de intérpretes de lenguaje natural en un lenguaje funcional perezoso» (PDF) . The Computer Journal . Edición especial sobre Programación Funcional Perezosa. 32 (2): 108–121 . doi : 10.1093/comjnl/32.2.108 . Archivado del original el 6 de junio de 2013.
{{cite journal}}: CS1 maint: bot: estado de la URL original desconocido ( enlace ) - Frost, Richard A.; Szydlowski, Barbara (1996). "Memoización de procesadores de lenguaje puramente funcionales de retroceso descendente" (PDF) . Sci. Comput. Program . 27 (3): 263– 288. doi : 10.1016/0167-6423(96)00014-7 .
- Frost, Richard A. (2003). «Memorización monádica para la reducción de búsquedas que preservan la corrección». Actas de la 16.ª Conferencia de la Sociedad Canadiense de Estudios Computacionales de Inteligencia sobre Avances en Inteligencia Artificial (AI'03) (PDF) . Springer. págs. 66-80 . ISBN 978-3-540-40300-5.
- Frost, Richard A.; Hafiz, Rahmatullah (2006). "Un nuevo algoritmo de análisis sintáctico descendente para acomodar la ambigüedad y la recursión izquierda en tiempo polinomial" (PDF) . ACM SIGPLAN Notices . 41 (5): 46– 54. doi : 10.1145/1149982.1149988 . S2CID 8006549 .
- Frost, Richard A.; Hafiz, Rahmatullah; Callaghan, Paul (2007). "Análisis sintáctico descendente modular y eficiente para gramáticas recursivas izquierdas ambiguas". Actas del 10.º Taller Internacional sobre Tecnologías de Análisis Sintáctico (IWPT), ACL-SIGPARSE : 109–120 . CiteSeerX 10.1.1.97.8915 .
- Frost, Richard A.; Hafiz, Rahmatullah; Callaghan, Paul (2008). «Combinadores de analizadores sintácticos para gramáticas recursivas izquierdas ambiguas». Aspectos prácticos de los lenguajes declarativos . ACM-SIGPLAN. Vol. 4902. pp. 167–181 . CiteSeerX 10.1.1.89.2132 . doi : 10.1007/978-3-540-77442-6_12 . ISBN 978-3-540-77441-9.
- Hutton, Graham (1992). "Funciones de orden superior para el análisis sintáctico". Journal of Functional Programming . 2 (3): 323– 343. CiteSeerX 10.1.1.34.1287 . doi : 10.1017/s0956796800000411 . S2CID 31067887 .
- Okasaki, Chris (1998). "Funciones de orden aún superior para el análisis sintáctico o ¿Por qué alguien querría usar una función de sexto orden?" . Journal of Functional Programming . 8 (2): 195– 199. doi : 10.1017/S0956796898003001 . S2CID 59694674 .
- Swierstra, S. Doaitse (2001). "Analizadores combinatorios: De juguetes a herramientas" . Electronic Notes in Theoretical Computer Science . 41 : 38–59 . doi : 10.1016/S1571-0661(05)80545-6 .
- Wadler, Philip (1985). «Cómo reemplazar el fallo por una lista de éxitos: un método para el manejo de excepciones, retroceso y coincidencia de patrones en lenguajes funcionales perezosos». Lenguajes de programación funcional y arquitectura de computadoras . Notas de clase en ciencias de la computación. Vol. 201. pp. 113–128 . doi : 10.1007/3-540-15975-4_33 . ISBN 978-0-387-15975-1– a través de las Actas de una Conferencia sobre Lenguajes de Programación Funcional y Arquitectura de Computadoras.
- Análisis sintáctico
- Lenguajes formales
- Programación funcional
