Que es un traductor. Lenguajes de programación. compiladores e intérpretes. El algoritmo del intérprete simple

Los ejecutores concretos de los lenguajes de programación son los traductores e intérpretes.

Traductor es un programa sobre la base del cual la computadora convierte los programas ingresados ​​en ella a lenguaje de máquina, ya que puede ejecutar programas escritos solo en el lenguaje de su procesador, y los algoritmos especificados en otro lenguaje deben traducirse a lenguaje de máquina antes de ejecutarse .

Traductor- un programa o medio técnico que realiza la emisión del programa.

Emisión de programa- transformación de un programa presentado en uno de los lenguajes de programación en un programa en otro lenguaje, equivalente en cuanto a los resultados del primero. El traductor suele realizar también diagnósticos de errores, generar diccionarios de identificadores, imprimir textos de programas, etc.

El lenguaje en el que se presenta el programa de entrada se llama original lenguaje, y el programa en sí - el código fuente. El idioma de salida se denomina idioma de destino o objetivo código. El propósito de la traducción es convertir el texto de un idioma a otro que sea comprensible para el destinatario del texto. En el caso de los programas traductores, el destino es dispositivo técnico(procesador) o programa intérprete.

Los traductores se implementan como compiladores o intérpretes. En términos de trabajo, un compilador y un intérprete son muy diferentes.

El lenguaje del procesador (código de máquina) es de bajo nivel. Un traductor que convierte programas en lenguaje de máquina que es aceptado y ejecutado directamente por el procesador se llama compilador.

Compilador(Inglés) compilador- compilador, colector) lee todo el programa, lo traduce y crea una versión completa del programa en lenguaje máquina, que luego se ejecuta. La salida del compilador es un archivo ejecutable binario.

Ventaja del compilador: el programa se compila una vez y no se requieren transformaciones adicionales para cada ejecución. En consecuencia, no se requiere un compilador en la máquina de destino para la que se está compilando el programa. Desventaja: un paso de compilación separado ralentiza la escritura y la depuración y dificulta la ejecución de programas pequeños, simples o de una sola vez.

Si el lenguaje fuente es un lenguaje ensamblador (un lenguaje de bajo nivel cercano al lenguaje de máquina), entonces el compilador de dicho lenguaje se llama ensamblador.

Otro método de implementación es cuando el programa se ejecuta con Interprete ninguna traducción en absoluto.

Interprete(Inglés) Interprete- intérprete, intérprete) traduce y ejecuta el programa línea por línea.

El intérprete simula mediante programación una máquina cuyo ciclo de búsqueda y ejecución opera con instrucciones en lenguajes de alto nivel en lugar de instrucciones de máquina. Dicho modelado de software crea una máquina virtual que implementa el lenguaje. Este enfoque se llama interpretación pura. La interpretación pura generalmente se usa para lenguajes con una estructura simple (por ejemplo, APL o Lisp). Intérpretes línea de comando procesar comandos en scripts en UNIX o en archivos por lotes (.bat) en MS-DOS, también generalmente en modo de interpretación pura.

La ventaja de un intérprete puro: la ausencia de acciones intermedias para la traducción simplifica la implementación del intérprete y lo hace más cómodo de usar, incluso en el modo interactivo. La desventaja es que el intérprete debe estar disponible en la máquina de destino donde se ejecutará el programa. Además, por regla general, hay una pérdida de velocidad más o menos importante. Y la propiedad de un intérprete puro, que los errores en el programa interpretado se detectan solo cuando se intenta ejecutar un comando (o línea) con un error, puede reconocerse como una desventaja y una ventaja.

Hay compromisos entre la compilación y la interpretación pura de las implementaciones del lenguaje de programación, cuando el intérprete, antes de ejecutar el programa, lo traduce a un lenguaje intermedio (por ejemplo, a bytecode o p-code), que es más conveniente para la interpretación (es decir, estamos hablando de un intérprete con traductor incorporado). Tal método se llama implementación mixta. Perl es un ejemplo de una implementación de lenguaje mixto. Este enfoque combina las ventajas de un compilador y un intérprete (mayor velocidad de ejecución y facilidad de uso) y desventajas (se requieren recursos adicionales para traducir y almacenar el programa en un lenguaje intermedio; se debe proporcionar un intérprete para ejecutar el programa en el máquina de destino). Al igual que en el caso de un compilador, una implementación mixta requiere que antes de ejecutar fuente no contenía errores (léxicos, sintácticos y semánticos).

Con el aumento de los recursos informáticos y la expansión de redes heterogéneas (incluida Internet) que conectan ordenadores de diferentes tipos y arquitecturas, ha surgido un nuevo tipo de interpretación, en el que el código fuente (o intermedio) se compila en código de máquina directamente en tiempo de ejecución. , "sobre la marcha". Las secciones de código ya compiladas se almacenan en caché para que, cuando se vuelva a acceder a ellas, reciban el control de inmediato, sin necesidad de volver a compilarlas. Este enfoque ha sido llamado compilación dinámica.

La ventaja de la compilación dinámica es que la velocidad de interpretación de programas se vuelve comparable a la velocidad de ejecución de programas en lenguajes compilados convencionales, mientras que el programa en sí se almacena y distribuye de una sola forma, independientemente de las plataformas de destino. La desventaja es una mayor complejidad de implementación y mayores requerimientos de recursos que en el caso de simples compiladores o puros intérpretes.

Este método es muy adecuado para aplicaciones web. En consecuencia, apareció la compilación dinámica y se admite hasta cierto punto en las implementaciones de Java, . NET Framework, Perl, Pitón.

Una vez que se ha compilado un programa, no se necesita ni el código fuente del programa ni el compilador para ejecutar el programa. Al mismo tiempo, el programa procesado por el intérprete debe volver a traducirse a lenguaje máquina cada vez que se ejecuta el programa. Es decir, el archivo fuente es directamente ejecutable.

Los programas compilados se ejecutan más rápido, pero los programas interpretados son más fáciles de arreglar y cambiar.

Cada lenguaje específico se enfoca ya sea en la compilación o en la interpretación, dependiendo del propósito para el cual fue creado. Por ejemplo, C++ se suele utilizar para resolver problemas bastante complejos en los que la velocidad de los programas es importante, por lo que este lenguaje se implementa mediante un compilador.

Para lograr una mayor velocidad de los programas en lenguajes de programación interpretados, se puede utilizar la traducción a un bytecode intermedio. Los lenguajes que permiten este truco son Java, Python y algunos otros lenguajes de programación.

El algoritmo de un intérprete simple:

2. analizar la instrucción y determinar las acciones apropiadas;

3. tomar las medidas apropiadas;

4. si no se alcanza la condición de terminación del programa, lea la siguiente instrucción y vaya al paso 2

Los lenguajes de programación se pueden dividir en compilados e interpretados.

Un programa en un lenguaje compilado se convierte (compila) en un conjunto de instrucciones para de este tipo procesador (código de máquina) y luego se escribe en un módulo ejecutable, que se puede iniciar para su ejecución como un programa separado. En otras palabras, el compilador traduce el código fuente del programa desde un lenguaje de programación de alto nivel a códigos binarios de instrucciones del procesador.

Si el programa está escrito en un lenguaje interpretado, entonces el intérprete ejecuta (interpreta) directamente el texto fuente sin traducción previa. El programa permanece en su idioma original y no se puede ejecutar sin un intérprete. Podemos decir que el procesador de la computadora es un intérprete del código máquina.

En resumen, el compilador traduce el código fuente del programa a lenguaje de máquina de forma inmediata y completa, creando un programa ejecutable separado, y el intérprete ejecuta el código fuente justo durante la ejecución del programa.

La división en lenguajes compilados e interpretados es algo arbitraria. Entonces, para cualquier lenguaje compilado tradicionalmente, como Pascal, puede escribir un intérprete. Además, la mayoría de los intérpretes "puros" modernos no ejecutan construcciones de lenguaje directamente, sino que las compilan en alguna representación intermedia de alto nivel (por ejemplo, con desreferencia variable y expansión macro).

Para cualquier lenguaje interpretado, puede crear un compilador; por ejemplo, el lenguaje Lisp, interpretado originalmente, se puede compilar sin restricciones. El código generado en tiempo de ejecución también se puede compilar dinámicamente en tiempo de ejecución.

Por regla general, los programas compilados se ejecutan más rápido y no requieren programas adicionales, ya que ya han sido traducidos a lenguaje máquina. Al mismo tiempo, con cada cambio en el texto del programa, se requiere su recompilación, lo que crea dificultades en el desarrollo. Además, un programa compilado solo puede ejecutarse en el mismo tipo de computadora, y generalmente bajo el mismo sistema operativo, para el cual fue diseñado el compilador. Para crear un ejecutable para un tipo diferente de máquina, se requiere una nueva compilación.

Los idiomas interpretados tienen algunas características adicionales específicas (ver arriba), además, los programas en ellos se pueden ejecutar inmediatamente después de la modificación, lo que facilita el desarrollo. Un programa de lenguaje interpretado a menudo se puede ejecutar en muchos tipos diferentes de máquinas y sistemas operativos sin esfuerzo adicional.

Sin embargo, los programas interpretados se ejecutan notablemente más lentos que los programas compilados y no se pueden ejecutar sin un programa de interpretación adicional.

Algunos lenguajes, como Java y C#, se encuentran entre compilados e interpretados. Es decir, el programa no se compila en lenguaje de máquina, sino en código de bajo nivel independiente de la máquina, código de bytes. A continuación, se ejecuta el código de bytes. máquina virtual. Para ejecutar el código de bytes, generalmente se usa la interpretación, aunque algunas de sus partes se pueden traducir a código de máquina directamente durante la ejecución del programa usando la compilación Just-in-time (JIT) para acelerar el programa. Para Java, el código de bytes es ejecutado por un virtual maquina Java(Java Virtual Machine, JVM), para C# - Common Language Runtime.

Este enfoque, en cierto sentido, le permite utilizar las ventajas tanto de los intérpretes como de los compiladores. También se debe mencionar el idioma Forth original, que tiene un intérprete y un compilador.

Dado que el texto escrito en un lenguaje de programación es incomprensible para una computadora, se requiere traducirlo a código de máquina. Tal traducción de un programa de un lenguaje de programación a un lenguaje de código de máquina se denomina traducción y se realiza mediante programas especiales: traductores.

Traductor: un programa de utilidad que convierte el programa fuente proporcionado en el lenguaje de programación de entrada en programa de trabajo presentado en un lenguaje objeto.

Actualmente, los compiladores se dividen en tres grupos principales: ensambladores, compiladores e intérpretes.

Assembler es una utilidad del sistema que convierte construcciones simbólicas en instrucciones de lenguaje de máquina. Una característica específica de los ensambladores es que literalmente traducen una instrucción simbólica en una instrucción de máquina. Por lo tanto, el lenguaje ensamblador (también llamado autocodificación) está diseñado para facilitar la percepción del conjunto de instrucciones de la computadora y acelerar la programación en este conjunto de instrucciones. Es mucho más fácil para un programador recordar la designación mnemotécnica de las instrucciones de máquina que su código binario.

Al mismo tiempo, el lenguaje ensamblador, además de los análogos de las instrucciones de la máquina, contiene muchas directivas adicionales que facilitan, en particular, la gestión de los recursos informáticos, la escritura de fragmentos repetitivos y la creación de programas de varios módulos. Por lo tanto, la expresividad del lenguaje es mucho más rica que un simple lenguaje de codificación simbólica, lo que aumenta en gran medida la eficiencia de la programación.

Un compilador es un programa de utilidad que traduce a lenguaje de máquina un programa escrito en un lenguaje de programación fuente. Al igual que un ensamblador, un compilador convierte un programa de un idioma a otro (la mayoría de las veces, al idioma de una computadora en particular). Sin embargo, los comandos del lenguaje de origen difieren significativamente en organización y potencia de los comandos del lenguaje de máquina. Hay idiomas en los que una instrucción del idioma de origen se traduce en 7-10 instrucciones de máquina. Sin embargo, también existen lenguajes en los que cada instrucción puede corresponder a 100 o más instrucciones máquina (por ejemplo, Prolog). Además, en los idiomas de origen se suele utilizar una tipificación estricta de los datos, que se lleva a cabo a través de su descripción preliminar. Es posible que la programación no dependa de la codificación de un algoritmo, sino de una reflexión cuidadosa sobre las estructuras o clases de datos. El proceso de traducción de dichos lenguajes generalmente se denomina compilación, y los lenguajes de origen generalmente se denominan lenguajes de programación de alto nivel (o lenguajes de alto nivel). La abstracción del lenguaje de programación del sistema de comandos de la computadora llevó a la creación independiente de una amplia variedad de lenguajes enfocados a resolver problemas específicos. Aparecieron los lenguajes para cálculos científicos, cálculos económicos, acceso a bases de datos y otros.

Un intérprete es un programa o dispositivo que realiza la traducción y ejecución operador por operador del programa fuente. A diferencia de un compilador, un intérprete no produce un programa en lenguaje de máquina como salida. Habiendo reconocido el comando del idioma fuente, lo ejecuta inmediatamente. Tanto los compiladores como los intérpretes usan los mismos métodos para analizar el código fuente de un programa. Pero el intérprete le permite comenzar a procesar datos después de escribir incluso un comando. Esto hace que el proceso de desarrollo y depuración de programas sea más flexible. Además, la falta de código de máquina de salida le permite no "tirar basura" dispositivos externos archivos adicionales, y el propio intérprete se puede adaptar con bastante facilidad a cualquier arquitectura de máquina desarrollándolo una sola vez en un lenguaje de programación ampliamente utilizado. Por lo tanto, los lenguajes interpretados como Java Script, VB Script se han generalizado. La desventaja de los intérpretes es la baja velocidad de ejecución del programa. Por lo general, los programas interpretados se ejecutan de 50 a 100 veces más lento que los programas escritos en código de máquina.

Un emulador es un programa o herramienta de software y hardware que brinda la capacidad de ejecutar un programa en una computadora determinada sin reprogramar, utilizando códigos o métodos para realizar operaciones que son diferentes de esta computadora. Un emulador es similar a un intérprete en que ejecuta directamente un programa escrito en algún idioma. Sin embargo, la mayoría de las veces es lenguaje de máquina o código intermedio. Ambos representan instrucciones en código binario que pueden ejecutarse inmediatamente después de reconocer el código de operación. A diferencia de los programas de texto, no es necesario reconocer la estructura del programa para seleccionar operandos.

Los emuladores se utilizan con bastante frecuencia para una variedad de propósitos. Por ejemplo, al desarrollar nuevos sistemas informáticos, primero se crea un emulador que ejecuta programas que se están desarrollando para computadoras que aún no existen. Esto le permite evaluar el sistema de mando y desarrollar una base software incluso antes de que se cree el equipo correspondiente.

Muy a menudo, el emulador se usa para ejecutar programas antiguos en computadoras nuevas. Por lo general, las computadoras más nuevas son más rápidas y tienen mejores periféricos. Esto le permite emular programas antiguos de manera más eficiente que ejecutarlos en computadoras antiguas.

Transcodificador: un programa o dispositivo de software que traduce programas escritos en el lenguaje de máquina de una computadora a programas en el lenguaje de máquina de otra computadora. Si el emulador es un análogo menos inteligente del intérprete, entonces el transcodificador actúa en la misma capacidad en relación con el compilador. De manera similar, el código de máquina fuente (y generalmente binario) o una representación intermedia se convierte en otro código similar en una instrucción y sin ningún análisis general de la estructura del programa fuente. Los transcodificadores son útiles cuando se transfieren programas de una arquitectura de computadora a otra. También se pueden utilizar para reconstruir el texto de un programa de lenguaje de alto nivel a partir del código binario disponible.

Un macroprocesador es un programa que reemplaza una secuencia de caracteres por otra. Es una especie de compilador. Genera texto de salida mediante el procesamiento de inserciones especiales ubicadas en el texto de origen. Estas inserciones están hechas de una manera especial y pertenecen a las construcciones del lenguaje, llamadas macrolenguaje. Los macroprocesadores se utilizan a menudo como complementos de los lenguajes de programación, lo que aumenta la funcionalidad de los sistemas de programación. Casi cualquier ensamblador contiene un macroprocesador, lo que aumenta la eficiencia del desarrollo de programas de máquina. Dichos sistemas de programación se denominan comúnmente ensambladores de macros.

Los macroprocesadores también se utilizan con lenguajes de alto nivel. Aumentan la funcionalidad de lenguajes como PL/1, C, C++. Los macroprocesadores se utilizan especialmente en C y C++, lo que facilita la escritura de programas. Los macroprocesadores aumentan la eficiencia de la programación sin cambiar la sintaxis y la semántica del lenguaje.

Sintaxis: un conjunto de reglas de un determinado idioma que determina la formación de sus elementos. En otras palabras, es un conjunto de reglas para la formación de secuencias de caracteres semánticamente significativos en un idioma dado. La sintaxis se especifica mediante reglas que describen los conceptos de un lenguaje determinado. Ejemplos de conceptos son: variable, expresión, operador, procedimiento. La secuencia de conceptos y su uso permitido en las reglas se determina sintácticamente estructuras correctas, formando programas. Es la jerarquía de los objetos, no cómo interactúan entre sí, lo que se define a través de la sintaxis. Por ejemplo, un operador puede aparecer solo en un procedimiento, mientras que una expresión en un operador, una variable puede consistir en un nombre e índices opcionales, etc. La sintaxis no está relacionada con fenómenos en el programa como "saltar a una etiqueta inexistente" o "una variable con el nombre dado no está definida". Esto es lo que hace la semántica.

Semántica: reglas y condiciones que determinan la relación entre los elementos del lenguaje y sus significados semánticos, así como la interpretación del significado significativo de las construcciones sintácticas del lenguaje. Los objetos del lenguaje de programación no solo se colocan en el texto de acuerdo con una cierta jerarquía, sino que también se interconectan adicionalmente a través de otros conceptos que forman varias asociaciones. Por ejemplo, una variable para la cual la sintaxis define una ubicación válida solo en declaraciones y algunas sentencias, tiene un tipo específico, puede usarse con un conjunto limitado de operaciones, tiene una dirección, un tamaño y debe declararse antes de usarse en un programa.

Un analizador es un componente del compilador que comprueba las declaraciones de origen para el cumplimiento de las reglas de sintaxis y la semántica de un lenguaje de programación determinado. A pesar del nombre, el analizador comprueba tanto la sintaxis como la semántica. Consta de varios bloques, cada uno de los cuales resuelve sus propios problemas. Se considerará con más detalle al describir la estructura del traductor. traductor compilador lenguaje programacion

Cualquier traductor realiza las siguientes tareas principales:

  • - analiza el programa emitido, en particular, determina si contiene errores sintácticos;
  • - genera un programa de salida (a menudo llamado programa objeto) en el lenguaje de instrucciones de máquina;
  • - asigna memoria para el programa objeto. 1.1 Intérpretes

Una ventaja citada a menudo de la implementación del intérprete es que permite el "modo inmediato". El modo inmediato le permite pedirle a la computadora una tarea como PRINT 3.14159*3/2.1 y le devuelve la respuesta tan pronto como presione ENTER (esto le permite usar una computadora de $3,000 como una calculadora de $10). Además, los intérpretes tienen atributos especiales que facilitan la depuración. Puede, por ejemplo, interrumpir el procesamiento de un programa intérprete, mostrar el contenido de ciertas variables, hojear el programa y luego continuar con la ejecución.

Lo que más les gusta a los programadores de los intérpretes es la capacidad de obtener una respuesta rápida. Aquí no hay necesidad de compilar, ya que el intérprete siempre está listo para intervenir en su programa. Introduce RUN y el resultado es tuyo ultimo cambio aparece en la pantalla.

Sin embargo, los lenguajes de interpretación tienen desventajas. Es necesario, por ejemplo, tener una copia del intérprete en la memoria en todo momento, mientras que muchas de las funciones del intérprete, y por lo tanto de sus funciones, pueden no ser necesarias para la ejecución de un programa en particular.

Una desventaja sutil de los intérpretes es que tienden a desalentar el buen estilo de programación. Debido a que los comentarios y otros detalles formalizables ocupan mucha memoria del programa, la gente tiende a no usarlos. El diablo está menos furioso que un programador intérprete de BASIC tratando de obtener un programa de 120K en una memoria de 60K. pero lo peor de todo es que los intérpretes son lentos.

Pasan demasiado tiempo averiguando qué hacer en lugar de hacer lo real. Al ejecutar sentencias de programa, el intérprete primero debe escanear cada sentencia para leer su contenido (¿qué me pide esta persona que haga?) y luego realizar la operación solicitada. Las declaraciones en bucles se escanean demasiado.

Considere el programa: en el intérprete BASIC 10 FOR N=1 TO 1000 20 PRINT N,SQR(N) 30 NEXT N en la primera transición a través de este programa, el intérprete BASIC debe adivinar qué significa la línea 20:

  • 1. convertir variable numérica N a cadena
  • 2. enviar una cadena a la pantalla
  • 3. pasar a la siguiente área de impresión
  • 4. calcular la raíz cuadrada de N
  • 5. convertir resultado a cadena
  • 6. enviar una cadena a la pantalla

En el segundo paso del bucle, todas estas conjeturas se repiten nuevamente, ya que todos los resultados del estudio de esta cadena hace unos milisegundos se olvidan por completo. Y así en todos los próximos 998 pases. Obviamente, si pudieras separar de alguna manera la fase de escaneo/comprensión de la fase de ejecución, tendrías más programa rapido. Y eso es exactamente para lo que están los compiladores.


4. Principios básicos de construcción de traductores. Traductores, compiladores e intérpretes: el esquema general de trabajo. Compiladores e intérpretes modernos.

Principios básicos de la construcción de traductores.

Traductores, compiladores, intérpretes: el esquema general de trabajo.

Definición de traductor, compilador, intérprete

Para empezar, demos algunas definiciones: qué son los traductores y compiladores ya mencionados muchas veces.

Definición formal de un traductor

Traductor es un programa que traduce un programa de entrada en el idioma fuente (entrada) a un programa de salida equivalente en el idioma resultante (salida). En esta definición, la palabra "programa" aparece tres veces, y esto no es un error ni una tautología. De hecho, tres programas siempre participan en el trabajo del traductor.

En primer lugar, el traductor en sí es un programa 1; generalmente se incluye en el software del sistema de un sistema informático. Es decir, un traductor es una pieza de software (software), es un conjunto de instrucciones y datos de máquina y es ejecutado por una computadora, como todos los demás programas dentro de un sistema operativo (SO). Todos los componentes del traductor son fragmentos o módulos del programa con sus propios datos de entrada y salida.

En segundo lugar, los datos iniciales para el trabajo del traductor son el texto del programa de entrada, alguna secuencia de oraciones del lenguaje de programación de entrada. Suele ser un archivo de caracteres, pero este archivo debe contener texto de programa que satisfaga los requisitos sintácticos y semánticos del idioma de entrada. Además, este archivo tiene algún significado, determinado por la semántica del idioma de entrada.

En tercer lugar, la salida del traductor es el texto del programa resultante. El programa resultante se construye de acuerdo con las reglas sintácticas especificadas en el idioma de salida del traductor, y su significado está determinado por la semántica del idioma de salida. Un requisito importante en la definición de un traductor es la equivalencia de los programas de entrada y salida. La equivalencia de dos programas significa la coincidencia de su significado en cuanto a la semántica del lenguaje de entrada (para el programa fuente) y la semántica del lenguaje de salida (para el programa resultante). Sin cumplir este requisito, el propio traductor pierde todo sentido práctico.

Entonces, para crear un traductor, primero debe seleccionar los idiomas de entrada y salida. Desde el punto de vista de convertir oraciones en el idioma de entrada en oraciones equivalentes en el idioma de salida, el traductor actúa como traductor. Por ejemplo, traducir un programa de C a lenguaje ensamblador esencialmente no es diferente de traducir, digamos, del ruso al inglés, con la única diferencia de que la complejidad de los lenguajes es algo diferente (¿por qué no hay traductores de lenguajes naturales? ​- ver el apartado "Lenguajes y gramáticas de clasificación, capítulo 9). Por lo tanto, la misma palabra "traductor" (Inglés: traductor) significa "traductor".

El resultado del trabajo del traductor será el programa resultante, pero solo si el texto del programa fuente es correcto; no contiene errores en términos de sintaxis y semántica del idioma de entrada. Si el programa fuente es incorrecto (contiene al menos un error), el resultado del trabajo del traductor será un mensaje de error (por regla general, con explicaciones adicionales y una indicación de la ubicación del error en el programa fuente). En este sentido, el traductor es similar a un traductor, por ejemplo, del inglés, al que se le deslizó el texto equivocado.

Teóricamente, es posible implementar un traductor usando hardware. El autor ha conocido tales desarrollos, pero se desconoce su amplia aplicación práctica. En este caso, todos los componentes del traductor se pueden implementar en forma de hardware y sus fragmentos, ¡entonces el circuito de reconocimiento puede obtener una implementación completamente práctica!

definición del compilador.

La diferencia entre un compilador y un traductor

Además del concepto de "traductor", también se usa ampliamente el concepto de "compilador", que tiene un significado cercano.

compilador - es un traductor que traduce un programa fuente a su programa objeto equivalente en lenguaje máquina o lenguaje ensamblador.

Por lo tanto, un compilador se diferencia de un traductor solo en que su programa resultante siempre debe estar escrito en código de máquina o lenguaje ensamblador. El programa traductor resultante, en general, se puede escribir en cualquier idioma; es posible, por ejemplo, traducir programas de Pascal a C. En consecuencia, cada compilador es un traductor, pero no al revés: no todos los traductores serán un compilador . Por ejemplo, el traductor de Pascal a C mencionado anteriormente no será 1 .

La palabra "compilador" en sí proviene de Término en inglés"compilador" ("compilador", "enlazador"). Aparentemente, el término debe su origen a la capacidad de los compiladores para crear programas objeto basados ​​en programas fuente.

El programa compilador resultante se denomina "programa objeto" o "código objeto". El archivo en el que se escribe generalmente se denomina "archivo de objeto". Incluso cuando el programa resultante se genera en un lenguaje de máquina, existe una diferencia significativa entre un programa objeto (archivo objeto) y un programa ejecutable (archivo ejecutable). El programa generado por el compilador no se puede ejecutar directamente en una computadora, ya que no está atado a un área de memoria específica donde se debe ubicar su código y datos (para más detalles, consulte la sección "Principios de funcionamiento de los sistemas de programación", Capítulo 15 ) 2 .

Los compiladores son, con mucho, el tipo de compilador más común (muchos los consideran el único tipo de compilador, aunque no lo son). Tienen la aplicación práctica más amplia, lo que se debe a la amplia distribución de todo tipo de lenguajes de programación. En lo que sigue, siempre hablaremos de compiladores, lo que significa que el programa de salida está escrito en

Naturalmente, los traductores y compiladores, como todos los demás programas, son desarrollados por una persona (personas), generalmente un grupo de desarrolladores. En principio, podrían crearlo directamente en el lenguaje de instrucciones de máquina, pero la cantidad de código y datos de los compiladores modernos es tal que su creación en el lenguaje de instrucciones de máquina es casi imposible en un tiempo razonable con costos de mano de obra razonables. Por lo tanto, casi todos los compiladores modernos también se crean utilizando compiladores (generalmente, las versiones anteriores de compiladores del mismo fabricante actúan en este rol). Y en esta capacidad, el compilador ya es el programa de salida para otro compilador, que no es ni mejor ni peor que todos los demás programas de salida generados 2 .

Definición de intérprete. La diferencia entre intérpretes y traductores

Además de los conceptos similares de "traductor" y "compilador", existe un concepto fundamentalmente diferente de intérprete.

Interprete - es un programa que toma un programa de entrada en un lenguaje fuente y lo ejecuta.

A diferencia de los traductores, los intérpretes no generan el programa resultante (ni ningún código resultante en general), y esta es la diferencia fundamental entre ellos. El intérprete, al igual que el traductor, analiza el texto del programa fuente. Sin embargo, no genera el programa resultante, sino que ejecuta inmediatamente el programa original de acuerdo con su significado dado por la semántica del lenguaje de entrada. Así, el resultado del intérprete será el resultado dado por el significado del programa fuente, si este programa es correcto, o un mensaje de error si el programa fuente es incorrecto.

Por supuesto, para ejecutar el programa fuente, el intérprete debe convertirlo de alguna manera a lenguaje de máquina, ya que de lo contrario es imposible ejecutar programas en una computadora. Lo hace, pero los códigos de máquina resultantes no son accesibles, no son visibles para el usuario del intérprete. Estos códigos de máquina son generados por el intérprete, ejecutados y destruidos

1 Cabe mencionar especialmente que ahora en los sistemas de programación modernos han comenzado a aparecer compiladores en los que el programa resultante se crea no en el lenguaje de instrucciones máquina y no en lenguaje ensamblador, sino en algún lenguaje intermedio. Por sí mismo, este lenguaje intermedio no puede ejecutarse directamente en una computadora, sino que requiere un intérprete intermedio especial para ejecutar programas escritos en él. Aunque en este caso el término "traductor" probablemente sería más correcto, en la literatura se utiliza el término "compilador", ya que el lenguaje intermedio es un lenguaje de muy bajo nivel, estando relacionado con instrucciones de máquina y lenguajes ensambladores.

Esto trae a colación la vieja cuestión del huevo y la gallina. Por supuesto, en la primera generación, los primeros compiladores se escribieron directamente en instrucciones de máquina, pero luego, con la llegada de los compiladores, esta práctica se abandonó. Incluso las partes más críticas de los compiladores se crean, como mínimo, utilizando lenguaje ensamblador, y también son procesadas por el compilador. tozhayutsya según sea necesario, según lo requiera una implementación específica) del intérprete. El usuario ve el resultado de la ejecución de estos códigos: está el resultado de la ejecución del programa fuente (el requisito para la equivalencia del programa fuente y los códigos de máquina generados en este caso, (condicionalmente, debe cumplirse).

Con más detalle, los temas relacionados con la implementación de los intérpretes y su salida de los compiladores se discuten más adelante en la sección correspondiente.

Finalidad de los traductores, compiladores e intérpretes. Ejemplos de implementación

Los primeros programas que se crearon para las computadoras de la primera generación se escribieron directamente en el lenguaje de los códigos de máquina. Fue realmente un infierno de un trabajo. Inmediatamente quedó claro que una persona no debe ni puede hablar el lenguaje de los comandos de la máquina, incluso si es un especialista en informática. O; Sin embargo, todos los intentos de enseñarle a una computadora a hablar los idiomas de las personas se han visto coronados por el éxito y es poco probable que alguna vez lo sean (para lo cual hay ciertas razones positivas discutidas en el primer capítulo de este manual).

Desde entonces, todo el desarrollo de los programas informáticos ha estado indisolublemente ligado al surgimiento y desarrollo de los compiladores.

Los primeros compiladores fueron compiladores de lenguajes ensambladores o, como se les llamaba, códigos mnemotécnicos. Los mnemocódigos han convertido la "letra de filkin" de los comandos de máquina en un lenguaje más o menos comprensible para un especialista: las designaciones mónicas (principalmente en inglés) de estos comandos. (Ya se ha vuelto mucho más fácil dar programas, pero ni una sola computadora es capaz de ejecutar mnems (lenguaje ensamblador), por lo tanto, surgió la necesidad de crear compiladores. Estos compiladores son elementales, pero continúan desempeñando un papel importante en Los sistemas de programación hoy Más detalles sobre el lenguaje ensamblador y los compiladores de su historia: más adelante en la sección correspondiente.

El siguiente paso fue la creación de lenguajes de alto nivel. Los lenguajes de alto nivel (a ellos pertenecen la mayoría de los lenguajes de programación) representan algún eslabón intermedio entre los lenguajes puramente formales y los lenguajes de comunicación natural de las personas. Del primero obtuvieron una estricta malización de la estructura sintáctica de las oraciones del idioma, del segundo: la parte significativa del vocabulario, la semántica de las estructuras y expresiones principales (con elementos de operaciones matemáticas que provienen del álgebra).

El advenimiento de los lenguajes de alto nivel simplificó mucho el proceso de programación, aunque no lo redujo al “nivel de ama de casa”, como alegaban con arrogancia algunos autores en los albores del nacimiento de los lenguajes de programación 1 . Había pocos idiomas de este tipo, luego docenas, ahora, probablemente, hay más de cien de ellos. No se vislumbra el final de este proceso. Sin embargo, todavía prevalecen las computadoras de la arquitectura tradicional "Neumann", que solo pueden entender instrucciones de máquina, por lo que la cuestión de crear compiladores sigue siendo relevante.

Tan pronto como hubo una necesidad masiva de crear compiladores, comenzó a desarrollarse una teoría especializada. Con el tiempo, encontró una aplicación práctica en una variedad de compiladores creados. Se han creado y se siguen creando compiladores no solo para lenguajes nuevos, sino también para lenguajes conocidos desde hace mucho tiempo. Muchos fabricantes, desde compañías conocidas y de buena reputación (como Microsoft o Inprise) hasta equipos de autores poco conocidos, lanzan cada vez más muestras de compiladores nuevas al mercado. Esto se debe a una serie de razones, que se discutirán a continuación.

Finalmente, dado que la mayoría de los aspectos teóricos en el campo de los compiladores han recibido su implementación práctica (y esto, todo hay que decirlo, sucedió con bastante rapidez, a finales de los años 60), el desarrollo de los compiladores ha ido por el camino de su amabilidad con una persona - un usuario, un desarrollador de programas en lenguajes de alto nivel. La conclusión lógica de este proceso fue la creación de sistemas de programación - sistemas de software, que combinan, además de compiladores directos, muchos componentes de software relacionados. Habiendo aparecido, los sistemas de programación conquistaron rápidamente el mercado y ahora lo dominan en su mayor parte (de hecho, los compiladores separados son una rareza entre las herramientas de software modernas). Para obtener información sobre qué son los sistemas de programación modernos y cómo están organizados, consulte el capítulo Sistemas de programación modernos. Ahora los compiladores son una parte integral de cualquier sistema informático. Sin su existencia, la programación de cualquier tarea aplicada sería difícil, si no imposible. Y la programación de tareas especializadas del sistema, por regla general, se lleva a cabo si no en un lenguaje de alto nivel (actualmente, C se usa con mayor frecuencia en este rol), entonces en lenguaje ensamblador, por lo tanto, se usa un compilador apropiado. La programación directa en lenguajes de código de máquina es extremadamente rara y solo para resolver problemas muy limitados. Algunas palabras sobre ejemplos de implementaciones de compiladores e intérpretes, así como también cómo se relacionan con otros existentes herramientas de software. Los compiladores, como se mostrará más adelante, suelen ser algo más fáciles de implementar que los intérpretes. En términos de eficiencia, también los superan: es obvio que el código compilado siempre se ejecutará más rápido que la interpretación de un programa fuente similar. Además, no todos los lenguajes de programación permiten la construcción de un intérprete simple. Sin embargo, los intérpretes tienen una ventaja significativa: el código compilado siempre está vinculado a la arquitectura del sistema informático al que está orientado, y el programa fuente solo está vinculado a la semántica del lenguaje de programación, que es mucho más fácil de estandarizar. Este aspecto fue inicialmente ignorado. Los primeros compiladores fueron compiladores mnemotécnicos. Sus descendientes, compiladores modernos de lenguajes ensambladores, existen para casi todos los sistemas informáticos conocidos. Están extremadamente rígidamente enfocados en la arquitectura. Luego estaban los compiladores de lenguajes como FORTRAN, ALGOL-68, PL/1. Se centraron en computadoras centrales con procesamiento por lotes de tareas. De los anteriores, solo FORTRAN, quizás, se sigue utilizando hasta el día de hoy, ya que tiene una gran cantidad de bibliotecas para diversos propósitos. Muchos idiomas, después de haber nacido, no se generalizaron: ADA, Modula, Simula son conocidos solo por un círculo reducido de especialistas. Al mismo tiempo, el mercado sistemas de software dominado por compiladores de lenguajes que no se preveía que tuvieran un futuro brillante. En primer lugar, ahora es C y C ++. El primero de ellos nació con sistemas operativos como UNIX, junto con él ganó su "lugar bajo el sol", y luego pasó a sistemas operativos de otros tipos. El segundo incorporó con éxito en sí mismo un ejemplo de la implementación de las ideas de la programación orientada a objetos sobre una base práctica bien establecida 1 . También se puede mencionar el bastante común Pascal, que, inesperadamente para muchos, ha ido más allá del ámbito de un lenguaje puramente educativo para un entorno universitario.

La historia de los intérpretes no es tan rica (¡todavía!). Como ya se mencionó, inicialmente no se les dio una importancia significativa, ya que en casi todos los aspectos son inferiores a los compiladores. De los lenguajes conocidos que implicaban interpretación, solo se puede mencionar Basic, aunque la mayoría ahora conoce su implementación compilada de Visual Basic, hecha por Microsoft. Sin embargo, ahora la situación ha cambiado un poco, ya que el tema de la portabilidad del software y su independencia hardware-plataforma es cada vez más importante con el desarrollo de Internet. El ejemplo más conocido hoy en día es el lenguaje Java (que a su vez combina compilación e interpretación) y el JavaScript asociado. Después de todo, el lenguaje HTML, que sustenta el protocolo HTTP que dio lugar a un desarrollo tan rápido de la World Wide Web, también es un lenguaje interpretado. Según el autor, todavía esperan sorpresas para todos en el campo de la aparición de nuevos intérpretes, y el primero de ellos ya apareció, por ejemplo, el lenguaje C # ("C-sharp", pero el nombre está en todas partes como “C sostenido”), anunciado por Microsoft.

O b la historia de los lenguajes de programación y el estado actual del mercado de compiladores, se puede hablar durante mucho tiempo y mucho. El autor considera posible limitarse a lo ya dicho, ya que no es el objeto de este manual. Aquellos que lo deseen pueden consultar la literatura.

etapas de la traducción. Esquema general del traductor

En la fig. 13.1 muestra el esquema general del compilador. De ella queda claro que b En general, el proceso de compilación consta de dos etapas principales: síntesis y análisis.

En la etapa de análisis, se reconoce el texto del programa fuente, se crean y completan las tablas de identificadores. El resultado de su trabajo es una cierta representación interna del programa, comprensible para el compilador.

En la etapa de síntesis, a partir de la representación interna del programa y de la información contenida en la tabla (tablas) de identificadores, se genera el texto del programa resultante. El resultado de esta etapa es el código objeto.

Además, el compilador contiene una parte responsable de analizar y corregir errores, que, si hay un error en el texto del programa fuente, debe informar al usuario lo más completo posible sobre el tipo de error y el lugar donde ocurrió. En el mejor de los casos, el compilador puede ofrecer al usuario una opción para corregir el error.

Estos pasos, a su vez, consisten en pasos más pequeños llamados fases de compilación. La composición de las fases de compilación se da en la forma más general, su implementación específica y el proceso de interacción.

Primero, es un reconocedor del idioma del programa fuente. Luego debe recibir una cadena de caracteres del idioma de entrada como entrada, verificar la pertenencia al idioma y, además, identificar las reglas por las cuales se construyó esta cadena (ya que la respuesta a la pregunta sobre pertenencia "sí" y "no" es de poco interés). Es interesante que el usuario, el autor del programa de entrada, actúe como generador de cadenas de lenguaje de entrada.

En segundo lugar, el compilador es un generador del lenguaje del programa resultante. Debe construir la cadena del idioma de salida por oprah en la salida; reglas dadas, lenguaje de máquina previsto o lenguaje i dechado. El reconocedor de esta cadena será el sistema informático bajo el cual se crea el programa resultante.

Análisis léxico(escáner) es la parte del compilador que lee programas literales en el idioma de origen y crea palabras (tokens) del idioma de origen a partir de ellos. La entrada del analizador léxico recibe el texto del programa fuente y la información de salida se transmite para que el compilador la procese en la etapa de análisis. Desde un punto de vista teórico, el analizador léxico no es una parte obligatoria y necesaria del compilador. Sin embargo, existen razones que determinan su presencia en casi todos los compiladores. Para más detalles, consulte la sección “Analizadores léxicos (anotadores). Principios de construcción de escáneres”.

análisis es la parte principal del compilador en la etapa de análisis. O realiza la extracción de construcciones sintácticas en el texto del programa fuente, procesado por el analizador léxico. En la misma fase de la compilación, se comprueba la corrección sintáctica del programa. El analizador desempeña un papel importante: el papel del reconocedor de texto del lenguaje de programación de entrada (consulte la sección "Analizadores. Traducción controlada sintácticamente" de este capítulo).

Análisis semántico es la parte del compilador que verifica el texto correcto* del programa fuente en términos de la semántica del lenguaje de entrada. Validación directa de CRS, el análisis semántico debe realizar la conversión; Definiciones de texto requeridas por la semántica del idioma de origen (como la adición de funciones de conversión de tipos implícitas). En varias implementaciones de comp. Tori, el análisis semántico puede entrar en parte en la fase de análisis sintáctico *, en parte, en la fase de preparación para la generación de código.

Preparación para la generación de código es la fase en la que el compilador realiza acciones preliminares que están directamente relacionadas con la síntesis del texto del programa resultante, pero que aún no conducen a la generación de texto en el idioma de destino. Normalmente, esta fase incluye actividades relacionadas con la identificación de elementos del lenguaje, asignación de memoria, etc. (ver la sección "Análisis semántico y preparación para la generación de código", Capítulo 14).

Codigo de GENERACION- esta es la fase directamente relacionada con la generación de la coma de las oraciones constituyentes del idioma de destino y el texto resultante como un todo

Por supuesto, pueden variar dependiendo de la versión del compilador. Sin embargo, de una forma u otra, todas las fases presentadas casi siempre están presentes en cada compilador en particular.

El compilador como un todo, desde el punto de vista de la teoría de los lenguajes formales, actúa en “dos roles”, realiza dos funciones principales. programas Esta es la fase principal en la etapa de síntesis del programa resultante. Además de la generación directa del texto del programa resultante, la generación suele incluir también la optimización, un proceso asociado con el procesamiento de texto ya generado. A veces, la optimización se destaca como una fase separada de la compilación, ya que tiene un impacto significativo en la calidad y la eficiencia del programa resultante (consulte las secciones "Generación de código. Métodos de generación de código" y "Optimización de código. Métodos básicos de optimización", Capítulo 14 ).

Tablas de identificadores(a veces "tablas de caracteres") son conjuntos de datos especialmente organizados que sirven para almacenar información sobre los elementos del programa fuente, que luego se utilizan para generar el texto del programa resultante. Puede haber una tabla de identificadores en una implementación particular del compilador, o puede haber varias tablas de este tipo. Los elementos del programa fuente, cuya información debe almacenarse durante la compilación, son variables, constantes, funciones, etc. - la composición específica del conjunto de elementos depende del lenguaje de programación de entrada utilizado. El concepto de "tabla" no implica en absoluto que este almacén de datos deba organizarse precisamente en forma de tablas u otras matrices de información; los posibles métodos para organizarlos se analizan en detalle más adelante, en la sección "Tablas de identificadores". Organización de tablas de identificadores.

Se muestra en la figura. 13.1 La división del proceso de compilación en fases tiene fines más bien metodológicos y en la práctica puede no observarse tan estrictamente. Las siguientes subsecciones de este manual discuten las diversas opciones para la organización técnica de las fases de compilación presentadas. Indica cómo se pueden relacionar entre sí. Aquí consideramos sólo los aspectos generales de este tipo de relación.

Primero, durante la fase de análisis léxico, los tokens se extraen del texto del programa de entrada en la medida en que sean necesarios para la siguiente fase de análisis. En segundo lugar, como se mostrará a continuación, el análisis y la generación de código se pueden realizar al mismo tiempo. Por lo tanto, estas tres fases de compilación pueden funcionar en combinación, y la preparación para la generación de código también se puede realizar junto con ellas. A continuación, consideramos los aspectos técnicos de la implementación de las principales fases de compilación, que están estrechamente relacionados con el concepto paso.

Concepto de pasaje. Compiladores multipaso y paso único

Como ya se mencionó, el proceso de compilación de programas consta de varias fases. En los compiladores reales, la composición de estas fases puede diferir un poco de la considerada anteriormente: algunas de ellas se pueden dividir en componentes, mientras que otras, por el contrario, se combinan en una sola fase. El orden en que se ejecutan las fases de compilación también puede variar entre las distintas variantes del compilador. En un caso, el compilador mira el texto del programa fuente, realiza inmediatamente todas las fases de compilación y recibe el resultado: el código objeto. En otra variante, realiza solo algunas de las fases de compilación en el texto fuente y no recibe el resultado final, sino un conjunto de algunos datos intermedios. Luego, estos datos se procesan nuevamente y este proceso se puede repetir varias veces.

Los compiladores reales, por regla general, traducen el texto del programa fuente en varias pasadas.

paso - este es el proceso de lectura secuencial por parte del compilador de datos de la memoria externa, procesándolos y colocando el resultado del trabajo en la memoria externa. La mayoría de las veces, un solo paso implica la ejecución de una o más fases de compilación. El resultado de los pases intermedios es la representación interna del programa fuente, el resultado del último pase es el programa objeto resultante.

Como memoria externa cualquier portador de información puede actuar: RAM de una computadora, unidades en discos magnéticos, cintas magnéticas, etc. Los compiladores modernos, por regla general, se esfuerzan por aprovechar al máximo la RAM de una computadora para el almacenamiento de datos, y solo cuando falta de la memoria disponible, se utilizan discos duros magnéticos. Otros medios de almacenamiento no se utilizan en los compiladores modernos debido a la baja tasa de intercambio de datos.

Durante cada pase, el compilador tiene acceso a la información obtenida de todos los pases anteriores. Por regla general, tiende a utilizar en primer lugar sólo la información obtenida en el pase inmediatamente anterior al actual, pero en principio puede acceder a datos de pases anteriores hasta el código fuente del programa. La información que obtiene el compilador al ejecutar pases no está disponible para el usuario. O bien se almacena en memoria de acceso aleatorio, que el compilador libera después de completar el proceso de traducción, o está en forma de archivos temporales en el disco, que también se destruyen después de que finaliza el compilador. Por lo tanto, una persona que trabaja con un compilador puede que ni siquiera sepa cuántos pasos realiza el compilador; siempre ve solo el texto del programa fuente y el programa objeto resultante. Pero el número de pases realizados es un factor importante especificaciones técnicas compilador, empresas de renombre: los desarrolladores de compiladores generalmente lo indican en la descripción de su producto.

Está claro que los desarrolladores están tratando de minimizar la cantidad de pases realizados por los compiladores. Esto aumenta la velocidad del compilador, reduce la cantidad de memoria que necesita. Un compilador de un solo paso que toma un programa fuente como entrada e inmediatamente produce el programa objeto resultante es ideal.

Sin embargo, no siempre es posible reducir el número de pasadas. El número de pases necesarios está determinado principalmente por las reglas gramaticales y semánticas del idioma de origen. Cuanto más compleja sea la gramática del lenguaje y más opciones sugieran las reglas semánticas, más pases realizará el compilador (por supuesto, las calificaciones de los desarrolladores del compilador también juegan un papel). Por ejemplo, esta es la razón por la que los compiladores de Pascal suelen ser más rápidos que los compiladores de C: la gramática de Pascal es más simple y las reglas semánticas son más estrictas. Los compiladores de un solo paso son raros, solo posibles para lenguajes muy simples. Los compiladores reales suelen realizar de dos a cinco pases. Entonces, los compiladores reales son de múltiples pasos. Los más comunes son los compiladores de dos y tres pasos, por ejemplo: el primer paso es el análisis léxico, el segundo es el análisis sintáctico y semántico, el tercero es la generación y optimización de código (las opciones de ejecución, por supuesto, dependen del desarrollador). En los sistemas de programación modernos, el primer paso del compilador (análisis léxico del código) a menudo se realiza en paralelo con la edición del código del programa fuente (esta variante de construcción de compiladores se analiza más adelante en este capítulo).

Intérpretes. Características de la construcción de intérpretes.

Interprete es un programa que toma un programa de entrada en un lenguaje fuente y lo ejecuta. Como se mencionó anteriormente, la principal diferencia entre intérpretes y traductores y compiladores es que el intérprete no genera el programa resultante, sino que simplemente ejecuta el programa fuente.

El término 'intérprete', como 'traductor', significa 'traductor'. Desde el punto de vista de la terminología, estos conceptos son similares, pero desde el punto de vista de la teoría de los lenguajes formales y la compilación, existe una gran diferencia fundamental entre ellos. Si los conceptos de "traductor" y "compilador" son casi indistinguibles, entonces no pueden confundirse con el concepto de "intérprete".

La forma más sencilla de implementar un intérprete sería tener el programa fuente primero traducido completamente a instrucciones de máquina y luego ejecutarlo inmediatamente. En tal implementación, el intérprete, de hecho, no se diferenciaría mucho del compilador, con la única diferencia de que el programa resultante sería inaccesible para el usuario. La desventaja de tal intérprete sería que el usuario tendría que esperar a que se compile todo el programa fuente antes de poder ejecutarlo. De hecho, tal intérprete no tendría mucho sentido, no proporcionaría ninguna ventaja sobre un compilador similar 1 . Por lo tanto, la gran mayoría de los intérpretes actúan de tal manera que ejecutan el programa fuente secuencialmente, a medida que ingresa a la entrada del intérprete. Entonces, el usuario no tiene que esperar a que se complete la compilación de todo el programa fuente. Además, puede ingresar secuencialmente el programa fuente e inmediatamente observar el resultado de su ejecución a medida que se ingresan los comandos.

Con este orden de trabajo del intérprete, se manifiesta una característica esencial que lo distingue del compilador: si el intérprete ejecuta los comandos a medida que llegan, entonces no puede realizar la optimización del programa fuente. En consecuencia, la fase de optimización en la estructura general del intérprete estará ausente. De lo contrario, diferirá poco de la estructura de un compilador similar. Solo debe tenerse en cuenta que en la última etapa, la generación de código, las instrucciones de la máquina no se escriben en el archivo del objeto, sino que se ejecutan a medida que se generan.

La ausencia de un paso de optimización define otra característica típica de muchos intérpretes: la notación polaca inversa se usa muy a menudo como una representación interna del programa (consulte la sección "Generación de código. Métodos de generación de código", Capítulo 14). Esta forma conveniente de representación de operaciones tiene solo un inconveniente importante: es difícil de optimizar. Pero en los intérpretes, esto es exactamente lo que no se requiere.

No todos los lenguajes de programación permiten la construcción de intérpretes que puedan ejecutar el programa fuente a medida que llegan los comandos, para ello el lenguaje debe permitir la existencia de un compilador que analice el programa fuente de una sola pasada. Además, un lenguaje no puede interpretarse como se reciben comandos si permite que aparezcan llamadas a funciones y estructuras de datos antes de que se describan directamente. Por lo tanto, lenguajes como C y Pascal no pueden ser interpretados por este método.

La ausencia de un paso de optimización conduce al hecho de que la ejecución del programa con la ayuda de un intérprete es menos eficiente que con la ayuda de un compilador similar. Además, al interpretar, el programa fuente debe volver a analizarse cada vez que se ejecuta, mientras que al compilar, se analiza solo una vez y el archivo objeto siempre se usa después de eso. Por lo tanto, los intérpretes siempre pierden rendimiento en comparación con los compiladores.

La ventaja del intérprete es la independencia de la ejecución del programa de la arquitectura del sistema informático de destino. Como resultado de la compilación se obtiene un código objeto, que siempre está orientado a una determinada arquitectura. Para cambiar a otra arquitectura de los sistemas informáticos de destino], se debe volver a compilar el programa. Y para interpretar programas] es necesario tener solo su código fuente y un intérprete del lenguaje apropiado.

Los intérpretes durante mucho tiempo fueron significativamente inferiores en prevalencia quién peladores Por regla general, los intérpretes existían para una gama limitada de lenguajes de programación relativamente simples (como, por ejemplo, las herramientas básicas de desarrollo de software profesional de alto rendimiento se construyeron sobre compiladores.

Un nuevo ímpetu para el desarrollo de intérpretes fue dado por la difusión de Red de computadoras. Tales redes pueden incluir computadoras de una arquitectura personal, y entonces el requisito de una implementación uniforme del texto del programa fuente en cada una de ellas se vuelve decisivo. Por lo tanto, con el desarrollo de redes globales y la expansión de Internet en todo el mundo,
En los sistemas de programación modernos, existen implementaciones de software que combinan las funciones de un compilador y las funciones de un intérprete; según los requisitos del usuario, el programa fuente se compila o ejecuta (interpreta). Además, algunos lenguajes de programación modernos involucran dos etapas de desarrollo: primero, el programa fuente se compila en un código intermedio (algún lenguaje de bajo nivel), y luego este resultado de la compilación se ejecuta utilizando el intérprete de este lenguaje intermedio. En más detalle, las opciones para tales sistemas se discuten en el capítulo "Sistemas de programación modernos".

Un ejemplo ampliamente utilizado de lenguaje interpretado es HTML (Hypertext Markup Language), un lenguaje de descripción de hipertexto. Casi toda la estructura de Internet funciona actualmente sobre esta base. Otro ejemplo - Lenguajes Java y JavaScript: combina las funciones de compilación e interpretación. El texto del programa fuente se compila en un código binario intermedio que no depende de la arquitectura del sistema informático de destino, este código se distribuye a través de la red y se ejecuta en el lado receptor: se interpreta.

Traductores de lenguaje ensamblador ("ensambladores")

lenguaje ensamblador - es un lenguaje de bajo nivel. La estructura y la interconexión de las cadenas de este lenguaje están cerca de las instrucciones de la máquina del sistema informático de destino, donde se debe ejecutar el programa resultante. El uso del lenguaje ensamblador permite al desarrollador administrar los recursos (procesador, RAM, dispositivos externos, etc.) del sistema informático de destino al nivel de las instrucciones de la máquina. Cada instrucción del programa fuente en lenguaje ensamblador se convierte en una instrucción de máquina como resultado de la compilación.

El traductor del lenguaje ensamblador siempre será, por supuesto, un compilador, ya que el lenguaje del programa resultante son los códigos de máquina. Un traductor de lenguaje ensamblador a menudo se denomina simplemente "ensamblador" o "programa ensamblador".

Implementación de compiladores desde lenguaje ensamblador

El lenguaje ensamblador, por regla general, contiene códigos mnemotécnicos para instrucciones de máquina. En la mayoría de los casos, se utilizan mnemotécnicos de comando en inglés, pero existen otras variantes de lenguajes ensambladores (incluidas las versiones en ruso). Es por eso que el lenguaje ensamblador solía llamarse "lenguaje mnemocode" (ahora este nombre prácticamente ya no se usa). Todas las instrucciones posibles en cada lenguaje ensamblador se pueden dividir en dos grupos: el primer grupo incluye instrucciones en lenguaje ordinario, que se convierten en instrucciones de máquina durante la traducción; el segundo grupo consta de instrucciones de lenguaje especiales que no se convierten en instrucciones de máquina, pero que el compilador utiliza para realizar tareas de compilación (como, por ejemplo, la tarea de asignar memoria). La sintaxis del lenguaje es extremadamente simple. Los comandos del programa fuente generalmente se escriben de tal manera que hay un comando por línea del programa. Cada instrucción en lenguaje ensamblador, por regla general, se puede dividir en tres componentes, siguiendo secuencialmente uno tras otro: etiquetas, un código de operación y un campo de operando. El compilador del lenguaje ensamblador normalmente también contempla la posibilidad de tener comentarios en el programa de entrada que estén separados de los comandos por un delimitador dado.

El campo de etiqueta contiene un identificador que representa la etiqueta o está vacío. Cada identificador de etiqueta solo puede aparecer una vez en un programa en lenguaje ensamblador. La etiqueta se considera descrita cuando a se reunió mediocre en el programa (se requiere una descripción preliminar de las etiquetas). Se puede usar una etiqueta para transferir el control al comando que envía. No es raro que una etiqueta esté separada del resto del comando por un carácter de separación (más comúnmente dos puntos ":").

El código de operación siempre es un mnemotécnico estrictamente definido de uno de los posibles comandos del procesador o también un comando estrictamente definido (de mi compilador). El código de operación está escrito en caracteres alfabéticos del idioma nativo. La mayoría de las veces, su longitud es 3-4, con menos frecuencia - 5 o 6 caracteres.

El campo de operando está vacío o es una lista de uno, dos, con menos frecuencia tres operandos. El número de operandos está estrictamente definido y depende del código de operación: cada operación en lenguaje ensamblador proporciona un número fijo de sus operandos. En consecuencia, cada una de estas opciones corresponde a instrucciones sin dirección, unidireccional, de dos direcciones o de tres direcciones (prácticamente no se usa una mayor cantidad de operandos; en las computadoras modernas, incluso las instrucciones de tres direcciones son raras). como óperas; dov pueden ser identificadores o constantes.

Una característica del lenguaje ensamblador es que una serie de identificadores en n se asignan específicamente para designar registros del procesador. Dichos ficadores, por un lado, no requieren una descripción preliminar, pero, con D1, no pueden ser utilizados por el usuario para otros fines. El conjunto de estos identificadores está predefinido para cada lenguaje ensamblador.

A veces, el lenguaje ensamblador permite el uso como operandos de ciertas combinaciones limitadas de designaciones de registro, identificadores y constantes, que se combinan mediante algunos signos de operador. Tales combinaciones se usan con mayor frecuencia para designar tipos de direccionamiento, por ejemplo, en instrucciones de máquina del sistema informático de destino.

Por ejemplo, la siguiente secuencia de comandos

Este es un ejemplo de una secuencia de comandos en lenguaje ensamblador n; Procesadores de la familia Intel 80x86. Aquí hay un comando para describir un conjunto (datos (db), una etiqueta (bucles), códigos de operación (mov, dec y jnz). Los operandos son el identificador del conjunto de datos (datos), las designaciones de registro de proceso

(bx y cx), etiqueta (bucles) y constante (4). El operando compuesto de datos asigna el direccionamiento indirecto de datos al registro base bx en el desplazamiento 4.

Esta sintaxis del lenguaje se puede describir fácilmente usando una gramática regular. Por lo tanto, construir un reconocedor para lenguaje ensamblador no es difícil. Por la misma razón, en los compiladores del lenguaje ensamblador, el análisis léxico y sintáctico, por regla general, se combinan en un reconocedor.

La semántica del lenguaje ensamblador está total y completamente determinada por el sistema informático de destino al que está orientado este lenguaje. La semántica del lenguaje ensamblador determina qué instrucción de máquina corresponde a cada instrucción del lenguaje ensamblador, así como qué operandos y cuántos se permiten para un código de operación en particular.

Por lo tanto, el análisis semántico en un compilador de lenguaje ensamblador es tan simple como el análisis sintáctico. Su tarea principal es verificar la validez de los operandos para cada código de operación y también verificar que se describan todos los identificadores y etiquetas encontrados en el programa de entrada y que los identificadores que los denotan no coincidan con los identificadores predefinidos utilizados para designar los códigos de operación y los registros del procesador.

Los esquemas semánticos y de análisis sintáctico en un compilador de lenguaje ensamblador pueden implementarse sobre la base de una máquina de estado convencional. Es esta característica la que determinó el hecho de que históricamente los compiladores de lenguaje ensamblador fueran los primeros compiladores creados para computadoras. También hay una serie de otras características que son específicas de los lenguajes ensambladores y facilitan la creación de compiladores para ellos.

Primero, en los compiladores del lenguaje ensamblador, no se realiza ninguna identificación adicional de variables: todas las variables del lenguaje conservan los nombres que les asignó el usuario. El desarrollador del programa fuente es responsable de la unicidad de los nombres en el programa fuente; la semántica del lenguaje no impone ningún requisito adicional en este proceso. En segundo lugar, en los compiladores de lenguaje ensamblador, la asignación de memoria se simplifica enormemente. El compilador del lenguaje ensamblador funciona solo con memoria estática. Si se usa memoria dinámica, para trabajar con ella, debe usar la biblioteca adecuada o las funciones del sistema operativo, y el desarrollador del programa fuente es responsable de su asignación. El desarrollador del programa fuente también es responsable de pasar parámetros y organizar la visualización de procedimientos y funciones en la memoria. También debe ocuparse de separar los datos del código del programa: un compilador de lenguaje ensamblador, a diferencia de los compiladores de lenguajes de alto nivel, no realiza automáticamente dicha separación. Y en tercer lugar, en la etapa de generación de código en el compilador a partir del lenguaje ensamblador, no se realiza la optimización, ya que el desarrollador del programa fuente es responsable de la organización de los cálculos, la secuencia de las instrucciones de la máquina y la asignación de los registros del procesador.

Excepto por estas características, un compilador de lenguaje ensamblador es un compilador normal, pero mucho más simplificado en comparación con cualquier compilador de lenguaje de alto nivel. Los compiladores del lenguaje ensamblador se implementan con mayor frecuencia en un esquema de dos pasos. En la primera pasada, el compilador analiza el programa fuente, lo convierte en códigos de máquina y simultáneamente completa la tabla de identificadores. Pero en el primer paso en las instrucciones de la máquina, las direcciones de esos operandos que se encuentran en la RAM permanecen sin llenar. En el segundo paso, el compilador completa estas direcciones y simultáneamente detecta identificadores no declarados. Esto se debe a que el operando se puede declarar en el programa después de que se haya utilizado por primera vez. Entonces, su dirección aún no se conoce en el momento de construir la instrucción de la máquina y, por lo tanto, se requiere una segunda pasada. Un ejemplo típico de dicho operando es una etiqueta que salta hacia adelante en la secuencia de instrucciones.

Definiciones de macros y comandos de macros

Desarrollar programas en lenguaje ensamblador es un proceso bastante laborioso que a menudo requiere una simple repetición de las mismas operaciones repetidas. Un ejemplo sería una secuencia de instrucciones que se ejecutan cada vez para configurar una pantalla de pila de memoria cuando se ingresa un procedimiento o función.

Para facilitar el trabajo del desarrollador se crearon las llamadas macros.

macro es una sustitución de texto, durante la cual cada identificador de cierto tipo se reemplaza con una cadena de caracteres de algún almacén de datos. El proceso de ejecución de una macro se denomina generación de macros y la cadena de caracteres resultante de la ejecución de una macro se denomina expansión de macros.

El proceso de ejecución de macros consiste en revisar secuencialmente el texto del programa fuente, detectando en él ciertos identificadores y reemplazándolos por las cadenas de caracteres correspondientes. Además, es la sustitución textual de una cadena de caracteres (identificador) por otra cadena de caracteres (string) que se realiza. Tal sustitución se llama macro sustitución.

Las definiciones de macro se utilizan para especificar qué identificadores deben reemplazarse con qué líneas. Las definiciones de macros están presentes directamente en el texto del programa fuente. Se distinguen por palabras clave especiales o delimitadores que no se pueden encontrar en ningún otro lugar del texto del programa. Durante el procesamiento, todas las definiciones de macro se excluyen por completo del texto del programa de entrada y la información contenida en ellas se almacena para su procesamiento cuando se ejecutan comandos de macro.

Una macro puede contener parámetros. Luego, cada macro correspondiente debe, cuando se le llame, contener una cadena de caracteres en lugar de cada parámetro. Esta cadena se sustituye al ejecutar la macro en cada lugar donde aparece el parámetro correspondiente en la definición de la macro. El parámetro de la macro puede ser otra macro, en cuyo caso se llamará recursivamente cada vez que se necesite realizar una sustitución de parámetro. En principio, las macros pueden formar una secuencia

Llamadas recursivas, similares a la secuencia de procedimientos recursivos y llamadas a funciones, pero en lugar de calcular y pasar parámetros, solo realizan sustituciones textuales 1 .

Los comandos de macros y las definiciones de macros son procesados ​​por un módulo especial llamado procesador de macros o generador de macros. El macrogenerador recibe como entrada el texto del programa fuente que contiene definiciones de macro y comandos de macro, y en su salida aparece el texto de la extensión de macro del programa fuente, que no contiene definiciones de macro ni comandos de macro. Ambos textos son solo textos de programa, no se realiza ningún otro procesamiento. Es la macro expansión del texto fuente que ingresa a la entrada del compilador.

La sintaxis de los comandos de macro y las definiciones de macro no está estrictamente definida. Puede diferir según la implementación del compilador del lenguaje ensamblador. Pero el principio mismo de realizar sustituciones de macros en el texto del programa no cambia y no depende de su sintaxis.

El macrogenerador generalmente no existe como un módulo de programa separado, sino que es parte del compilador del lenguaje ensamblador. Una extensión de macro de un programa fuente generalmente no está disponible para su desarrollador. Además, las sustituciones de macros se pueden realizar secuencialmente al analizar el código fuente en la primera pasada del compilador, junto con el análisis de todo el texto del programa, y ​​luego la expansión de macros del programa fuente como un todo puede no existir como tal.

Por ejemplo, el siguiente texto define la macro push_0 en lenguaje ensamblador de un procesador tipo Intel 8086:

Hog ah, ah ■ empujar hacha endm

La semántica de esta macro es escribir el número "0" en la pila a través del registro del procesador ax. Luego, en todas partes del texto del programa, donde se encontrará la macro

Será reemplazado como resultado de la sustitución de macros con una secuencia de comandos:

Hog ah, ah ■ empujar hacha

Esta es la definición de macro más simple. Es posible crear definiciones de macros más complejas con parámetros. Una de estas definiciones de macro se describe a continuación:

La profundidad de tal recurrencia suele ser muy limitada. La secuencia de llamadas recursivas a macros suele estar sujeta a restricciones significativamente más estrictas que la secuencia de llamadas recursivas a procedimientos y funciones, que, con una pantalla de memoria apilada, está limitada solo por el tamaño de la pila de transferencia de parámetros. add_abx macro xl,x2

hacha de empuje
fin

Luego, el comando de macro también debe especificarse en el texto del programa con el número correspondiente de parámetros. En este ejemplo, la macro

Add_abx4,8 será reemplazado por una secuencia de comandos como resultado de la sustitución de macros:

Agregar hacha,4 agregar bx.4 agregar ex,8 empujar hacha

En muchos compiladores del lenguaje ensamblador, son posibles construcciones aún más complejas, que pueden contener variables y etiquetas locales. Un ejemplo de una construcción de este tipo es una definición de macro:

Loop_ax macro xl,x2,yl

Hog bx.bx loopax: agregar bx.yl

Aquí la etiqueta 1 oopax es local, definida solo dentro de esta macro. En este caso, ya no se puede realizar una simple sustitución de texto del comando de macro en el texto del programa, ya que si este comando de macro se ejecuta dos veces, esto conducirá a la aparición de dos etiquetas idénticas en el texto del programa. En este caso, el macrogenerador debe usar métodos de sustitución de texto más complejos, similares a los que usan los compiladores cuando identifican elementos léxicos del programa de entrada, para dar a todas las posibles variables locales y etiquetas de macro nombres únicos dentro de todo el programa. Las definiciones de macros y los comandos de macros han encontrado uso no solo en lenguajes ensambladores, sino también en muchos lenguajes de alto nivel. Allí son procesados ​​por un módulo especial llamado preprocesador de lenguaje (por ejemplo, el preprocesador de lenguaje C es ampliamente conocido). El principio de procesamiento sigue siendo el mismo que para los programas en lenguaje ensamblador: el preprocesador realiza sustituciones de texto directamente en las líneas del propio programa fuente. En los lenguajes de alto nivel, las definiciones de macros deben estar separadas del texto del programa fuente en sí, para que el preprocesador no pueda confundirlas con la sintaxis del lenguaje de entrada. Para hacer esto, se utilizan caracteres especiales y comandos (comandos de preprocesador), que nunca pueden aparecer en el texto del programa fuente, o se encuentran definiciones de macro

Dentro de una parte insignificante del programa fuente, son parte de los comentarios (tal implementación existe, por ejemplo, en el compilador Pascal creado por Borland). Las macros, por otro lado, pueden aparecer en cualquier parte del código fuente del programa y, sintácticamente, su llamada puede no diferir de la llamada de las funciones en el idioma fuente.

Debe recordarse que, a pesar de la similitud de la sintaxis de llamada, los comandos de macro son fundamentalmente diferentes de los procedimientos y funciones, ya que no generan el código resultante, sino que son una sustitución de texto realizada directamente en el texto del programa fuente. El resultado de llamar a una función y una macro puede ser muy diferente debido a esto.

Considere un ejemplo en lenguaje C. Si se describe una función

Int fKint a) (retorna a + a:) y una macro similar

#define f2(a) ((a) + (a)) entonces el resultado de llamarlos no siempre será el mismo.

De hecho, las llamadas j=fl(i) y j=f2(i) (donde i y j son algunas variables enteras) conducirán al mismo resultado. Pero las llamadas j=fl(++i) y j=f2(++i) darán diferentes significados variable j. El caso es que como f2 es una definición de macro, en el segundo caso se realizará una sustitución de texto, lo que dará lugar a una secuencia de operadores j=((++i) + (++i)). Se puede ver que en esta secuencia la operación ++i se ejecutará dos veces, en contraste con la llamada a la función fl(++i), donde se ejecuta solo una vez.

Dado que el texto de un programa escrito en algún lenguaje de programación no es comprensible para una computadora, se requiere traducirlo a lenguaje de máquina. La traducción de un programa de un lenguaje de programación a un lenguaje de código de máquina se llama transmisión(traducción - traducción), y se realiza mediante programas especiales - traductores.

Hay dos clases de traductores: intérpretes y compiladores.

Interprete se denomina traductor que realiza la traducción operador por operador (comando por comando) y la posterior ejecución del operador traducido del programa fuente. Dos desventajas del método de interpretación:

1. El programa de interpretación debe estar en la memoria de la computadora durante todo el proceso de ejecución del programa original, es decir, ocupar cierta cantidad de memoria;

2. El proceso de traducción de una misma sentencia se repite tantas veces como deba ejecutarse este comando en el programa.

Compilador es un programa que convierte (traduce) el programa fuente en un programa (módulo) en lenguaje máquina. Después de eso, el programa se escribe en la memoria de la computadora y solo entonces se ejecuta.

Durante la compilación, los procesos de traducción y ejecución están separados en el tiempo: primero, el programa fuente se traduce completamente al lenguaje de máquina (después de lo cual no se necesita la presencia de un traductor en la RAM), y luego el programa traducido se puede ejecutar repetidamente.

Cualquier traductor resuelve las siguientes tareas principales:

1. Analiza el programa emitido y determina si contiene errores de sintaxis;

2. Genera un programa de salida en lenguaje de comando de computadora;

3. Asigna memoria para el programa de salida, es decir cada variable, constante, arreglos y demás objetos tiene su propia área de memoria.

De este modo, Compilador(Inglés) compilador- compilador, colector) lee todo el programa enteramente, lo traduce y crea una versión completa del programa en lenguaje máquina, que luego se ejecuta.

Interprete(Inglés) Interprete- intérprete, intérprete) traduce y ejecuta el programa linea por linea.

Una vez que se compila el programa, ya no se necesita ni el programa fuente ni el compilador. Al mismo tiempo, el programa que está procesando el intérprete debe volver a transferir en lenguaje máquina cada vez que se ejecuta el programa.

Cada lenguaje específico se enfoca ya sea en la compilación o en la interpretación, dependiendo del propósito para el cual fue creado. Por ejemplo, Pascal suele utilizarse para resolver problemas bastante complejos en los que la velocidad de los programas es importante. Por lo tanto, este lenguaje generalmente se implementa usando compilador. Por otro lado, BÁSICO fue creado como un lenguaje para programadores novatos, para quienes la ejecución de programas línea por línea tiene ventajas innegables. A veces para un idioma hay y compilador, e intérprete. En este caso, puede usar un intérprete para desarrollar y probar el programa y luego compilar el programa depurado para acelerar su ejecución.

Dado que el texto escrito en un lenguaje de programación es incomprensible para una computadora, se requiere traducirlo a código de máquina. Tal traducción de un programa de un lenguaje de programación a un lenguaje de código de máquina se denomina traducción y se realiza mediante programas especiales: traductores.

Un traductor es un programa de utilidad que convierte un programa fuente proporcionado en un lenguaje de programación de entrada en un programa de trabajo presentado en un lenguaje objeto.

Actualmente, los compiladores se dividen en tres grupos principales: ensambladores, compiladores e intérpretes.

Assembler es una utilidad del sistema que convierte construcciones simbólicas en instrucciones de lenguaje de máquina. Una característica específica de los ensambladores es que literalmente traducen una instrucción simbólica en una instrucción de máquina. Por lo tanto, el lenguaje ensamblador (también llamado autocodificación) está diseñado para facilitar la percepción del conjunto de instrucciones de la computadora y acelerar la programación en este conjunto de instrucciones. Es mucho más fácil para un programador recordar la designación mnemotécnica de las instrucciones de máquina que su código binario.

Al mismo tiempo, el lenguaje ensamblador, además de los análogos de las instrucciones de la máquina, contiene muchas directivas adicionales que facilitan, en particular, la gestión de los recursos informáticos, la escritura de fragmentos repetitivos y la creación de programas de varios módulos. Por lo tanto, la expresividad del lenguaje es mucho más rica que un simple lenguaje de codificación simbólica, lo que aumenta en gran medida la eficiencia de la programación.

Un compilador es un programa de utilidad que traduce a lenguaje de máquina un programa escrito en un lenguaje de programación fuente. Al igual que un ensamblador, un compilador convierte un programa de un idioma a otro (la mayoría de las veces, al idioma de una computadora en particular). Sin embargo, los comandos del lenguaje de origen difieren significativamente en organización y potencia de los comandos del lenguaje de máquina. Hay idiomas en los que una instrucción del idioma de origen se traduce en 7-10 instrucciones de máquina. Sin embargo, también existen lenguajes en los que cada instrucción puede corresponder a 100 o más instrucciones máquina (por ejemplo, Prolog). Además, en los idiomas de origen se suele utilizar una tipificación estricta de los datos, que se lleva a cabo a través de su descripción preliminar. Es posible que la programación no dependa de la codificación de un algoritmo, sino de una reflexión cuidadosa sobre las estructuras o clases de datos. El proceso de traducción de dichos lenguajes generalmente se denomina compilación, y los lenguajes de origen generalmente se denominan lenguajes de programación de alto nivel (o lenguajes de alto nivel). La abstracción del lenguaje de programación del sistema de comandos de la computadora llevó a la creación independiente de una amplia variedad de lenguajes enfocados a resolver problemas específicos. Aparecieron los lenguajes para cálculos científicos, cálculos económicos, acceso a bases de datos y otros.

Un intérprete es un programa o dispositivo que realiza la traducción y ejecución operador por operador del programa fuente. A diferencia de un compilador, un intérprete no produce un programa en lenguaje de máquina como salida. Habiendo reconocido el comando del idioma fuente, lo ejecuta inmediatamente. Tanto los compiladores como los intérpretes usan los mismos métodos para analizar el código fuente de un programa. Pero el intérprete le permite comenzar a procesar datos después de escribir incluso un comando. Esto hace que el proceso de desarrollo y depuración de programas sea más flexible. Además, la ausencia de código de máquina de salida permite no "ensuciar" los dispositivos externos con archivos adicionales, y el intérprete en sí se puede adaptar con bastante facilidad a cualquier arquitectura de máquina, habiéndolo desarrollado solo una vez en un lenguaje de programación ampliamente utilizado. Por lo tanto, los lenguajes interpretados como Java Script, VB Script se han generalizado. La desventaja de los intérpretes es la baja velocidad de ejecución del programa. Por lo general, los programas interpretados se ejecutan de 50 a 100 veces más lento que los programas escritos en código de máquina.

Un emulador es un programa o herramienta de software y hardware que brinda la capacidad de ejecutar un programa en una computadora determinada sin reprogramar, utilizando códigos o métodos para realizar operaciones que son diferentes de esta computadora. Un emulador es similar a un intérprete en que ejecuta directamente un programa escrito en algún idioma. Sin embargo, la mayoría de las veces es lenguaje de máquina o código intermedio. Ambos representan instrucciones en código binario que pueden ejecutarse inmediatamente después de reconocer el código de operación. A diferencia de los programas de texto, no es necesario reconocer la estructura del programa para seleccionar operandos.

Los emuladores se utilizan con bastante frecuencia para una variedad de propósitos. Por ejemplo, al desarrollar nuevos sistemas informáticos, primero se crea un emulador que ejecuta programas que se están desarrollando para computadoras que aún no existen. Esto permite evaluar el conjunto de instrucciones y desarrollar el software básico antes de crear el hardware correspondiente.

Muy a menudo, el emulador se usa para ejecutar programas antiguos en computadoras nuevas. Por lo general, las computadoras más nuevas son más rápidas y tienen mejores periféricos. Esto le permite emular programas antiguos de manera más eficiente que ejecutarlos en computadoras antiguas.

Transcodificador: un programa o dispositivo de software que traduce programas escritos en el lenguaje de máquina de una computadora a programas en el lenguaje de máquina de otra computadora. Si el emulador es un análogo menos inteligente del intérprete, entonces el transcodificador actúa en la misma capacidad en relación con el compilador. De manera similar, el código de máquina fuente (y generalmente binario) o una representación intermedia se convierte en otro código similar en una instrucción y sin ningún análisis general de la estructura del programa fuente. Los transcodificadores son útiles cuando se transfieren programas de una arquitectura de computadora a otra. También se pueden utilizar para reconstruir el texto de un programa de lenguaje de alto nivel a partir del código binario disponible.

Un macroprocesador es un programa que reemplaza una secuencia de caracteres por otra. Es una especie de compilador. Genera texto de salida mediante el procesamiento de inserciones especiales ubicadas en el texto de origen. Estas inserciones están hechas de una manera especial y pertenecen a las construcciones del lenguaje, llamadas macrolenguaje. Los macroprocesadores se utilizan a menudo como complementos de los lenguajes de programación, lo que aumenta la funcionalidad de los sistemas de programación. Casi cualquier ensamblador contiene un macroprocesador, lo que aumenta la eficiencia del desarrollo de programas de máquina. Dichos sistemas de programación se denominan comúnmente ensambladores de macros.

Los macroprocesadores también se utilizan con lenguajes de alto nivel. Aumentan la funcionalidad de lenguajes como PL/1, C, C++. Los macroprocesadores se utilizan especialmente en C y C++, lo que facilita la escritura de programas. Los macroprocesadores aumentan la eficiencia de la programación sin cambiar la sintaxis y la semántica del lenguaje.

Sintaxis: un conjunto de reglas de un determinado idioma que determina la formación de sus elementos. En otras palabras, es un conjunto de reglas para la formación de secuencias de caracteres semánticamente significativos en un idioma dado. La sintaxis se especifica mediante reglas que describen los conceptos de un lenguaje determinado. Ejemplos de conceptos son: variable, expresión, operador, procedimiento. La secuencia de conceptos y su uso permitido en las reglas determina las estructuras sintácticamente correctas que forman los programas. Es la jerarquía de los objetos, no cómo interactúan entre sí, lo que se define a través de la sintaxis. Por ejemplo, un operador puede aparecer solo en un procedimiento, mientras que una expresión en un operador, una variable puede consistir en un nombre e índices opcionales, etc. La sintaxis no está relacionada con fenómenos en el programa como "saltar a una etiqueta inexistente" o "una variable con el nombre dado no está definida". Esto es lo que hace la semántica.

Semántica: reglas y condiciones que determinan la relación entre los elementos del lenguaje y sus significados semánticos, así como la interpretación del significado significativo de las construcciones sintácticas del lenguaje. Los objetos del lenguaje de programación no solo se colocan en el texto de acuerdo con una cierta jerarquía, sino que también se interconectan adicionalmente a través de otros conceptos que forman varias asociaciones. Por ejemplo, una variable para la cual la sintaxis define una ubicación válida solo en declaraciones y algunas sentencias, tiene un tipo específico, puede usarse con un conjunto limitado de operaciones, tiene una dirección, un tamaño y debe declararse antes de usarse en un programa.

Un analizador es un componente del compilador que comprueba las declaraciones de origen para el cumplimiento de las reglas de sintaxis y la semántica de un lenguaje de programación determinado. A pesar del nombre, el analizador comprueba tanto la sintaxis como la semántica. Consta de varios bloques, cada uno de los cuales resuelve sus propios problemas. Se considerará con más detalle al describir la estructura del traductor.

Cualquier traductor realiza las siguientes tareas principales:

Analiza el programa que se está traduciendo, en particular, determina si contiene errores de sintaxis;

Genera un programa de salida (a menudo llamado programa objeto) en el lenguaje de instrucciones de máquina;

Asigna memoria para el programa objeto.