Sunteți pe pagina 1din 58

Compiladores e Interpretes

Clase Introductoria
Luis Ochoa
ziul1979@gmail.com
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.
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.
▫ Teorema de Turing, lo 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. Lo cual en resumen significa que existen problemas que
una computadora nunca podrá resolver.
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.
Eslabones importantes
• 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…)
• 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 de programas. El Fuente: wikipedia
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 los programas almacenados se hizo necesario escribir


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, que son códigos


númericos 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.

• 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.

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


comprendido.
Una breve historia (continuación)

• 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: 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.
ACTIVIDAD EN CLASES:

Hacer un resumen que muestre


ordenados cronológicamente los
aspectos más relevantes de la historia
de los compiladores.
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
•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
•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
•Ensamblador. Algunos compiladores, en lugar
de generar directamente código objeto ejecutable,
generan código de ensamblador que debe
convertirse después en ejecutable utilizando un
programa de ensamblador y un enlazador.
Conceptos Importantes
•Ensamblador. Algunos compiladores, en lugar
de generar directamente código objeto ejecutable,
generan código de ensamblador que debe
convertirse después en ejecutable utilizando un
programa de ensamblador y un enlazador.
Conceptos Importantes
•La integración de estos tres módulos puede observarse acá:
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 de código intermedio: Una
técnica para facilitar la tarea de creación de nuevos
compiladores consiste en dividir el compilador en dos partes:
una fase de análisis y una de síntesis, comunicadas con un
lenguaje intermedio. De esta manera, sólo se deben construir
m fases de análisis que traducen el programa fuente a su
representación intermedia y n fases de síntesis que traducen
del lenguaje intermedio al lenguaje objeto. Es decir, hemos
pasado de crear m x n bloques a crear sólo m + n. Es una
reducción importante que permite construir nuevos
compiladores en mucho menos tiempo.
Desgraciadamente, no hay un lenguaje intermedio de uso
global y cada empresa suele utilizar su propio lenguaje
intermedio.
Principales Fases de un compilador
•Generación de código intermedio
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
•Optimización del Código
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 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
Una conversión directa de código
intermedio a código objeto suele comportar
muchas cargas y descargas innecesarias de
memoria y que los recursos de la máquina se
utilicen de manera poco eficiente. Por este
motivo, muchas veces, después de la
generación del código objeto, también suele
haber una nueva fase de optimización de
código.
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
• 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.
Principales Fases de un compilador
• Gestión y recuperación frente a
errores Algo importante a tener en cuenta
es que aunque es una de las tareas más
importantes del compilador, la gestión de los
errores es lo que más dificulta su
programación:
▫ Unos errores pueden ocultar otros.
▫ Al detectar un error y continuar el análisis,
se puede provocar un alud de errores que
se solucionen automáticamente
resolviendo el primero.
Construcción de Compiladores
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.
Construcción de Compiladores
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. A
veces, algunas reglas semánticas se pueden incluir
dentro de la especificación sintáctica, por ejemplo, la
procedencia y la asociatividad de los operadores. A
partir de esta especificación semántica se construye
el analizador semántico y se genera el código.
Asignación
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.

S-ar putea să vă placă și