Está en la página 1de 57

Compiladores e

Interpretes
Clase Introductoria
Algunas ideas previas
 El alumno debe estar familiarizado con matemáticas discretas, estructuras
de datos básicas, arquitectura del computador, lenguaje ensamblador,
lenguajes formales, sistemas de control.

 Aunque esto no es parte de la materia, se intentará suplir deficiencias en


estas áreas siempre que sea posible y este al alcance del ritmo de las clases.

 Código de ética mínimo a cumplir a la hora de entregar trabajos:


 No tomar ideas de otros y presentarlas como propias (esto implica no copiar, no
"traducir" artículos de la red y ponerlos en los trabajos como ideas originales, no
copiar tareas, etc.)
 No inducir a sus compañeros a violar el punto 1 (no dejarse copiar, no vender tareas).

 En caso de violación de la ética mínima que debería tener un futuro


profesional, la persona y su equipo recibirán cero como nota en dicho
proyecto y además se volverán a revisar todos sus proyectos anteriores en
búsqueda de indicios de otras violaciones éticas y se tomarán las medidas
que sean correspondientes en caso de conseguirlas.
Orígenes de los lenguajes de
programación
Eslabones importantes
• Gödel y Turing: Cambiaron radicalmente las ciencias matemáticas,
con sus descubrimientos.
Hechos importantes:

• Teorema de Gödel, “Toda formulación axiomática consistente de la


teoría de números contiene preposiciones indecidibles”, es decir
cualquier teoría matemática será siempre incompleta, porque existirán
afirmaciones que no se podrán demostrar ni negar.

• El teorema anterior, motivo a Alan Turing a estudiar que funciones


podían ser calculadas y cuáles no, surgiendo de esta forma el Teorema
de Turing, el cual publico en su articulo sobre los números calculables en
el cual demostraba que existen problemas irresolubles y además
introdujo el concepto de la máquina de Turing, que es una entidad
matemática abstracta que formalizo por primera vez el concepto de
algoritmo.
Eslabones importantes
• Autómatas: Claude Elwood Shannon creo las bases para la
aplicación de la lógica matemática a los circuitos combinatorios y
secuenciales, lo cual a la larga se convirtió en la teoría de maquinas
secuenciales y autómatas finitos.
Hechos importantes:

• Un autómata es un sistema capaz de transmitir información. Es decir


acepta señales de su entorno, como resultado cambia de estado y
transmite otras señales a su medio.

• Esta definición es muy amplia y abarca cualquier máquina, por lo que se


hace demasiado general para su estudio teórico, por lo que era
necesario introducir limitaciones en su definición.

• La salida de un autómata puede ser muy resumida como por ejemplo


una señal binaria.
Eslabones importantes
• Lenguajes y Gramáticas: En el campo de la lingüística Noam
Chomsky propuso la teoría de las gramáticas transformacionales.
Hechos importantes:

• Esta teoría estableció las bases para la lingüística matemática que sirvió
como herramienta para el estudio y formalización de los lenguajes de
computadora.

• Dividió el estudio de los lenguajes en análisis de la estructura de las


frases (gramática) y su significado (semántica), pudiendo a su vez la
gramática analizar las formas que toman las palabras (morfología), su
combinación para formar frases correctas (sintaxis).

• Aunque la distinción entre la sintaxis y la semántica desde el punto de


vista teórico es un poco artificial, tiene enorme trascendencia práctica,
especialmente en el diseño de compiladores.
Eslabones importantes
• Máquinas Abstractas y Lenguajes Formales: Ambas disciplinas
poseen una relación muy estrecha, ya que los mismos fenómenos
aparecen independientemente en ambas y es posible establecer
correspondencia entre ellas (isomorfismo).
Hechos importantes:

• Noam Chomsky, clasificó las gramáticas y lenguajes formales de acuerdo


con una jerarquía de cuatro grados.

• Paralelo a esto existe una jerarquía de maquinas abstractas equivalentes


para cada nivel de la clasificación de Chomsky.

• Debido a estos descubrimientos, surgió una nueva rama de


estudios: La informática teórica.
La informática teórica
• Informática teórica: Rama de rama de las matemáticas que hace
uso de lo anteriormente expuesto, pero con un objetivo muy
concreto: conseguir mejores sistemas de computación y clasificar los
problemas de acuerdo con su dificultad al ser computados por
diferentes modelos.

Hechos importantes:

• Nace a partir de problemas que se planteaban en la lógica matemática


hacia principios de siglo.

• Tradicionalmente, Se distinguen dos grandes campos: la teoría de


lenguajes formales y las teorías de la calculabilidad y de la complejidad.

• Sus intenciones iniciales eran formalizar el lenguaje natural (hablado).

• Son la base fundamental para la creación de compiladores e interpretes.


Una breve historia
(al menos se intento esto…)
• En un inicio las computadoras eran grandes circuitos
electrónicos, que poseían codificada en su arquitectura la
funcionalidad que ejecutarían, o en otras palabras la
disposición de sus circuitos, buses de comunicación e
interruptores, determinaba su funcionamiento.

• Pero el concepto de programa aún no existía, siendo estas


computadoras, si podían nombrarse así, imposibles de
programar mediante el uso de un lenguaje, y su
“programación” consistía en modificar su hardware de alguna
forma para que cumpliesen con la tarea requerida, lo cual era
propenso a muchos errores.
Una breve historia (continuación)

• John von Neumann fue un matemático húngaro-


estadounidense, de ascendencia judía, que realizó
contribuciones importantes en física cuántica,
análisis funcional, teoría de conjuntos, informática,
economía, análisis numérico, hidrodinámica (de
explosiones), estadística y muchos otros campos de
la matemática. Recibió su doctorado en
matemáticas de la Universidad de Budapest a los 23
años.

• Fue pionero de la computadora digital moderna


publicando un artículo acerca del almacenamiento Fuente: wikipedia
de programas.

• El concepto de programa almacenado permitió la


lectura de un programa dentro de la memoria de la
computadora, y después la ejecución de las
instrucciones del mismo sin tener que volverlas a
escribir.
Una breve historia (continuación)

• Debido a al trabajo de John von Neumann ahora se tiene la


idea de un computador que ejecute un conjunto mínimo de
instrucciones básicas para realizar todas sus operaciones en
lugar de cambiar su arquitectura de hardware para llevar a
cabo la misma labor.

• Debido a esto ahora surge el concepto de lenguaje de


maquina, el cual expresa a nivel de este conjunto de
instrucciones básicas expresadas en binario, el programa que
resolvía la tarea requerida por el operador de la computadora.
Una breve historia (continuación)

• Ahora los programas podían ser planteados con este lenguaje de


maquina en la memoria de la maquina, como por ejemplo el
siguiente programa realizado para la arquitectura LC-3

• En este programa una línea de 16 bits representa un valor simple o


una instrucción de maquina, comenzando su ejecución en la primera
línea, al tomarla de la memoria decodificarla (interpretarla) y
ejecutarla, entonces el control pasa a la siguiente línea y el proceso
es repetido hasta que no existan instrucciones o se consiga una
instrucción de fin de ejecución (halt).
Una breve historia (continuación)

• Debido a los programas almacenados se hizo necesario escribir estas


secuencias de códigos o programas que permitirían que los
computadores realizaran los cálculos deseados.

• En un principio se uso el lenguaje de Maquina como se mostro


anteriormente, que son códigos numéricos que representan las
operaciones reales de la maquina que iban a efectuarse, por ejemplo:

C7 06 0000 0002

Representa la instrucción para mover el número 2 a la ubicación 0000


(hexadecimal) en un procesador Intel 80x86.

• La escritura de estos códigos es tediosa y consume mucho tiempo, por


lo que se debió buscar una solución a este problema.
Una breve historia (continuación)

• Debido a esto surgió el lenguaje ensamblador, en el cual las instrucciones


y las localidades de memoria son formas simbólicas dadas, por ejemplo la
instrucción anterior seria:

MOV X,2

Suponiendo claro que la localidad de memoria 0000 es X.

• El trabajo es traducir los códigos simbólicos y las localidades de memoria


del lenguaje ensamblador a los códigos numéricos correspondientes del
lenguaje de máquina.
Una breve historia (continuación)

• Esto mejoro la velocidad con la que se podían escribir los programas, sin
embargo su principal defecto es que no es fácil de escribir y es difícil de
leer y comprender, además de ser dependiente de la plataforma para la
cual se creo.
Una breve historia (continuación)

• Por lo que el siguiente paso fue escribir las operaciones de un programa


de una manera concisa que se pareciera a la notación matemática o el
lenguaje natural.

• Esto implica que sea independiente de cualquier maquina en particular y


se pudiese traducir mediante un programa en código ejecutable, por
ejemplo la instrucción anterior se podría plasmar como:

X=2

• En un principio se temía que esto no fuese posible y que si lo fuera, el


código objeto sería tan poco eficiente que resultaría inútil.

• El lenguaje FORTRAN, creado por John Backus, demostró que estos


temores eran infundados.
Una breve historia (continuación)

• Lo que comenzó a hacer evidente la importancia de los


lenguajes de alto nivel, por ejemplo a continuación se
compara un programa en ensamblador contra uno equivalente
en C++, el objetivo del mismo es llevar a cabo una suma de los
elementos presentes en un vector.
Una breve historia (continuación)

• No obstante el trabajo realizado hasta el momento no era bien


comprendido.

• Noam Chomsky comenzó con su estudios del lenguaje natural por ese
mismo momento lo cual junto con las maquinas abstractas permitieron
sentar las bases de los compiladores modernos.

• A medida que el problema del léxico, sintaxis y semántica se comprendía


mejor gracias a los avances en estas áreas, se empezaron a realizar
programas que automatizaran esta parte del desarrollo de un compilador,
llamándose a estos programas compiladores de compilador.

• Ejemplo para el análisis léxico: Jflex o lex.

• Ejemplo para el análisis sintáctico: Yacc o CUP.


Una breve historia (continuación)

RESUMIENDO:
• La arquitectura de Von Neumann marca el punto de partida de la
informática moderna.

• Si se escribían los programas utilizando claves mnemotécnicas


(abreviaciones de los códigos de operación, más fáciles de recordar que
los números), los programas eran más sencillos de escribir: Lenguaje
Ensamblador.

• En esta década de los cincuenta, la investigación se orientó hacia la


creación de un lenguaje en el que las acciones fueran expresadas de la
manera más natural posible por el programador y que fuese lo más
independiente posible de la máquina, por lo tanto aparecen: Los
lenguajes de alto nivel.

• En caso de que el lenguaje fuente que hay que traducir sea de alto nivel y
el lenguaje obtenido, de bajo nivel, se utiliza el término compilador.
Popularidad de los lenguajes
de programación (fuente:www.complang.tuwien.ac.at/anton/comp.lang-statistics/)
Paradigmas computacionales
• Los lenguajes de programación se iniciaron imitando y abstrayendo
las instrucciones existentes de forma predeterminada en las
computadoras.

• No es entonces ninguna sorpresa que todos se asemejen al modelo


propuesto por von Neumman ya que todos los computadores
modernos se basan en el mismo: las variables representan
localidades de memoria y la asignación le permite al programa
operar sobre estas localidades, etc.

• Por lo que se puede decir que los primeros lenguajes fueron


imperativos debido a que su característica principal es ser
semejantes a la arquitectura de von Neumman representando una
secuencia de sentencias que representaban comandos o
imperativos.
Paradigmas computacionales
• Con el paso del tiempo surgieron dos paradigmas o patrones
de tipos de lenguajes de lenguajes de programación,
proviniendo ambos de las matemáticas.

• El paradigma funcional, que esta basado en la noción


abstracta del calculo de una función como se estudia en el
cálculo lambda.

• El paradigma lógico, que se encuentra basado en la lógica


simbólica.
Paradigmas computacionales
• Posteriormente, surgió un cuarto paradigma, el orientado a
objetos, el cual es una extensión al paradigma imperativo con
la diferencia de que permite obtener programas compuestos
de muy pequeñas piezas que interactúan de una manera muy
cuidadosamente controlada y cuya interacción y por lo tanto
su resultado final, puede ser cambiado fácilmente.

• Pero sin importar cual paradigma sea el escogido, todos los


lenguajes de programación comparten algunos conceptos en
común y estos son los que estudiaremos a continuación.
Conceptos Importantes
Traductor:
Transforma un código escrito en un lenguaje fuente a un código
equivalente escrito en un lenguaje objeto. como función importante,
el traductor informa de la presencia de errores en el programa fuente.
Conceptos Importantes
Compilador:
Traductor de un lenguaje de alto nivel a uno de bajo nivel.
Esto se cumple en el caso de que el lenguaje fuente sea un
lenguaje de alto nivel, como por ejemplo Cobol, Pascal o C, y el
lenguaje objeto sea un lenguaje de bajo nivel, como por ejemplo el
lenguaje máquina o el de ensamblador.
Conceptos Importantes
Ensamblador:
Traductor de lenguaje de ensamblador a lenguaje máquina.
Por ejemplo cuando el lenguaje fuente sea un lenguaje de
ensamblador y el lenguaje objeto, un lenguaje máquina.
Conceptos Importantes
Vida de un programa: Desde que se escribe hasta que se ejecuta en
una plataforma, se pueden distinguir dos periodos de tiempo:

• El tiempo de compilación. Periodo en el que el programa fuente se traduce


al programa objeto equivalente.
• El tiempo de ejecución. Cuando el programa objeto se ejecuta sobre una
plataforma.
Conceptos Importantes
Interprete:
Compila y ejecuta cada sentencia del programa. No genera código
objeto equivalente al código fuente, sino que compila paso a paso
ejecutando al mismo tiempo:
1) Toma una sentencia del programa fuente.
2) La traduce a su equivalente en el lenguaje objeto.
3) La ejecuta sobre la plataforma.
4) Repite el proceso con la sentencia siguiente del código fuente.
Conceptos Importantes
Intérprete precompilado o compilador interpretativo:
Hace una primera compilación para obtener un código
intermedio libre de error que después se ejecuta por interpretación.
Conceptos Importantes
Preprocesador:
Lee el programa fuente y
en cierta manera lo modifica antes
de la compilación, procesándolo
según unas directivas de
precompilación y las opciones del
entorno de programación.

Así pues, según el


lenguaje, el preprocesador se
ocupa de incluir archivos, eliminar
comentarios, expandir macros,
activar directivas de compilación,
etc.
Conceptos Importantes
Enlazador (linker)
Muchas veces el programa fuente remite a bibliotecas de
programas que ya existen (reaprovechamiento de código).
El enlazador es el encargado de construir el archivo ejecutable
añadiendo al archivo objeto generado por compilador las cabeceras
necesarias y las bibliotecas utilizadas por el programa fuente.
Conceptos Importantes
Depurador (debugger)
Si el compilador ha generado correctamente el programa
objeto, el depurador permite hacer un seguimiento de la ejecución del
mismo paso a paso, muestra el contenido de las variables en tiempo
de ejecución, permite introducir puntos de detención y ayuda a buscar
errores de funcionamiento del programa.
Conceptos Importantes
Visión superficial del proceso de compilación mediante sus principales
actores:
Nociones básicas de un compilador
La tarea de crear un traductor de un lenguaje fuente a un lenguaje objeto es
compleja, pero se puede reducir en gran medida si el proceso se divide en fases
especializadas que realicen cada una tarea específica y el modelo básico de
compilador que estudiaremos es:
Nociones básicas de un compilador
Las fases del compilador, como se ve en la figura anterior, se agrupan en dos
bloques:
• Fases de análisis (front-end). Analizan el programa fuente en busca de
errores (léxicos, sintácticos y semánticos). Son fases que dependen del
lenguaje fuente y que deberían ser independientes de la máquina
(excepto la lectura y la escritura en disco, agrupada en módulos
independientes), mediante las cuales se genera el código objeto.

• Fases de síntesis (back-end). A partir del código intermedio, salido del


bloque anterior, estas fases generan y optimizan el código objeto. Son
fases que dependen del lenguaje objeto y, en general, de la máquina en
la que se ejecutará el código generado.
Nociones básicas de un compilador
• En el compilador hay unas estructuras de datos comunes a todas las
fases.

• La más importante es la tabla de símbolos, que guarda la


información de los objetos que se encuentran en el análisis del código
fuente (variables, etiquetas, tipos, etc.).

• Las diferentes fases acceden constantemente a esta tabla y, por lo


tanto, las rutinas que la gestionan deberían ser muy eficientes.
Nociones básicas de un compilador
Estrategia del código intermedio:
si se genera el mismo tipo de código intermedio, sólo es
necesario programar una única parte de análisis para cada
lenguaje que se desarrolle y una única parte de síntesis para
cada plataforma de ejecución.
Principales Fases de un compilador
Las principales fases de un compilador son:

I. Análisis léxico.
II.Análisis sintáctico.
III.Análisis semántica.
IV.Generación de Código.
V.Optimización.
Principales Fases de un compilador
Análisis léxico:

• El analizador léxico lee el archivo fuente carácter por carácter y


forma grupos de caracteres (lexemas) con un significado léxico
mínimo, denominados testigos, que son tratados como una
entidad única.

• El analizador léxico también elimina los componentes no


esenciales del programa fuente, e ignora los espacios en blanco,
los tabuladores, los caracteres de final de línea, los comentarios y,
en general, todo lo que no sea necesario en las fases posteriores.
Principales Fases de un compilador
Análisis léxico
Principales Fases de un compilador
Análisis sintáctico:

• El analizador sintáctico utiliza los testigos encontrados por el


analizador léxico y comprueba si llegan en el orden correcto: el
proporcionado por la gramática libre de contexto que define el
lenguaje fuente.

• La salida del análisis sintáctico suele ser un árbol sintáctico con la


estructura sintáctica del programa fuente.
Principales Fases de un compilador
Análisis sintáctico
• Siguiendo el ejemplo que hemos visto en el análisis léxico, si
utilizamos la gramática parcial siguiente para reconocer la
estructura sintáctica del programa fuente (las minúsculas
representan variables sintácticas y las mayúsculas testigos
terminales:

exp -> asigna PUNTO_Y_COMA


asigna -> IDENTIFICADOR ASIGNACIÓN operación
operación -> operando operador operando
operador -> SUMA
operando -> IDENTIFICADOR
operando -> ENTERO
Principales Fases de un compilador
Análisis sintáctico el árbol sintáctico
generado sería el que se muestra en la figura:
Principales Fases de un compilador
Análisis semántico:
• El analizador semántico se ocupa de comprobar el significado
de las sentencias: puede haber sentencias sintácticamente
correctas, pero que no se puedan ejecutar por no tener ningún
sentido.
• Generalmente, este análisis se hace al mismo tiempo que el
sintáctico, e introduce unas rutinas semánticas que intentan
encontrar errores de significado (semánticos) a partir del árbol
sintáctico, y al mismo tiempo reúnen información sobre los
tipos de datos de los nombres del programa fuente (variables,
constantes, etc.) que será utilizada en la fase de generación de
código.
Principales Fases de un compilador
Análisis semántico
Entre otras comprobaciones, el análisis semántico:

• Determina el tipo de los resultados intermedios de


las operaciones.

• Comprueba que los operandos de un operador


pertenezcan al conjunto de los tipos posibles para el
operador y si son compatibles entre sí.
Principales Fases de un compilador
Análisis semántico
La salida del análisis semántico suele ser un árbol
semántico, que no es más que un árbol sintáctico en el que
cada nodo ha adquirido su significado.
Principales Fases de un compilador
Generación del Código

En esta última fase de la compilación se genera el código


objeto (generalmente código de ensamblador o código máquina
reubicable) a partir del código intermedio (optimizado o no):

• Se asigna espacio de memoria para cada nombre del programa


fuente (variables, tipos, constantes, etc.).

• Se traduce cada una de las instrucciones en código intermedio a


una secuencia de instrucciones en código objeto que ejecuten la
misma tarea.
Principales Fases de un compilador
•Generación del Código
Ejemplo:

’C := A + B’

podría tener como objeto equivalente:

LOAD A
SUM B
STO C
Principales Fases de un compilador
Optimización del Código:
Esta fase de síntesis está presente en los compiladores más
sofisticados, y su propósito es producir un código objeto más
eficiente:

1. Reduciendo el espacio ocupado por el código generado.

2. Aumentando la rapidez de ejecución.

3. Haciendo que se necesite menos memoria cuando se ejecute.


Principales Fases de un compilador
Gestión y recuperación frente a errores:
• Se debe prever cómo responderá frente a los errores que
encuentre mientras analiza el código fuente y genera el
código objeto.

• Cada una de las fases del compilador detecta unos tipos de


errores determinados.

• Por lo tanto, la gestión de errores tiene que preverse


individualmente en cada fase.
Principales Fases de un compilador
Gestión y recuperación frente a errores

• En el análisis léxico:
símbolos ajenos al lenguaje.

• En el análisis sintáctico:
expresiones mal construidas.

• En el análisis semántico:
variables sin declarar.
En resumen
Al hablar de un compilador, hay que especificar como mínimo los tipos
de lenguajes siguientes:
• El lenguaje fuente.
• El lenguaje objeto y la plataforma de ejecución.
• El lenguaje en el que está escrito el propio compilador, denominado
lenguaje de implementación.
• Ejemplo
En un compilador de Fortran a código máquina del PC que se ejecuta sobre un PC:
– El lenguaje fuente sería el Fortran.
– El lenguaje objeto sería el código máquina del PC.
– El lenguaje en el que está escrito también sería código máquina del PC.
En resumen
Normalmente el lenguaje fuente se especifica generalmente en tres
partes:

•Especificación léxica.
•Especificación sintáctica .
•Especificación semántica .
Construcción de Compiladores
Especificación léxica.

Se hace con expresiones regulares que


definen los componentes léxicos del lenguaje
(testigos).

Utilizando estas definiciones se puede crear


automáticamente un analizador léxico del lenguaje.
Construcción de Compiladores
Especificación sintáctica.

Se utiliza una gramática libre de contexto,


escrita generalmente en notación BNF, para detallar
la estructura sintáctica del lenguaje.
A partir de esta gramática, y junto con el
analizador léxico obtenido en la parte anterior, se
puede generar, también automáticamente, un
analizador sintáctico del lenguaje.
Construcción de Compiladores
Especificación semántica.
Se describe el significado de cada instrucción
sintáctica y las reglas semánticas que se deben
cumplir.
Hay notaciones formales para especificar la
semántica de un lenguaje, pero no se utilizan
demasiado.
La semántica suele ser especificada con
palabras (lenguaje natural) y se programa
manualmente.
Lectura Complementaria
Leer en el libro:

http://homepages.mty.itesm.mx/rbrena/AyL.html

http://homepages.mty.itesm.mx/rbrena/AyL.pdf

Desde el inicio hasta la página 29 (omita la parte de pruebas por


inducción), es decir los preliminares realizando al final los ejercicios
que considere acordes con lo leído.

Importante!!!!: aunque esto no es obligatorio, es deseable que Ud.


lo haga para poder comprender posteriormente de manera más
intuitiva (sin traumas) algunos conceptos más avanzados de la teoría
de lenguajes formales que aplicaremos durante el transcurso de esta
materia y por lo tanto es importante si Ud. tiene aspiraciones de
aprobar la misma.

También podría gustarte