En informática , un compilador es un programa de computadora que traduce código de computadora escrito en un lenguaje de programación (el lenguaje de origen ) a otro lenguaje (el lenguaje de destino ). El nombre "compilador" se utiliza principalmente para programas que traducen el código fuente de un lenguaje de programación de alto nivel a un lenguaje de programación de bajo nivel (por ejemplo, lenguaje ensamblador , código objeto o código de máquina ) para crear un programa ejecutable . [1] [2] : p1 [3]
Hay muchos tipos diferentes de compiladores que producen resultados en diferentes formas útiles. Un compilador cruzado produce código para una CPU o sistema operativo diferente al que se ejecuta el compilador cruzado. Un compilador de arranque es a menudo un compilador temporal, que se utiliza para compilar un compilador más permanente o mejor optimizado para un lenguaje.
El software relacionado incluye descompiladores , programas que traducen de lenguajes de bajo nivel a lenguajes de nivel superior; programas que traducen entre lenguajes de alto nivel, generalmente llamados compiladores o transpiladores de fuente a fuente ; reescritores de idiomas , normalmente programas que traducen la forma de expresiones sin cambiar de idioma; y compiladores-compiladores , compiladores que producen compiladores (o partes de ellos), a menudo de forma genérica y reutilizable para poder producir muchos compiladores diferentes.
Es probable que un compilador realice algunas o todas las siguientes operaciones, a menudo llamadas fases: preprocesamiento , análisis léxico , análisis sintáctico , análisis semántico ( traducción dirigida por sintaxis ), conversión de programas de entrada a una representación intermedia , optimización de código y generación de código específico de la máquina. . Los compiladores generalmente implementan estas fases como componentes modulares, promoviendo un diseño eficiente y la corrección de las transformaciones de la entrada de origen a la salida de destino. Los fallos de programa causados por un comportamiento incorrecto del compilador pueden ser muy difíciles de localizar y solucionar; por lo tanto, los implementadores del compilador invierten un esfuerzo significativo para garantizar la corrección del compilador . [4]
Los compiladores no son los únicos procesadores de lenguaje utilizados para transformar programas fuente. Un intérprete es un software informático que transforma y luego ejecuta las operaciones indicadas. [2] : p2 El proceso de traducción influye en el diseño de los lenguajes informáticos, lo que conduce a una preferencia por la compilación o la interpretación. En teoría, un lenguaje de programación puede tener tanto un compilador como un intérprete. En la práctica, los lenguajes de programación tienden a estar asociados con uno solo (un compilador o un intérprete).
Los conceptos informáticos teóricos desarrollados por científicos, matemáticos e ingenieros formaron la base del desarrollo de la informática digital moderna durante la Segunda Guerra Mundial. Los lenguajes binarios primitivos evolucionaron porque los dispositivos digitales sólo entienden unos y ceros y los patrones de circuitos en la arquitectura de la máquina subyacente. A finales de la década de 1940, se crearon lenguajes ensambladores para ofrecer una abstracción más viable de las arquitecturas informáticas. [5] La capacidad de memoria limitada de las primeras computadoras generó importantes desafíos técnicos cuando se diseñaron los primeros compiladores. Por lo tanto, el proceso de compilación tuvo que dividirse en varios programas pequeños. Los programas de front-end producen los productos de análisis utilizados por los programas de back-end para generar código de destino. A medida que la tecnología informática proporcionó más recursos, los diseños de los compiladores pudieron alinearse mejor con el proceso de compilación.
Generalmente es más productivo para un programador utilizar un lenguaje de alto nivel, por lo que el desarrollo de lenguajes de alto nivel surgió naturalmente de las capacidades que ofrecen las computadoras digitales. Los lenguajes de alto nivel son lenguajes formales que están estrictamente definidos por su sintaxis y semántica que forman la arquitectura del lenguaje de alto nivel. Los elementos de estos lenguajes formales incluyen:
Las oraciones en un idioma pueden definirse mediante un conjunto de reglas llamadas gramática. [6]
La forma Backus-Naur (BNF) describe la sintaxis de las "oraciones" de un idioma. Fue desarrollado por John Backus y utilizado para la sintaxis de Algol 60 . [7] Las ideas se derivan de los conceptos gramaticales libres de contexto del lingüista Noam Chomsky . [8] "BNF y sus extensiones se han convertido en herramientas estándar para describir la sintaxis de notaciones de programación. En muchos casos, partes de los compiladores se generan automáticamente a partir de una descripción de BNF". [9]
Entre 1942 y 1945, Konrad Zuse diseñó el primer lenguaje de programación (algorítmico) para computadoras llamado Plankalkül ("Plan Cálculo"). Zuse también imaginó un Planfertigungsgerät ("dispositivo de ensamblaje de planos") para traducir automáticamente la formulación matemática de un programa en una película perforada legible por máquina . [10] Si bien no se produjo ninguna implementación real hasta la década de 1970, presentó conceptos que se vieron más tarde en APL diseñado por Ken Iverson a finales de la década de 1950. [11] APL es un lenguaje para cálculos matemáticos.
Entre 1949 y 1951, Heinz Rutishauser propuso Superplan , un lenguaje de alto nivel y traductor automático. [12] Sus ideas fueron posteriormente refinadas por Friedrich L. Bauer y Klaus Samelson . [13]
El diseño de lenguajes de alto nivel durante los años de formación de la informática digital proporcionó herramientas de programación útiles para una variedad de aplicaciones:
La tecnología de compilación surgió de la necesidad de una transformación estrictamente definida del programa fuente de alto nivel en un programa destino de bajo nivel para la computadora digital. El compilador podría verse como una interfaz para abordar el análisis del código fuente y un back-end para sintetizar el análisis en el código de destino. La optimización entre el front-end y el back-end podría producir un código de destino más eficiente. [17]
Algunos de los primeros hitos en el desarrollo de la tecnología de compilación:
Los primeros sistemas operativos y software se escribieron en lenguaje ensamblador. En los años 1960 y principios de los 1970, el uso de lenguajes de alto nivel para la programación de sistemas todavía era controvertido debido a las limitaciones de recursos. Sin embargo, varios esfuerzos de investigación e industria iniciaron el cambio hacia lenguajes de programación de sistemas de alto nivel, por ejemplo, BCPL , BLISS , B y C.
BCPL (Lenguaje de programación combinado básico) diseñado en 1966 por Martin Richards en la Universidad de Cambridge se desarrolló originalmente como una herramienta de escritura compiladora. [29] Se han implementado varios compiladores; el libro de Richards proporciona información sobre el lenguaje y su compilador. [30] BCPL no solo fue un lenguaje de programación de sistemas influyente que todavía se utiliza en la investigación [31] sino que también proporcionó una base para el diseño de lenguajes B y C.
BLISS (lenguaje básico para la implementación del software del sistema) fue desarrollado para una computadora PDP-10 de Digital Equipment Corporation (DEC) por el equipo de investigación de la Universidad Carnegie Mellon (CMU) de WA Wulf. El equipo de CMU desarrolló el compilador BLISS-11 un año después, en 1970.
Multics (Servicio de Computación e Información Multiplexada), un proyecto de sistema operativo de tiempo compartido, involucró al MIT , Bell Labs , General Electric (más tarde Honeywell ) y fue liderado por Fernando Corbató del MIT. [32] Multics fue escrito en el lenguaje PL/I desarrollado por IBM e IBM User Group. [33] El objetivo de IBM era satisfacer los requisitos empresariales, científicos y de programación de sistemas. Había otros lenguajes que se podrían haber considerado pero PL/I ofrecía la solución más completa aunque no se hubiera implementado. [34] Durante los primeros años del proyecto Multics, Doug McIlory y Bob Morris de Bell Labs pudieron compilar un subconjunto del lenguaje en lenguaje ensamblador con el compilador Early PL/I (EPL). [35] EPL apoyó el proyecto hasta que se pudo desarrollar un compilador de arranque para el PL/I completo. [36]
Bell Labs abandonó el proyecto Multics en 1969 y desarrolló un lenguaje de programación de sistemas B basado en conceptos BCPL, escrito por Dennis Ritchie y Ken Thompson . Ritchie creó un compilador de arranque para B y escribió el sistema operativo Unics (Uniplexed Information and Computing Service) para un PDP-7 en B. Unics finalmente se deletreó Unix.
Bell Labs inició el desarrollo y expansión de C basado en B y BCPL. Bell Labs había transportado el compilador BCPL a Multics y BCPL era el lenguaje preferido en Bell Labs. [37] Inicialmente, se utilizó un programa de interfaz para el compilador B de Bell Labs mientras se desarrollaba un compilador C. En 1971, un nuevo PDP-11 proporcionó el recurso para definir extensiones de B y reescribir el compilador. En 1973, el diseño del lenguaje C estaba esencialmente completo y el kernel Unix para un PDP-11 se reescribió en C. Steve Johnson comenzó el desarrollo del compilador C portátil (PCC) para admitir la reorientación de compiladores C a nuevas máquinas. [38] [39]
La programación orientada a objetos (POO) ofrecía algunas posibilidades interesantes para el desarrollo y mantenimiento de aplicaciones. Los conceptos de programación orientada a objetos se remontan a más atrás, pero eran parte de LISP y la ciencia del lenguaje Simula . [40] Bell Labs se interesó en la programación orientada a objetos con el desarrollo de C++ . [41] C++ se utilizó por primera vez en 1980 para la programación de sistemas. El diseño inicial aprovechó las capacidades de programación de sistemas en lenguaje C con conceptos de Simula. En 1983 se agregaron funciones orientadas a objetos. [42] El programa Cfront implementó una interfaz C++ para el compilador del lenguaje C84. En los años siguientes se desarrollaron varios compiladores de C++ a medida que crecía la popularidad de C++.
En muchos dominios de aplicaciones, la idea de utilizar un lenguaje de nivel superior se popularizó rápidamente. Debido a la creciente funcionalidad soportada por los nuevos lenguajes de programación y la creciente complejidad de las arquitecturas informáticas, los compiladores se volvieron más complejos.
DARPA (Agencia de Proyectos de Investigación Avanzada de Defensa) patrocinó un proyecto de compilación con el equipo de investigación CMU de Wulf en 1970. El diseño del Compilador de Calidad de Producción-Compilador PQCC produciría un Compilador de Calidad de Producción (PQC) a partir de definiciones formales del lenguaje de origen y el de destino. [43] PQCC intentó extender el término compilador-compilador más allá del significado tradicional como generador de analizadores (por ejemplo, Yacc ) sin mucho éxito. PQCC podría denominarse más propiamente generador de compiladores.
La investigación del PQCC sobre el proceso de generación de código buscó construir un sistema de escritura de compiladores verdaderamente automático. El esfuerzo descubrió y diseñó la estructura de fases del PQC. El compilador BLISS-11 proporcionó la estructura inicial. [44] Las fases incluyeron análisis (front-end), traducción intermedia a la máquina virtual (middle end) y traducción al destino (back-end). TCOL fue desarrollado para la investigación de PQCC para manejar construcciones específicas del lenguaje en la representación intermedia. [45] Las variaciones de TCOL admitían varios idiomas. El proyecto PQCC investigó técnicas de construcción automatizada de compiladores. Los conceptos de diseño resultaron útiles para optimizar compiladores y compiladores para el lenguaje de programación Ada (desde 1995, orientado a objetos) .
El documento de Ada STONEMAN [a] formalizó el entorno de soporte de programas (APSE) junto con el kernel (KAPSE) y el mínimo (MAPSE). Un intérprete de Ada NYU/ED apoyó los esfuerzos de desarrollo y estandarización con el Instituto Nacional Estadounidense de Estándares (ANSI) y la Organización Internacional de Estándares (ISO). El desarrollo inicial del compilador Ada por parte de los Servicios Militares de EE. UU. incluyó los compiladores en un entorno de diseño integrado completo siguiendo las líneas del documento STONEMAN . El Ejército y la Armada trabajaron en el proyecto Ada Language System (ALS) destinado a la arquitectura DEC/VAX, mientras que la Fuerza Aérea comenzó en el Ada Integrated Environment (AIE) destinado a la serie IBM 370. Si bien los proyectos no proporcionaron los resultados deseados, sí contribuyeron al esfuerzo general de desarrollo de Ada. [46]
Otros esfuerzos para compilar Ada se pusieron en marcha en Gran Bretaña, en la Universidad de York, y en Alemania, en la Universidad de Karlsruhe. En Estados Unidos, Verdix (posteriormente adquirida por Rational) entregó el sistema de desarrollo Verdix Ada (VADS) al ejército. VADS proporcionó un conjunto de herramientas de desarrollo que incluye un compilador. Unix/VADS podría alojarse en una variedad de plataformas Unix como DEC Ultrix y Sun 3/60 Solaris destinado a Motorola 68020 en una evaluación CECOM del Ejército. [47] Pronto hubo muchos compiladores de Ada disponibles que pasaron las pruebas de validación de Ada. El proyecto GNU de la Free Software Foundation desarrolló la Colección de compiladores GNU (GCC), que proporciona una capacidad central para admitir múltiples idiomas y objetivos. La versión GNAT de Ada es uno de los compiladores de Ada más utilizados. GNAT es gratuito pero también hay soporte comercial, por ejemplo, AdaCore, se fundó en 1994 para proporcionar soluciones de software comercial para Ada. GNAT Pro incluye GNAT basado en GNU GCC con un conjunto de herramientas para proporcionar un entorno de desarrollo integrado .
Los lenguajes de alto nivel continuaron impulsando la investigación y el desarrollo de compiladores. Las áreas de enfoque incluyeron optimización y generación automática de código. Las tendencias en los lenguajes de programación y los entornos de desarrollo influyeron en la tecnología de compilación. Se incluyeron más compiladores en distribuciones de lenguajes (PERL, Java Development Kit) y como componentes de un IDE (VADS, Eclipse, Ada Pro). La interrelación e interdependencia de las tecnologías crecieron. La llegada de los servicios web promovió el crecimiento de los lenguajes web y los lenguajes de programación. Los scripts se remontan a los primeros días de las interfaces de línea de comandos (CLI), donde el usuario podía ingresar comandos para que los ejecutara el sistema. Conceptos de Shell de usuario desarrollados con lenguajes para escribir programas de Shell. Los primeros diseños de Windows ofrecían una capacidad de programación por lotes sencilla. La transformación convencional de estos lenguajes utilizó un intérprete. Aunque no se utilizan mucho, se han escrito compiladores Bash y Batch. Más recientemente, los lenguajes interpretados sofisticados se convirtieron en parte del kit de herramientas de los desarrolladores. Los lenguajes de programación modernos incluyen PHP, Python, Ruby y Lua. (Lua se usa ampliamente en el desarrollo de juegos). Todos estos tienen soporte de intérprete y compilador. [48]
"Cuando el campo de la compilación comenzó a finales de los años 50, su enfoque se limitaba a la traducción de programas en lenguajes de alto nivel a código de máquina... El campo de la compilación está cada vez más entrelazado con otras disciplinas, incluyendo la arquitectura informática, los lenguajes de programación, los métodos formales, ingeniería de software y seguridad informática." [49] El artículo "Investigación sobre compiladores: los próximos 50 años" señaló la importancia de los lenguajes orientados a objetos y Java. Entre los objetivos futuros de la investigación se mencionaron la seguridad y la computación paralela .
Un compilador implementa una transformación formal de un programa fuente de alto nivel a un programa destino de bajo nivel. El diseño del compilador puede definir una solución de un extremo a otro o abordar un subconjunto definido que interactúe con otras herramientas de compilación, por ejemplo, preprocesadores, ensambladores y enlazadores. Los requisitos de diseño incluyen interfaces rigurosamente definidas tanto internamente entre los componentes del compilador como externamente entre los conjuntos de herramientas de soporte.
En los primeros días, el enfoque adoptado para el diseño del compilador se veía directamente afectado por la complejidad del lenguaje informático a procesar, la experiencia de las personas que lo diseñaban y los recursos disponibles. Las limitaciones de recursos llevaron a la necesidad de revisar el código fuente más de una vez.
Un compilador para un lenguaje relativamente simple escrito por una sola persona podría ser una pieza de software única y monolítica. Sin embargo, a medida que el idioma fuente crece en complejidad, el diseño puede dividirse en varias fases interdependientes. Las fases separadas proporcionan mejoras de diseño que centran el desarrollo en las funciones del proceso de compilación.
La clasificación de los compiladores por número de pasadas tiene su origen en las limitaciones de recursos de hardware de las computadoras. La compilación implica realizar mucho trabajo y las primeras computadoras no tenían suficiente memoria para contener un programa que hiciera todo este trabajo. Como resultado, los compiladores se dividieron en programas más pequeños, cada uno de los cuales pasó por la fuente (o alguna representación de ella) realizando algunos de los análisis y traducciones requeridos.
La capacidad de compilar en una sola pasada se ha visto clásicamente como un beneficio porque simplifica el trabajo de escribir un compilador y los compiladores de una sola pasada generalmente realizan compilaciones más rápido que los compiladores de múltiples pasadas . Así, impulsados en parte por las limitaciones de recursos de los primeros sistemas, muchos de los primeros lenguajes se diseñaron específicamente para que pudieran compilarse en una sola pasada (por ejemplo, Pascal ).
En algunos casos, el diseño de una característica del lenguaje puede requerir que un compilador realice más de una pasada sobre el código fuente. Por ejemplo, considere una declaración que aparece en la línea 20 de la fuente y que afecta la traducción de una declaración que aparece en la línea 10. En este caso, el primer paso debe recopilar información sobre las declaraciones que aparecen después de las declaraciones que afectan, y que se produce la traducción real. durante un pase posterior.
La desventaja de compilar en una sola pasada es que no es posible realizar muchas de las optimizaciones sofisticadas necesarias para generar código de alta calidad. Puede resultar difícil contar exactamente cuántas pasadas realiza un compilador de optimización. Por ejemplo, diferentes fases de optimización pueden analizar una expresión muchas veces pero solo analizar otra expresión una vez.
Dividir un compilador en pequeños programas es una técnica utilizada por investigadores interesados en producir compiladores demostrablemente correctos. Demostrar la corrección de un conjunto de programas pequeños a menudo requiere menos esfuerzo que demostrar la corrección de un programa más grande, único y equivalente.
Independientemente del número exacto de fases en el diseño del compilador, las fases se pueden asignar a una de tres etapas. Las etapas incluyen una parte delantera, una parte intermedia y una parte trasera.
Este enfoque front/middle/back-end hace posible combinar front-ends para diferentes lenguajes con back-ends para diferentes CPU mientras se comparten las optimizaciones del middle-end. [50] Ejemplos prácticos de este enfoque son GNU Compiler Collection , Clang ( compilador C/C++ basado en LLVM ), [51] y Amsterdam Compiler Kit , que tienen múltiples front-end, optimizaciones compartidas y múltiples back-end.
if(net>0.0)total+=net*(1.0+tax/100.0);
", el escáner compone una secuencia de tokens y categoriza cada uno de ellos, por ejemplo, como identificador , palabra reservada , número literal u operador . El analizador transforma la última secuencia en un árbol de sintaxis , que luego es tratado por las fases restantes del compilador. El escáner y el analizador manejan las partes normales y correctamente libres de contexto de la gramática de C , respectivamente.La interfaz analiza el código fuente para construir una representación interna del programa, llamada representación intermedia (IR). También gestiona la tabla de símbolos , una estructura de datos que asigna cada símbolo en el código fuente a información asociada, como ubicación, tipo y alcance.
Si bien la interfaz puede ser una única función o programa monolítico, como en un analizador sin escáner , tradicionalmente se implementaba y analizaba en varias fases, que pueden ejecutarse de forma secuencial o simultánea. Este método se ve favorecido debido a su modularidad y separación de preocupaciones . Por lo general, la interfaz se divide en tres fases: análisis léxico (también conocido como lexing o escaneo), análisis de sintaxis (también conocido como escaneo o análisis) y análisis semántico . Lexing y parsing comprenden el análisis sintáctico (sintaxis de palabras y sintaxis de frases, respectivamente) y, en casos simples, estos módulos (el lexer y el analizador) se pueden generar automáticamente a partir de una gramática del idioma, aunque en casos más complejos requieren modificación manual. . La gramática léxica y la gramática de frases suelen ser gramáticas libres de contexto , lo que simplifica significativamente el análisis, y la sensibilidad al contexto se maneja en la fase de análisis semántico. La fase de análisis semántico es generalmente más compleja y está escrita a mano, pero puede automatizarse parcial o totalmente mediante gramáticas de atributos . Estas fases en sí mismas se pueden dividir aún más: lexing como escaneo y evaluación, y parsing como construcción de un árbol de sintaxis concreto (CST, árbol de análisis) y luego transformándolo en un árbol de sintaxis abstracta (AST, árbol de sintaxis). En algunos casos se utilizan fases adicionales, en particular reconstrucción de líneas y preprocesamiento, pero son raras.
Las principales fases del front end incluyen las siguientes:
El extremo medio, también conocido como optimizador, realiza optimizaciones en la representación intermedia para mejorar el rendimiento y la calidad del código de máquina producido. [55] El extremo medio contiene aquellas optimizaciones que son independientes de la arquitectura de CPU a la que se dirige.
Las principales fases del extremo medio incluyen las siguientes:
El análisis del compilador es un requisito previo para cualquier optimización del compilador y trabajan estrechamente en conjunto. Por ejemplo, el análisis de dependencia es crucial para la transformación de bucles .
El alcance del análisis y las optimizaciones del compilador varían mucho; su alcance puede variar desde operar dentro de un bloque básico hasta procedimientos completos o incluso el programa completo. Existe un equilibrio entre la granularidad de las optimizaciones y el costo de compilación. Por ejemplo, las optimizaciones de mirilla se realizan rápidamente durante la compilación, pero solo afectan a un pequeño fragmento local del código y se pueden realizar independientemente del contexto en el que aparece el fragmento de código. Por el contrario, la optimización entre procedimientos requiere más tiempo de compilación y espacio de memoria, pero permite optimizaciones que sólo son posibles considerando el comportamiento de múltiples funciones simultáneamente.
El análisis y las optimizaciones interprocedurales son comunes en los compiladores comerciales modernos de HP , IBM , SGI , Intel , Microsoft y Sun Microsystems . El software libre GCC fue criticado durante mucho tiempo por carecer de potentes optimizaciones interprocedimientos, pero en este sentido está cambiando. Otro compilador de código abierto con infraestructura completa de análisis y optimización es Open64 , que utilizan muchas organizaciones con fines comerciales y de investigación.
Debido al tiempo y espacio adicional necesarios para el análisis y las optimizaciones del compilador, algunos compiladores los omiten de forma predeterminada. Los usuarios deben usar opciones de compilación para indicarle explícitamente al compilador qué optimizaciones deben habilitarse.
El back-end es responsable de las optimizaciones específicas de la arquitectura de la CPU y de la generación de código [55] .
Las principales fases del back end incluyen las siguientes:
La corrección del compilador es la rama de la ingeniería de software que se ocupa de intentar demostrar que un compilador se comporta de acuerdo con la especificación de su lenguaje . [57] Las técnicas incluyen el desarrollo del compilador utilizando métodos formales y el uso de pruebas rigurosas (a menudo llamadas validación del compilador) en un compilador existente.
Los lenguajes de programación de nivel superior suelen aparecer con un tipo de traducción en mente: ya sea diseñada como lenguaje compilado o como lenguaje interpretado . Sin embargo, en la práctica rara vez hay algo en un lenguaje que requiera que sea compilado o interpretado exclusivamente, aunque es posible diseñar lenguajes que dependan de la reinterpretación en tiempo de ejecución. La categorización generalmente refleja las implementaciones más populares o extendidas de un lenguaje; por ejemplo, a BASIC a veces se le llama lenguaje interpretado y C compilado, a pesar de la existencia de compiladores BASIC e intérpretes de C.
La interpretación no reemplaza completamente la compilación. Sólo lo oculta al usuario y lo hace gradual. Aunque un intérprete puede interpretarse en sí mismo, se necesita un conjunto de instrucciones de máquina ejecutadas directamente en algún lugar al final de la pila de ejecución (consulte lenguaje de máquina ).
Además, para la optimización, los compiladores pueden contener funciones de intérprete, y los intérpretes pueden incluir técnicas de compilación anticipadas. Por ejemplo, cuando se puede ejecutar una expresión durante la compilación y los resultados se pueden insertar en el programa de salida, se evita tener que volver a calcularla cada vez que se ejecuta el programa, lo que puede acelerar enormemente el programa final. Las tendencias modernas hacia la compilación justo a tiempo y la interpretación de códigos de bytes a veces desdibujan aún más las categorizaciones tradicionales de compiladores e intérpretes.
Algunas especificaciones de lenguaje establecen que las implementaciones deben incluir una función de compilación; por ejemplo, Common Lisp . Sin embargo, no hay nada inherente en la definición de Common Lisp que impida su interpretación. Otros lenguajes tienen características que son muy fáciles de implementar en un intérprete, pero hacen que escribir un compilador sea mucho más difícil; por ejemplo, APL , SNOBOL4 y muchos lenguajes de secuencias de comandos permiten que los programas construyan código fuente arbitrario en tiempo de ejecución con operaciones de cadena regulares y luego ejecuten ese código pasándolo a una función de evaluación especial . Para implementar estas características en un lenguaje compilado, los programas generalmente deben venir con una biblioteca de tiempo de ejecución que incluya una versión del propio compilador.
Una clasificación de los compiladores es según la plataforma en la que se ejecuta el código generado. Esto se conoce como plataforma de destino.
Un compilador nativo o alojado es aquel cuya salida está destinada a ejecutarse directamente en el mismo tipo de computadora y sistema operativo en el que se ejecuta el compilador. La salida de un compilador cruzado está diseñada para ejecutarse en una plataforma diferente. Los compiladores cruzados se utilizan a menudo cuando se desarrolla software para sistemas integrados que no están destinados a admitir un entorno de desarrollo de software.
La salida de un compilador que produce código para una máquina virtual (VM) puede ejecutarse o no en la misma plataforma que el compilador que lo produjo. Por este motivo, estos compiladores no suelen clasificarse como compiladores nativos o cruzados.
El lenguaje de nivel inferior que es el objetivo de un compilador puede ser en sí mismo un lenguaje de programación de alto nivel . C, visto por algunos como una especie de lenguaje ensamblador portátil, es frecuentemente el lenguaje de destino de dichos compiladores. Por ejemplo, Cfront , el compilador original de C++ , usaba C como lenguaje de destino. El código C generado por un compilador de este tipo generalmente no está destinado a ser legible ni mantenido por humanos, por lo que se ignoran el estilo de sangría y la creación de un bonito código intermedio en C. Algunas de las características de C que lo convierten en un buen lenguaje de destino incluyen la #line
directiva, que puede generar el compilador para admitir la depuración del código fuente original, y el amplio soporte de plataforma disponible con los compiladores de C.
Si bien un tipo de compilador común genera código de máquina, existen muchos otros tipos:
DOALL
). Otros términos para un compilador de fuente a fuente son transcompilador o transpilador. [58]Los ensambladores, que traducen el lenguaje ensamblador legible por humanos a instrucciones de código de máquina ejecutadas por hardware, no se consideran compiladores. [65] [b] (El programa inverso que traduce el código de máquina al lenguaje ensamblador se llama desensamblador ).
Un compilador es un programa informático que traduce un programa escrito en un lenguaje de alto nivel (HLL), como C, a un programa equivalente en lenguaje ensamblador [2].
{{cite book}}
: |website=
ignorado ( ayuda )El primer texto sobre la construcción de compiladores.