Esto es parte de una serie de preguntas que se enfoca en el proyecto hermano del Proyecto Abstracción, que tiene como objetivo abstraer los conceptos utilizados en el diseño del lenguaje en forma de marco. El proyecto hermano se llama OILexer, que tiene como objetivo construir un analizador a partir de archivos de gramática, sin el uso de inyección de código en las coincidencias.
Algunas otras páginas asociadas a estas preguntas, relacionadas con la tipificación estructural, se pueden ver aquí , y la facilidad de uso se encuentra aquí . El metatema asociado a una consulta sobre el marco y el lugar adecuado para publicar se puede encontrar aquí .
Estoy llegando al punto en el que estoy a punto de comenzar a extraer el árbol de análisis de una gramática dada, seguido de un analizador de Descenso recursivo que usa DFA para discernir rutas hacia adelante (similar a LL (*) de ANTLR 4), así que pensé que lo abriría para obtener una idea.
En un compilador de analizador, ¿qué tipo de características son ideales?
Hasta ahora, aquí hay una breve descripción de lo que se implementa:
- Plantillas
- Mire hacia adelante la predicción, sabiendo lo que es válido en un punto dado.
- Regla 'Deliteralización' tomando los literales dentro de las reglas y resolviendo de qué token provienen.
- Autómatas no deterministas
- Autómatas deterministas
- Máquina de estado léxico simple para reconocimiento de tokens
- Métodos de automatización de tokens:
- Escaneo: útil para comentarios: Comentario: = "/ *" Escaneo ("* /");
- Restar: útil para identificadores: Identificador: = Restar (IdentifierBody, palabras clave);
- Asegura que el identificador no acepte palabras clave.
- Codificar: codifica una automatización como un recuento en serie X de transiciones de base N.
- UnicodeEscape: = "\\ u" BaseEncode (IdentifierCharNoEscape, 16, 4);
- Realiza un escape unicode en hexadecimal, con 4 transiciones hexadecimales. La diferencia entre esto y: [0-9A-Fa-f] {4} es que la automatización resultante con Encode limita el conjunto permitido de valores hexadecimales al alcance de IdentifierCharNoEscape. Entonces, si le das \ u005c, la versión de codificación no aceptará el valor. Cosas como esta tienen una advertencia seria: usar con moderación. La automatización resultante podría ser bastante compleja.
- UnicodeEscape: = "\\ u" BaseEncode (IdentifierCharNoEscape, 16, 4);
Lo que no se implementa es la generación CST, necesito ajustar las automatizaciones deterministas para llevar el contexto adecuado para que esto funcione.
Para cualquier persona interesada, he subido una bonita versión impresa de la forma original del proyecto T * y♯ . Cada archivo debe vincularse con todos los demás, comencé a vincular las reglas individuales para seguirlos, pero habría tardado demasiado (¡habría sido más sencillo de automatizar!)
Si se necesita más contexto, publique en consecuencia.
Editar 5-14-2013 : he escrito código para crear gráficos GraphViz para las máquinas de estado dentro de un idioma determinado. Aquí hay un gráfico de GraphViz del AssemblyPart . Los miembros vinculados en la descripción del idioma deben tener un rulename.txt en su carpeta relativa con el dígrafo de esa regla. Parte de la descripción del idioma ha cambiado desde que publiqué el ejemplo, esto se debe a simplificar las cosas sobre la gramática. Aquí hay una imagen gráfica interesante .
fuente
Respuestas:
Esta es una excelente pregunta.
He estado trabajando en mucho análisis recientemente, y en mi humilde opinión, algunas de las características clave son:
una API programática, por lo que puede usarse desde un lenguaje de programación, idealmente simplemente importando una biblioteca. También puede tener una interfaz GUI o BNF, pero la clave es la programática, porque puede reutilizar sus herramientas, IDE, análisis estático, pruebas, facilidades de abstracción del lenguaje, familiaridad del programador, generador de documentación, proceso de compilación, Además, puedes jugar interactivamente con pequeños analizadores, lo que reduce drásticamente la curva de aprendizaje. Estas razones lo colocan en la parte superior de mi lista de "características importantes".
informe de errores, como @guysherman mencionó. Cuando se encuentra un error, quiero saber dónde estaba el error y qué estaba sucediendo cuando sucedió. Desafortunadamente, no he podido encontrar buenos recursos para explicar cómo generar errores decentes cuando se inicia el retroceso. (Aunque tenga en cuenta el comentario de @ Sk-logic a continuación).
Resultados parciales. Cuando el análisis falla, quiero poder ver qué se analizó correctamente desde la parte de la entrada que estaba antes de la ubicación del error.
abstracción. Nunca puede construir suficientes funciones, y el usuario siempre necesitará más, por lo que tratar de descubrir todas las funciones posibles por adelantado está condenado al fracaso. ¿Es esto lo que quieres decir con plantillas?
Estoy de acuerdo con tu # 2 (predicción anticipada). Creo que ayuda a generar buenos informes de error. ¿Es útil para algo más?
soporte para construir un árbol de análisis a medida que ocurre el análisis, quizás:
algún tipo de registro. No estoy seguro de esto; tal vez para mostrar un rastro de las reglas que el analizador ha intentado, o para hacer un seguimiento de los tokens basura como espacios en blanco o comentarios, en caso de que (por ejemplo) desee usar los tokens para generar documentación HTML.
capacidad de lidiar con lenguajes sensibles al contexto. Tampoco estoy seguro de cuán importante es este: en la práctica, parece más claro analizar un superconjunto de un lenguaje con una gramática libre de contexto, luego verificar las restricciones sensibles al contexto en pases posteriores adicionales.
mensajes de error personalizados, para que pueda ajustar los informes de errores en situaciones específicas y tal vez comprender y solucionar problemas más rápidamente.
Por otro lado, no considero que la corrección de errores sea particularmente importante, aunque no estoy actualizado sobre el progreso actual. Los problemas que he notado son que las posibles correcciones que proporcionan las herramientas son: 1) demasiado numerosas y 2) no corresponden a los errores reales cometidos, por lo que no son tan útiles. Esperemos que esta situación mejore (o tal vez ya lo haya hecho).
fuente
No tengo experiencia en el diseño de idiomas, pero una vez que creé e IDE para un motor de juegos tuve la oportunidad de escribir un analizador.
Algo que es importante para sus usuarios finales, en mi opinión, son los mensajes de error que tienen sentido. No es un punto particularmente devastador, lo sé, pero seguirlo al revés, una de las implicaciones clave de esto es que debes ser capaz de evitar falsos positivos. ¿De dónde vienen los falsos positivos? Vienen del analizador que se cae al primer error y nunca se recupera del todo. C / C ++ es conocido por esto (aunque los compiladores más nuevos son un poco más inteligentes).
Entonces, ¿qué necesitas en su lugar? Creo que, en lugar de simplemente saber qué es / no es válido en un momento, necesita saber cómo tomar lo que no es válido y hacer un cambio mínimo para que sea válido, para que pueda continuar analizando sin generar errores falsos relacionados a su descenso recursivo confundirse. Ser capaz de construir un analizador que pueda generar esta información no solo le brinda un analizador muy robusto, sino que abre algunas características fantásticas para el software que consume el analizador.
Me doy cuenta de que puedo estar sugiriendo algo realmente difícil, o estúpidamente obvio, lo siento si este es el caso. Si este no es el tipo de cosas que estás buscando, felizmente eliminaré mi respuesta.
fuente
La gramática no debe tener restricciones como "no dejar reglas recursivas". Es ridículo que las herramientas ampliamente utilizadas hoy en día tengan esto y solo puedan comprender las gramáticas LL de succión, casi 50 años después de que yacc lo hizo bien.
Un ejemplo de recursión correcta (usando la sintaxis yacc):
Un ejemplo para la recursividad izquierda (usando yacc synatx):
Ahora, esto podría "refactorizarse" a otra cosa, pero en ambos casos, el tipo específico de recursión es la forma "correcta" de escribir esto, ya que (en el lenguaje de ejemplo) las listas son recursivas a la derecha mientras que la aplicación de funciones se deja recursiva .
Se puede esperar de las herramientas avanzadas que admitan la forma natural de escribir cosas, en lugar de requerir que uno "refactorice" todo para que sea recursivo a la izquierda / derecha de la forma que la herramienta impone a uno.
fuente
*
o+
cuantificadores)? Admito libremente que mi conocimiento en esta área es limitado, pero nunca me he topado con el uso de la recursión izquierda que no podría refactorizarse en la repetición. Y también encontré la versión de repetición más clara (aunque eso es solo una preferencia personal).list = sepBy1(',', elem)
yfunapp = term{+}
(y, por supuesto,sepBy1
y+
se llevaría a cabo en términos de derecha / izquierda recursividad, y producir árboles de sintaxis estándar). Entonces, no es que piense que la recursividad izquierda y derecha son malas, es solo que siento que son de bajo nivel y me gustaría usar una abstracción de alto nivel cuando sea posible para aclarar las cosas. ¡Gracias de nuevo!