Introducción, Conceptos, Características y Componentes de los Compiladores. - Blog de Tecnologia, Ingenieria en Sistemas

Novedades

jueves, 2 de febrero de 2012

Introducción, Conceptos, Características y Componentes de los Compiladores.

CONTENIDO DE ÉSTA GUÍA


  1. Realizar un recordatorio sobre la clasificación de lenguajes de programación.
  2. Conocer los conceptos generales de compiladores, intérpretes, traductores.
  3. Comprender las fases de un compilador.
  4. Distinguir los diferentes tipos de traductores.
  5. Realizar ejemplos sencillos de compilación en lenguaje C++ y Java.

CLASIFICACION DE LOS LENGUAJES DE PROGRAMACION


La programación de computadoras se realiza en los llamados lenguajes de programación, éstos posibilitan la comunicación entre el programador y la computadora, a través de un conjunto de instrucciones u órdenes especificadas por el lenguaje.

Un lenguaje de programación puede definirse como: “Notación formal para describir algoritmos o funciones que serán ejecutados por una computadora”, o bien, un lenguaje para comunicar instrucciones al computador.

Diferentes puntos de vista para clasificar los lenguajes de programación:

1. Su grado de independencia con la máquina.
2. La forma de sus instrucciones y la forma de procesar el código fuente.
3. Por generaciones.
 

Los lenguajes de programación según su grado de independencia de la máquina:

a) Lenguaje máquina (representación binaria o hexadecimal.).
b) Lenguaje ensamblador o de bajo nivel (versión simbólica de un lenguaje máquina).
c) Lenguaje de medio nivel (lenguaje C).
d) Lenguaje de alto nivel (FORTRAN, COBOL, Pascal).

Aunque todos los lenguajes de programación tienen un conjunto de instrucciones que permiten realizar dichas operaciones, existe una marcada diferencia en los símbolos, caracteres y sintaxis de los lenguajes de máquina, lenguajes ensambladores y lenguajes de alto nivel.

a) Lenguaje de máquina.

El lenguaje de máquina de una computadora consta de cadenas de números binarios (ceros y unos) y es el único que "entienden" directamente los procesadores. Todas las instrucciones preparadas en cualquier lenguaje de máquina tienen por lo menos dos partes. La primera es el comando u operación, que dice a la computadora cuál es la función que va a realizar. Todas las computadoras tienen un código de operación para cada una de sus funciones. La segunda parte de la instrucción es el operando, que indica a la computadora donde hallar o almacenar los datos y otras instrucciones que se van a manipular; el número de operando de una instrucción varía en las distintas computadoras.

b) Lenguaje ensamblador

La comunicación en lenguaje de máquina es particular de cada procesador que se usa, y programar en este lenguaje es muy difícil y tedioso, por lo que se empezó a buscar mejores medios de comunicación con ésta.

A fin de facilitar la labor de los programadores en el pasado, se desarrollaron códigos mnemotécnicos para las operaciones y direcciones simbólicas. Uno de los primeros pasos para mejorar el proceso de preparación de programas fue sustituir los códigos de operación numéricos del lenguaje de máquina por símbolos alfabéticos, que conforman un lenguaje mnemotécnico.

Todas las computadoras actuales tienen códigos mnemotécnicos aunque, naturalmente, los símbolos que se usan varían en las diferentes marcas y modelos. La computadora sigue utilizando el lenguaje de máquina para procesar los datos, pero los programas ensambladores traducen antes los símbolos de código de operación especificados a sus equivalentes en lenguaje de máquina.

Los lenguajes ensambladores tienen ventajas sobre los lenguajes de máquina. Ahorran tiempo y requieren menos atención a detalles. Se incurren en menos errores y los que se cometen son más fáciles de localizar. Además, los programas en lenguaje ensamblador son más fáciles de modificar que los programas en lenguaje de máquina. Pero existen limitaciones. La codificación en lenguaje ensamblador es todavía un proceso lento. Además, una desventaja importante de estos lenguajes es que tienen una orientación a la máquina. Es decir, están diseñados para la marca y modelo específico de procesador que se utiliza.

En el principio de la computación este era el lenguaje que tenía que "hablar" el ser humano con la computadora y consistía en insertar en un tablero miles de conexiones y alambres y encender y apagar interruptores. Aunque en la actualidad ya no se emplea, es importante reconocer que ya no es necesario que nos comuniquemos en este lenguaje de "unos" y "ceros", pero es el que internamente una computadora reconoce o "habla".

c) Lenguajes de alto nivel

Los primeros programas ensambladores producían sólo una instrucción en lenguaje de máquina por cada instrucción del programa fuente. Para agilizar la codificación, se desarrollaron programas ensambladores que podían producir una cantidad variable de instrucciones en lenguaje de máquina por cada instrucción del programa fuente. Dicho de otra manera, una sola macroinstrucción podía producir varias líneas de código en lenguaje de máquina.

A diferencia de los programas de ensamble, los programas en lenguaje de alto nivel se pueden utilizar con diferentes marcas de computadoras sin tener que hacer modificaciones considerables. Esto permite reducir sustancialmente el costo de la reprogramación cuando se adquiere equipo nuevo.

Los lenguajes de programación según su forma de instrucciones

  • Lenguajes imperativos o procedimentales: lenguajes orientados a instrucciones (Pascal, C. C++, Ada, FORTRAN).
  • Lenguajes declarativos, funcionales o aplicativos: sus construcciones se basan en llamadas a funciones matemáticas, utilizados a menudo en Inteligencia Artificial ( LISP, ML. APL, Haskell ). o Lógicos: manejan relaciones entre objetos. Las relaciones se especifican con reglas y hechos. La ejecución de programas lógicos, consiste en la demostración de hechos sobre las relaciones por medio de preguntas. (PROLOG es el más utilizado de este tipo).
  • Lenguajes orientados a objetos: si soportan tipos de datos abstractos y clases. 
  • (C++, Smalltalk, Java, Prolog++). Los lenguajes de programación por generaciones son aquellos nombrados como: 1ª., 2ª., 3ª. 4ª. generación y aquellos identificados como generación visual y generación internet.

Los lenguajes de programación según su forma de procesar el código fuente
  • Traductores (translators).
  • Compiladores (compilers).
  • Ensambladores (assemblers).
  • Interpretes (interpreters).
  • Editores (editors).

Traductor: Un traductor es un programa que procesa un texto fuente destino.

Compiladores:
Un traductor que transforma textos fuente de lenguajes de alto nivel a lenguajes de bajo nivel se le denomina compilador.

  • El tiempo que se necesita para traducir un lenguaje de alto nivel a lenguaje objeto se denomina tiempo de compilación.
  • El tiempo que tarda en ejecutarse un programa objeto se denomina tiempo de ejecución.

Intérpretes: Los intérpretes son programas que simplemente ejecutan las instrucciones que encuentran en el texto fuente. En muchos casos coexisten en memoria el programa fuente y el programa intérprete.

Un ejemplo de lenguaje interpretado es BASIC. La ejecución de un programa compilado es mucho más rápida que la de un programa interpretado. Sin embargo los intérpretes son más interactivos y facilitan la puesta a punto de programas.

ARQUITECTURA DE LA COMPUTADORA


En la disciplina de los procesadores de lenguajes, los compiladores son los más utilizados por los programadores para el desarrollo de aplicaciones. En el caso particular del desarrollo de compiladores, hay que tener bien definida la arquitectura de la computadora.


¿Cómo se pueden ejecutar las aplicaciones desarrolladas para otras arquitecturas de computadoras en la nueva arquitectura?

El problema planteado anteriormente no sólo es aplicable a la construcción de nuevas arquitecturas, sino también cuando es necesaria la compatibilidad de aplicaciones entre diferentes sistemas operativos y arquitecturas de computadoras.


Algunas de las técnicas utilizadas para realizar migraciones de aplicaciones entre distintas arquitecturas y sistemas operativos se muestran en la siguiente figura:


Un intérprete software o emulador de software es un programa en código binario que lee una a una las instrucciones binarias de la arquitectura antigua de las computadoras, que se encuentran en un fichero ejecutable, y las interpreta. Los intérpretes no son muy rápidos, pero pueden construir y adaptar a distintas arquitecturas sin excesivos costos de desarrollo.

Algunos ejemplos de intérpretes software binario son:


  • Emuladores de DOS para distintos sistemas operativos (SoftPC de Insignia Solutions).
  • Intérpretes de la máquinaabstracta JVM (Java Virtual Machine) para distintas plataformas y ue permiten ejecutar los códigos binarios denominados bytecode (ficheros con la extensión .class).

Un emulador hardware trabaja de forma similar a un intérprete de software, pero está implementado en HW de forma que decodifica las instrucciones de la arquitectura antigua y las traduce a la nueva arquitectura. Un ejemplo de emulador de hardware son los microprocesadores Java, que emulan la máquina abstracta JVM (Java Virtual Machine).


Un traductor entre códigos binarios o ensambladores son conjuntos de instrucciones de la nueva arquitectura que reproducen el comportamiento de un programa en la arquitectura antigua (la información de la máquina antigua se almacena en registros de la nueva máquina). Un ejemplo de estos traductores son los desarrollados por DEC (Digital Equipment Corporation) para traducir instrucciones de la arquitectura VAX y MIPS a la nueva arquitctura ALPHA.


Un compilador nativo es aquél que toma un programa fuente antiguo y lo vuelve a compilar (recompilar) con compiladores desarrollados para la nueva arquitectura. Son los más rápidos y óptimos con respecto a las demás técnicas, pues produce la mejor calidad del código objeto.


GRAMATICAS DE LOS LENGUAJES DE PROGRAMACION


  • Reglas para escribir las sentencias del lenguaje.
  • Conjunto de símbolos que definen la estructura de procesamiento de un lenguaje.

Ejemplo de estructura gramatical de un lenguaje:


FASES DE LOS COMPILADORES


Analizando en detalle el proceso de compilación, se divide en dos grandes fases, una de Análisis y la otra de Síntesis.



Fase de Análisis:

En el llamado análisis lexicográfico o léxico, el compilador revisa y controla que las "palabras" estén bien escritas y pertenezcan a algún tipo de token (cadena) definido dentro del lenguaje, como por ejemplo que sea algún tipo de palabra reservada, o si es el nombre de una variable que este escrita de acuerdo a las pautas de definición del lenguaje. En esta etapa se crea la tabla de símbolos, la cual contiene las variables y el tipo de dato al que pertenece, las constantes literales, el nombre de funciones y los argumentos que reciben etc.


En el análisis sintáctico como su nombre lo indica se encarga de revisar que los tokens estén ubicados y agrupados de acuerdo a la definición del lenguaje. Dicho de otra manera, que los tokens pertenezcan a frases gramaticales validas, que el compilador utiliza para sintetizar la salida. Por lo general las frases gramaticales son representadas por estructuras jerárquicas, por medio de árboles de análisis sintáctico. En esta etapa se completa la tabla de símbolos con la dimensión de los identificadores y los atributos necesarios etc.


El análisis semántico se encarga de revisar que cada agrupación o conjunto de token tenga sentido, y no sea un absurdo. En esta etapa se reúne la información sobre los tipos para la fase posterior, en esta etapa se utiliza la estructura jerárquica de la etapa anterior y así poder determinar los operadores, y operandos de expresiones y preposiciones.


Fase de Síntesis:


Etapa de generación de código intermedio, aunque algunos compiladores no la tienen, es bueno saber de su existencia, en esta etapa se lleva el código del programa fuente a un código interno para poder trabajar más fácilmente sobre él. Esta representación interna debe tener dos propiedades, primero debe ser fácil de representar y segundo debe ser fácil de traducir al código objeto.


En la etapa de optimización de código, se busca obtener el código más corto y rápido posible, utilizando distintos algoritmos de optimización.


Etapa de generación de código, se lleva el código intermedio final a código maquina o código objeto, que por lo general consiste en un código maquina re localizable o código ensamblador. Se selecciona las posiciones de memoria para los datos (variables) y se traduce cada una de las instrucciones intermedias a una secuencia de instrucciones de maquina puro.


La tabla de símbolos no es una etapa del proceso de compilación, sino que una tarea, una
función que debe realizar el proceso de compilación. En ella se almacenan los identificadores que aparecen en el código fuente puro, como así también los atributos de los mismos, su tipo, su ámbito y en el caso de los procedimientos el número de argumentos el tipo del mismo etc. En otras palabras una tabla de símbolos es una estructura de datos, que contiene un registro por cada identificador, y sus atributos. La tabla de símbolo es accedida tanto para escritura como parar lectura por todas las etapas. Detector de errores o manejador de errores, al igual que la tabla de símbolos no es una etapa del proceso de compilación, sino que es una función, muy importante, pues al ocurrir un error esta función debe tratar de alguna forma el error para así seguir con el proceso de compilación (la mayoría de errores son detectados en las etapas de análisis léxico, análisis sintáctico, análisis semántico).

EJEMPLO EXPLICATIVO SOBRE LAS ETAPAS DE UN COMPILADOR.


¿Qué es lo que hacen los componentes de un compilador en sus Fases cuando tiene que analizar la siguiente expresión?


suma = var1 + var2 + 10 ;


Análisis Léxico


El analizador léxico lee los caracteres del programa fuente, y verifica que correspondan a una secuencia lógica (identificador, palabra reservada etc.). Esta secuencia de caracteres recibe el nombre componente léxico o lexema. En este caso el analizador léxico verifica si el identificador id1 (nombre interno para "suma") encontrado se halla en la tabla de símbolos, si no está produce un error porque todavía no fue declarado, si la preposición hubiese sido la declaración del identificador "suma" en lenguajes C, C++ (int suma;) el analizador léxico agregaría un identificador en la tabla de símbolos, y así sucesivamente con todos los componentes léxicos que aparezcan.


id1= id2+ id3 + 10


Análisis Sintáctico


El analizador sintáctico impone una estructura jerárquica a la cadena de componentes léxicos, generada por el analizador léxico, que es representada en forma de un árbol sintáctico.

Análisis Semántico
 
El analizador semántico verificara en este caso que cada operador tenga los operandos permitidos.






Análisis Semántico
El analizador semántico verificara en este caso que cada operador tenga los operandos permitidos.



Generador de código intermedio
 
Esta etapa se lleva la preposición a una representación intermedia como un programa para una maquina abstracta.


temp1= tipo_ent(10)
temp2= id3 * temp1
temp3= id2 + tem2
id1= temp3





 

Optimización de código

El código intermedio obtenido es representado de una forma más óptima y eficiente.


temp1= id3 * 10.0
id1= id2 + temp1


Generador de código
 
Finalmente lleva el código intermedio a un código objeto que en este caso es un código re localizable o código ensamblador (también llamado código no enlazado).


MOVF id3, R2
MULT #10.0, R2
MOVF id2, R1
ADDF R2, R1
MOVF R1, id1


Este el código objeto obtenido que es enviado al módulo de ensamblado.

GLOSARIO


  1. PROGRAMACIÓN DE SISTEMAS: Conjunto de reglas para crear soluciones a problemas computables. Conjunto de herramientas que nos permiten crear software de base que son de utilidad para interactuar con la máquina.
  2. SOFTWARE DE BASE: Compilador, Querys, Sistema Operativo, Cargador.
  3. AUTÓMATA: Son las cadenas posibles que aceptan un lenguaje.
  4. EXPRESIONES REGULARES: Conjunto de símbolos que aceptan una palabra reservada.
  5. GRAMÁTICA: Reglas para escribir las sentencias del lenguaje.
  6. LENGUAJE DE PROGRAMACIÓN: Es la notación formal para la descripción de algoritmos, basada en un conjunto de instrucciones en alto nivel, que finalmente pasarán a bajo nivel para interactuar con el hardware y generar herramientas de trabajo.



No hay comentarios:

Publicar un comentario

Gracias por su comentario