ventajas traductor tipos sobre semejanzas programacion lenguaje interpretes interprete interpretado informatica entre enlazadores diferencia compilador compilado compiler-construction interpreter

compiler construction - traductor - ¿Cuál es la diferencia entre implementar un compilador y un intérprete?



tipos de interpretes en programacion (10)

Ambos tienen mucho en común (por ejemplo, un analizador léxico) y existe un desacuerdo sobre la diferencia. Miro de esta manera:

La definición clásica sería que un compilador analiza y traduce una secuencia de símbolos en una secuencia de bytes que la CPU puede ejecutar, mientras que un intérprete hace lo mismo, pero los traduce a un formulario que debe ejecutarse en un software (p. Ej. JVM, CLR).

Sin embargo, las personas llaman a ''javac'' un compilador, por lo que la definición informal de compilador es algo que debe hacerse al código fuente como un paso separado, mientras que los intérpretes no tienen un paso de compilación (por ejemplo, PHP, Perl).

Recientemente leí todo el Libro del Dragón (solo por diversión, realmente no estoy planeando implementar un compilador real), y me quedé con esta gran pregunta colgando en mi cabeza.

¿Qué hay de diferente entre implementar un compilador y un intérprete?

Para mí, un compilador está formado por:

  • Lexer
  • Analizador (que construye el árbol de sintaxis)
  • Generar código intermedio (como 3 código de dirección)
  • Haz todas estas cosas locas para optimizar si quieres :-)
  • Genera "ensamblado" o "código nativo" desde el código de 3 direcciones.

Ahora, obviamente, el intérprete también tiene el mismo lexer y analizador que el compilador.
Pero, ¿qué hace después de eso?

  • ¿"Lee" el árbol de sintaxis y lo ejecuta directamente? (algo así como tener un puntero de instrucción apuntando al nodo actual en el árbol, y la ejecución es una gran travesía de árbol más la administración de memoria para la pila de llamadas) (y si es así, ¿cómo lo hace? Espero que el la ejecución es mejor que una declaración de conmutación enorme que comprueba qué tipo de nodo es)

  • ¿Genera 3 código de dirección e interpreta eso? (Si es así, ¿cómo lo hace? De nuevo, estoy buscando algo más elegante que una declaración de cambio de una milla de largo)

  • ¿Genera código nativo real, lo carga en la memoria y lo hace funcionar? (en ese momento supongo que ya no es un intérprete, sino más bien un compilador JIT)

Además, ¿en qué punto aparece el concepto de "máquina virtual"? ¿Para qué utilizas una máquina virtual en un idioma? (para tener claro mi nivel de ignorancia, para mí una máquina virtual es VMWare, no tengo idea de cómo se aplica el concepto de VM a los lenguajes de programación / ejecución de programas).

Como pueden ver, mi pregunta es bastante amplia. Principalmente estoy buscando no solo qué método se utiliza, sino principalmente para comprender primero los conceptos más importantes y luego ver cómo funciona en detalle. Quiero los detalles feos y crudos. Obviamente, esto es más una búsqueda de referencias a cosas para leer en lugar de esperar que respondas todos estos detalles aquí.

¡Gracias!
Daniel

EDITAR: Gracias por tus respuestas hasta ahora. Sin embargo, me di cuenta de que mi título era engañoso. Entiendo la diferencia "funcional" entre un compilador y un intérprete.
Lo que estoy buscando es la diferencia en cuanto a cómo implementar un intérprete, frente a un compilador.
Ahora entiendo cómo se implementa un compilador, la pregunta es cómo un intérprete se diferencia de eso.

Por ejemplo: VB6 es claramente un compilador y un intérprete. Ahora entiendo la parte del compilador. Sin embargo, no puedo entender cómo, cuando se ejecuta dentro del IDE, podría permitirme detener el programa en cualquier punto arbitrario, cambiar el código y reanudar la ejecución con el nuevo código. Es solo un pequeño ejemplo, no es la respuesta que estoy buscando. Lo que trato de entender, como explico a continuación, es lo que sucede después de tener un árbol de análisis sintáctico. Un compilador generará un nuevo código en el idioma "objetivo". ¿Qué hace un intérprete?

¡Gracias por tu ayuda!


No es tan claro como solía ser. Solía ​​ser construir un árbol de análisis sintáctico, vincularlo y ejecutarlo (a menudo vinculante en el último segundo).

BÁSICO fue hecho principalmente de esta manera.

Podría afirmar que las cosas que ejecutan bytecode (java / .net) sin hacer un JIT son interpriters, pero no en el sentido tradicional ya que todavía tiene que ''compilar'' a bytecode.

La diferencia de la vieja escuela era: si genera código de CPU, es un compilador. Si lo ejecuta directamente en su entorno de edición y puede interactuar con él durante la edición, es un intérprete.

Eso fue mucho menos formal que el libro real del Dragón, pero espero que sea informativo.


Si mi experiencia indica algo;

  1. Los intérpretes no intentan reducir / procesar AST aún más, cada vez que se hace referencia a un bloque de código, se atraviesa y ejecuta un nodo AST relevante. Los compiladores atraviesan un bloque varias veces para generar código ejecutable en un lugar determinado y terminan con él.
  2. La tabla de símbolos de los intérpretes guarda los valores y hace referencia a ellos durante la ejecución, la tabla de símbolos de los compiladores guarda la ubicación de las variables. No existe tal tabla de símbolos durante la ejecución.

En tiro la diferencia puede ser tan simple como

case ''+'': symtbl[var3] = symtbl[var1] + symtbl[var2]; break;

Entre,

case ''+'': printf("%s = %s + %s;",symtbl[var3],symtbl[var1],symtbl[var2]); break;

(No importa si se dirige a otro idioma o a instrucciones (virtuales) de la máquina).


Un programa es una descripción del trabajo que desea hacer.

Un compilador convierte una descripción de alto nivel en una descripción más simple.

Un intérprete lee una descripción de qué hacer y hace el trabajo .

  • Algunos intérpretes (por ejemplo, conchas de Unix) leen la descripción una pequeña pieza a la vez y actúan sobre cada pieza tal como la ven; algunos (por ejemplo, Perl, Python) leen la descripción completa, la convierten internamente a una forma más simple y luego actúan sobre eso.
  • Algunos intérpretes (por ejemplo, JVM de Java o un chip Pentium 4) solo entienden un lenguaje de descripción muy simple que es demasiado tedioso para que los humanos trabajen directamente, por lo que los humanos usan compiladores para convertir sus descripciones de alto nivel a este lenguaje.

Los compiladores nunca hacen el trabajo. Los intérpretes siempre hacen el trabajo.


Un compilador es un programa que traduce un programa en un lenguaje de programación a un programa en otro lenguaje de programación. Eso es todo, simple y llanamente.

Un intérprete traduce un lenguaje de programación en su significado semántico.

Un chip x86 es un intérprete para el lenguaje de máquina x86.

Javac es un compilador para Java a la máquina virtual Java. java, la aplicación ejecutable, es un intérprete para el jvm.

Algunos intérpretes comparten algunos elementos de compilación en el sentido de que pueden traducir un idioma a otro idioma interno que es más fácil de interpretar.

Por lo general, los intérpretes, aunque no siempre, cuentan con un ciclo read-eval-print.


respuesta corta:

  • un compilador convierte el código fuente en un formato ejecutable para su posterior ejecución
  • un intérprete evalúa el código fuente para la ejecución inmediata

hay un gran margen de maniobra en la forma en que se implementan. Es posible que un intérprete genere código máquina nativo y luego lo ejecute, mientras que un compilador para una máquina virtual puede generar código p en lugar de código máquina. Los idiomas interpretados como Rostros buscan palabras clave en un diccionario y ejecutan su función de código nativo asociado de inmediato.

los compiladores generalmente pueden optimizar mejor porque tienen más tiempo para estudiar el código y producir un archivo para su posterior ejecución; los intérpretes tienen menos tiempo para optimizar porque tienden a ejecutar el código "tal cual" a primera vista

un intérprete optimizado en segundo plano, también es posible aprender mejores formas de ejecutar el código

resumen: la diferencia realmente se reduce a "preparar el código para una ejecución posterior" o "ejecutar el código en este momento"


Con respecto a esta parte de su pregunta, que las otras respuestas no han abordado realmente:

Además, ¿en qué punto aparece el concepto de "máquina virtual"? ¿Para qué utilizas una máquina virtual en un idioma?

Las máquinas virtuales como JVM o CLR son una capa de abstracción que le permite reutilizar la optimización del compilador JIT, la recolección de elementos no utilizados y otros detalles de implementación para lenguajes completamente diferentes que se compilan para ejecutarse en la máquina virtual.

También lo ayudan a hacer que la especificación del idioma sea más independiente del hardware real. Por ejemplo, aunque el código C es teóricamente portable, siempre debe preocuparse por aspectos como la endianidad, el tamaño del texto y la alineación de las variables si realmente desea generar un código portátil. Mientras que con Java, la JVM está muy claramente especificada en estos aspectos, por lo que el diseñador del lenguaje y sus usuarios no tienen que preocuparse por ellos; el trabajo del implementador de JVM es implementar el comportamiento especificado en el hardware real.


Dada su lista de pasos:

  • Lexer
  • Analizador (que construye el árbol de sintaxis)
  • Generar código intermedio (como 3 código de dirección)
  • Haz todas estas cosas locas para optimizar si quieres :-)
  • Genera "ensamblado" o "código nativo" desde el código de 3 direcciones.

Un intérprete muy simple (como los primeros BASIC o TCL) solo realizaría los pasos uno y dos, una línea a la vez. Y luego descarta la mayoría de los resultados mientras avanzas a la siguiente línea para ejecutar. Los otros 3 pasos nunca se realizarán en absoluto.


Una vez que un árbol de análisis está disponible, hay varias estrategias:

1) interpretar directamente el AST (Ruby, intérprete original de WebKit) 2) transformación de código -> en códigos de bytes o códigos de máquina

Para lograr Editar-y-Continuar, el contador del programa o el puntero de instrucción debe ser recalculado y movido. Esto requiere cooperación del IDE, porque el código puede haberse insertado antes o después de la pequeña flecha amarilla.

Una forma en que esto podría hacerse es incrustar la posición del contador de programa en el árbol de análisis sintáctico. Por ejemplo, podría haber una declaración especial llamada "ruptura". El contador del programa solo necesita ubicarse después de la instrucción "break" para continuar funcionando.

Además, debe decidir qué desea hacer con respecto al marco de pila actual (y las variables en la pila). Tal vez reventando la pila actual, y copiando las variables, o manteniendo la pila, pero parcheando en un GOTO y RETORNO al código actual.


Si está buscando un libro, Estructura e Interpretación de Programas de Computadora ("el libro del Mago") es un buen lugar para comenzar con los conceptos de intérprete. Solo está tratando con el código Scheme, que puede atravesarse, evaluarse y transmitirse como si fuera un AST.

Además, Peter Norvig tiene un pequeño ejemplo que explica la idea principal usando Python (con muchos más ejemplos en los comentarios), y aquí hay otro pequeño ejemplo en Wikipedia.

Como dijiste, es un cruce de árboles, y al menos para llamar por valor es simple: cuando veas a un operador, evalúa el puño de los operandos, luego aplica el operador. El valor final devuelto es el resultado del programa (o la declaración dada a un REPL).

Tenga en cuenta que no siempre tiene que hacer el recorrido de árbol explícitamente: puede generar su AST de tal manera que acepte un visitante (creo que SableCC hace esto), o para idiomas muy pequeños, como las pequeñas gramáticas aritméticas utilizadas para demostrar generadores de analizadores, puede simplemente evaluar el resultado durante el análisis.

Para admitir declaraciones y asignaciones, necesita mantener un entorno alrededor. Del mismo modo que evaluaría "más" al agregar los operandos, evaluaría el nombre de una función, variable, etc., al buscarlo en el entorno. El alcance de soporte significa tratar el entorno como una pila y empujar y hacer estallar cosas en el momento correcto. En general, cuán complicado es su intérprete depende de las características del idioma que quiera apoyar. Por ejemplo, los intérpretes hacen posible la recolección de basura y la introspección.

Para máquinas virtuales: plinth y j_random_hacker describieron el hardware de la computadora como un tipo de intérprete. Lo contrario también es cierto: los intérpretes son máquinas; sus instrucciones resultan ser de nivel superior a las de un ISA real. Para los intérpretes al estilo VM, los programas realmente se asemejan al código de máquina, albiet para una máquina muy simple. Java bytecode utiliza solo unos pocos "registros", uno de los cuales contiene un contador de programa. Entonces, un intérprete de VM es más como un emulador de hardware que los intérpretes en los ejemplos que he vinculado anteriormente.

Pero tenga en cuenta que, por razones de velocidad, la JVM de Oracle predeterminada funciona traduciendo ejecuciones de instrucciones de bytecode de Java en instrucciones x86 ("compilación justo a tiempo").