Escribir un compilador Compilador: información sobre el uso y las características

10

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:

  1. Plantillas
  2. Mire hacia adelante la predicción, sabiendo lo que es válido en un punto dado.
  3. Regla 'Deliteralización' tomando los literales dentro de las reglas y resolviendo de qué token provienen.
  4. Autómatas no deterministas
  5. Autómatas deterministas
  6. Máquina de estado léxico simple para reconocimiento de tokens
  7. 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.

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 .

revs Alexander Morou
fuente
8
Muro de texto. No tome esto de la manera incorrecta, agradezco un problema completamente explicado. En este caso, es simplemente un poco demasiado detallado. Por lo que he recopilado, ¿se pregunta qué características deberían incluirse en un analizador gramatical o cómo hacer una sin comenzar desde cero? Edite para responder las siguientes preguntas (no tiene que volver a escribir, simplemente agregue al final en resumen): ¿Cuál es su problema? ¿A qué restricciones está sujeto en posibles soluciones a su problema (debe ser rápido, debe ser LL *, etc.)?
Neil
1
Estoy pidiendo información sobre el conjunto de características. La atención se centra en la facilidad de uso. La dificultad radica en conseguir que alguien que no conoce el proyecto, conozca el proyecto para que esté informado sobre su enfoque. No estoy preguntando "cómo hacerlo", sino preguntas relacionadas con la usabilidad. Se agradecen las sugerencias sobre cómo recortar la pregunta.
Allen Clark Copeland Jr
1
Para mí, no es obvio de qué se trata el proyecto. Por ejemplo, desde los días de yacc, hemos visto muchos generadores de analizadores sintácticos. ¿Qué es diferente en tu OILexer? ¿Qué es lo nuevo?
Ingo
1
El objetivo de este proyecto es simplificar la generación del analizador sintáctico. Similar sí, a YACC / Bison y FLEX / LEX. La principal diferencia es evitar la complejidad involucrada en esos programas. Mantener las cosas simples y al punto es el objetivo principal. Es por eso que creé un formato que carece de secciones extrañas, pero el objetivo es hacerlo similar a la programación regular: solo específica para el desarrollo del lenguaje. Los tokens se definen usando ': =' después de su nombre, las reglas se definen usando :: = después de su nombre. Las plantillas usan '<' y '>' para sus argumentos seguidos de ":: =" ya que comparten la sintaxis de la regla.
Allen Clark Copeland Jr
3
Este enfoque infernal en el análisis parece fuera de lugar; es un problema bien resuelto, y apenas afecta lo que necesita para procesar lenguajes de programación. Google para mi ensayo sobre "la vida después de analizar".
Ira Baxter

Respuestas:

5

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:

    • un árbol de sintaxis concreta, para el cual la estructura del árbol corresponde directamente a la gramática e incluye información de diseño para el informe de errores de las fases posteriores. En este caso, el cliente no debería tener que hacer nada para obtener la estructura de árbol correcta; debe depender directamente de la gramática.
    • Un árbol de sintaxis abstracta. En este caso, el usuario puede jugar con cualquiera y todos los árboles de análisis
  • 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
He editado el cuerpo de la pregunta para incluir un enlace a PrecedenceHelper en la viñeta que dice 'Plantillas'. Permite tuplas de matriz de parámetros, por lo que si tiene cuatro parámetros, cada matriz de parámetros, la plantilla debe usarse en conjuntos de argumentos de cuatro.
Allen Clark Copeland Jr
La razón principal por la que construiría el CST es el diseño general del archivo analizado. Si desea imprimir el documento, su mejor opción es usar un CST porque los AST, como su nombre implica, carecen de información para manejar el espacio extraño que capturaría un CST. La transformación de un CST suele ser bastante fácil si es un buen CST.
Allen Clark Copeland Jr
He editado la pregunta nuevamente sobre el tema de las funciones integradas disponibles para su uso.
Allen Clark Copeland Jr
Creo que no hice un gran trabajo expresando mi punto sobre las plantillas / funciones: quise decir que debido a que nunca se puede tener suficiente, un sistema no debería tratar de resolverlas con anticipación: el usuario debe poder crear su propia.
1
Encontré un enfoque particularmente útil para el informe de errores con retroceso infinito (Packrat): cada regla de producción se anota con mensajes de error (redactados como "blah-blah-blah esperado"), y en caso de falla, dicho mensaje se almacena en la secuencia de la misma manera como fichas memorizadas. Si todas las fallas no son recuperables (el análisis finaliza antes de llegar al final de la secuencia), el mensaje de error más a la derecha (o una colección de tales mensajes) es el más apropiado. Eso es lo más fácil de hacer, por supuesto, hay formas de refinarlo aún más con más anotaciones.
SK-logic
5

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.

hombre guapo
fuente
Esta es una de las cosas que planeo hacer. Para ayudar con mi conocimiento del dominio, un amigo mío sugirió escribir un analizador real a mano para acostumbrarse a automatizarlo. Una cosa me di cuenta rápidamente: los analizadores son complejos y hay cosas que hacemos a mano que simplifican el proceso. Las reglas y las plantillas comparten la misma sintaxis; sin embargo, hay elementos de lenguaje que son válidos en las Plantillas pero no reglas, hay estados internos que manejan esta tarea. Lo que trajo una idea a la mente: las reglas deberían poder especificar ayudas de ruta para facilitar el uso compartido de sub-reglas.
Allen Clark Copeland Jr
... esto debería ser bastante simple de transferir a la automatización, pero requeriría que las automatizaciones tengan condiciones basadas en estado. Trabajaré en esto y me pondré en contacto contigo. ANTLR usa automatizaciones de estado finito para manejar ciclos de decir: "T" *, donde como lo usaré para manejar la mayor parte del proceso de análisis, ya que las reducciones deberían ser más limpias como estados cuando hay más de 800 variaciones en una regla (esto se hincharía rápidamente como código de espagueti en forma estándar si / de lo contrario.)
Allen Clark Copeland Jr
0

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):

list: 
      elem                  { $$ = singleton($1); }
    | elem ',' list         { $$ = cons($1, $2);  }
    ;

Un ejemplo para la recursividad izquierda (usando yacc synatx):

funapp:
    term                    { $$ = $1; }
    | funapp term           { $$ = application($1, $2); }
    ;

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.

Ingo
fuente
Sí, es bueno no tener restricciones arbitrarias como esa. Sin embargo, ¿cuál es un ejemplo de recursión izquierda que no se puede reemplazar con un operador de repetición (como expresiones regulares *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).
1
@MattFenwick Vea mi edición. Observe cómo la directiva de sintaxis conduce a acciones semánticas simples y naturales (por ejemplo) para crear un árbol de sintaxis. Mientras que con la repetición (que no está disponible en yacc, por cierto), creo que a menudo debe verificar si tiene una lista vacía, un singleton, etc.
Ingo
Gracias por la respuesta. Creo que entiendo mejor ahora - yo preferiría escribir esos ejemplos como list = sepBy1(',', elem)y funapp = term{+}(y, por supuesto, sepBy1y +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!
1
De nada @MattFenwick. Pero entonces es quizás una cuestión de gustos. Para mí, la recursión es (al menos en el contexto de los lenguajes, que son inherentemente recursivos o totalmente desinteresados) la forma más natural de pensarlo. Además, un árbol es una estructura de datos recursiva, por lo que no veo la necesidad de recurrir a la iteración para simular la recursividad. Pero, por supuesto, las preferencias son diferentes.
Ingo