materia unidad compiladores

48
Elaborado por: Beatriz Pasaca| Noveno “A” 1 INTRODUCCIÓN Para empezar el estudio de Compiladores conozcamos algo a cerca de su historia. 1958, Strong y otros proponen una solución al problema de que un compilador fuera portable, y esta era dividir al compilador en dos fases “front end” (analiza el programa fuente) y “back end” (genera código objeto para la máquina objeto). El puente de unión era un lenguaje intermedio denominado UNCOL (no funcionó) 1959, Rabin y Scott proponen el empleo de AFD y AFN para el reconocimiento lexicográfico de los lenguajes Aparece BNF (Backus-1960, Naur-1963, Knuth-1964) como una guía para el desarrollo del análisis sintáctico 1959, Sheridan describe un método de parsing de FORTRAN para introducir paréntesis en una expresión 1975, aparece LEX generador automático de analizadores léxicos a partir de expresiones regulares bajo UNIX A mitad de los 70’s Johnson crea YACC para UNIX (generador de analizadores sintácticos) El último lenguaje de programación de amplia aceptación es JAVA (es interpretado) Para escribir un compilador necesitamos: - Algoritmos - Lenguajes de Programación - Lenguajes Formales - Arquitectura del Computador - Ingeniería de Software Programas que el compilador necesita para obtener un programa ejecutable Preprocesador Ligador Cargador Depurador Ensamblador

Upload: bachispasaca

Post on 28-Nov-2014

3.948 views

Category:

Education


1 download

DESCRIPTION

Recopilacion de la materia de compiladores dictada por el Ing. Luis Chamba

TRANSCRIPT

Page 1: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

1

INTRODUCCIÓN

Para empezar el estudio de Compiladores conozcamos algo a cerca de su historia.

• 1958, Strong y otros proponen una solución al problema de que un compilador fuera portable, y esta era dividir al compilador en dos fases “front end” (analiza el programa fuente) y “back end” (genera código objeto para la máquina objeto).

• El puente de unión era un lenguaje intermedio denominado UNCOL (no funcionó) • 1959, Rabin y Scott proponen el empleo de AFD y AFN para el reconocimiento lexicográfico de

los lenguajes

• Aparece BNF (Backus-1960, Naur-1963, Knuth-1964) como una guía para el desarrollo del análisis sintáctico

• 1959, Sheridan describe un método de parsing de FORTRAN para introducir paréntesis en una expresión

• 1975, aparece LEX generador automático de analizadores léxicos a partir de expresiones regulares bajo UNIX

• A mitad de los 70’s Johnson crea YACC para UNIX (generador de analizadores sintácticos)

• El último lenguaje de programación de amplia aceptación es JAVA (es interpretado)

Para escribir un compilador necesitamos: - Algoritmos - Lenguajes de Programación - Lenguajes Formales - Arquitectura del Computador - Ingeniería de Software Programas que el compilador necesita para obtener un programa ejecutable

• Preprocesador • Ligador • Cargador • Depurador • Ensamblador

Page 2: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

2

Conceptos Básicos que hay tener bien claro: � Traductor . Cualquier programa que toma como entrada un texto escrito en un lenguaje

llamado fuente y da como salida un programa equivalente en otro lenguaje, el lenguaje objeto. Si el lenguaje fuente de un lenguaje de programación de alto nivel y el objeto un lenguaje de bajo nivel (ensamblador o código de máquina), al traductor se le denomina compilador.

� Ensamblador . Es un programa traductor cuyo lenguaje fuente es el lenguaje ensamblador.

� Intérprete . Es un programa que no genera un programa equivalente, sino que toma una sentencia del programa fuente en un lenguaje de alto nivel y la traduce al código equivalente y al mismo tiempo lo ejecuta. En un principio debido a la escasez de memoria se utilizaban más los intérpretes, ahora se usan más los compiladores (a excepción de JAVA)

Conceptos básicos Ventajas de compilar vs a interpretar

o Se compila una vez, se ejecuta n veces o En ciclos, la compilación genera código equivalente, interpretándolo se traduce

tantas veces una línea como veces se repite el ciclo o El compilador tiene un visión global del programa

Ventajas del intérprete vs el compilador

o Un intérprete necesita menos memoria que un compilador o Permiten una mayor interactividad con el código en tiempo de desarrollo

Más Diferencias entre Traductor y un Compilador

Traductor: - Análisis Léxico Compilador: - Análisis Léxico - Análisis Sintáctico - Análisis Sintáctico - Análisis Semántico - Análisis Semántico - Generación de código intermedio - Código maquina - Fase de optimizacion Equivalencias

Gcódigo máquina

Page 3: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

3

• Un compilador es un programa informático que traduce un programa escrito en un lenguaje de

programación fuente a otro lenguaje de programación, generando un programa equivalente que

la máquina será capaz de interpretar. Usualmente el segundo lenguaje es código máquina,

pero también puede ser simplemente texto. Este proceso permite generar una lista de los

posibles errores que tenga el programa fuente.

• Un compilador es un programa que lee un programa escrito en un lenguaje, el lenguaje fuente,

y lo traduce a un programa equivalente en otro lenguaje, el lenguaje objeto. Como parte

importante de éste proceso de traducción, el compilador informa a su usuario de la presencia

de errores en el programa fuente.

• Compilador también define como aquel traductor que tiene como entrada una sentencia en

lenguaje formal y como salida tiene un fichero ejecutable, es decir, realiza una traducción de un

código de alto nivel a código máquina (también se entiende por compilador aquel programa que

proporciona un fichero objeto en lugar del ejecutable final).

Funcionamiento del compilador

Hay miles de lenguajes fuente, desde los lenguajes de programación tradicionales, hasta los

lenguajes especializados que han surgido virtualmente en todas las áreas de aplicación de la

información.

Page 4: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

4

Lenguajes objeto son igualmente variados; un lenguaje objeto puede ser otro lenguaje

de programación o el lenguaje de máquina de cualquier computador entre un

microprocesador y un supercomputador.

Estructura de un compilador

Estructura de un compilador.

Page 5: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

5

-Análisis Léxico, Rastreador o Scanner: Descomposición programa fuente en componentes

léxicos. En esta etapa se lee el archivo de texto que contiene el código fuente y las cadenas se

van agrupando en componentes léxicos o tokens.

Estos componentes léxicos son los operadores, identificadores y palabras reservadas del

lenguaje. Por lo tanto, transforma en flujo de caracteres en un flujo de objetos tokens,

ignorando los comentarios y los espacios en blanco y tabulaciones. Esta primera fase es capaz

de detectar errores de tipo léxico.

Por ejemplo, si en lugar de poner if pusiéramos ifi, el analizador léxico no usaría el token

predefinido IF para esa secuencia de caracteres, sino que entendería que es un identificador

cualquiera (como pudiera ser el nombre de una variable).

- Análisis Sintáctico o Parser: Agrupa componentes léxicos en frases gramaticales. Esta

etapa se encarga de verificar que el documento tiene una disposición sintáctica correcta,

agrupando el código en frases gramaticales con sentido. En otras palabras, que el flujo de

tokens tenga el orden correcto.

Esta etapa detecta errores sintácticos. Siguiendo el ejemplo anterior, si hubiésemos puesto ifi

en lugar de if, esta etapa detectaría que la estructura de un bloque if no es así, detectando así

el error en el código fuente. A la siguiente fase le manda árboles de sintaxis abstracta.

Por ejemplo, un árbol cuyo nodo raíz es PROGRAMA, que se compone de una lista de clases.

Cada una de estas serian arboles que describen los distintos atributos y métodos de la misma,

y así sucesivamente. Esta fase no puede detectar los errores.

- Análisis Semántico: Comprobación de validez semántica. Esta es una de las fases más

complejas del proceso de compilación. Tiene que realizar varias tareas: la resolución de

nombres, el tipo de las expresiones y la evaluación L/R. La resolución de nombres consiste en

comprobar que cada identificador que usemos (ya sea una variable local, parámetro, atributo o

método) ha sido previamente declarado y además, si es visible desde el ámbito actual.

Page 6: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

6

En cuanto a los tipos, hay que comprobar que en las expresiones donde intervengan 2

identificadores (asignación, suma, producto, concatenación, etc.) comprobar que ambos

identificadores aceptan dicha operación, que son de tipos compatibles. La evaluación L/R

consiste en que para cada asignación, cada componente puede aparecer o no en esa posición.

Por ejemplo, el resultado de una serie de operaciones no puede aparecer a la izquierda, solo a

la derecha, ya que no se puede asignar un valor al resultado de una serie de operaciones.

Tabla de símbolos: Una función esencial de un compilador es registrar los

identificadores de usuario (nombres de variables, de funciones, de tipos, etc.) utilizados

en el programa fuente y reunir información sobre los distintos atributos de cada

identificador. Estos atributos pueden proporcionar información sobre la memoria

asignada a un identificador, la dirección de memoria en que se almacenará en tiempo

de ejecución, su tipo, su ámbito (la parte del programa donde es visible), etc. Pues

bien, la tabla de símbolos es una estructura de datos que posee información sobre los

identificadores definidos por el usuario, ya sean constantes, variables, tipos u otros.

Dado que puede contener información de diversa índole, debe hacerse de forma que

su estructura no sea uniforme, esto es, no se guarda la misma información sobre una

variable del programa que sobre un tipo definido por el usuario. Hace funciones de

diccionario de datos y su estructura puede ser una tabla hash, un árbol binario de

búsqueda, etc., con tal de que las operaciones de acceso sean lo bastante eficientes.

Esquema por etapas definitivo del traductor

Tanto la etapa de análisis como la de síntesis acceden a esta estructura, por lo que se halla

muy acoplada al resto de fases del compilador. Por ello conviene dotar a la tabla de símbolos

de una interfaz lo suficientemente genérica como para permitir el cambio de las estructuras

Page 7: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

7

internas de almacenamiento sin que estas fases deban ser retocadas. Esto es así porque suele

ser usual hacer un primer prototipo de un compilador con una tabla de símbolos fácil de

construir, y cuando el compilador ya ha sido finalizado, entonces se procede a sustituir la tabla

de símbolos por otra más eficiente en función de las necesidades que hayan ido surgiendo. El

esquema general definitivo de un traductor se detalla en la figura anterior.

TIPOS DE COMPILADORES

Existen varias categorías de compiladores entre los más conocidos tenemos:

• Compiladores cruzados : generan código para un sistema distinto del que están

funcionando.

• Compiladores optimizadores : realizan cambios en el código para mejorar su

eficiencia, pero manteniendo la funcionalidad del programa original.

• Compiladores de una sola pasada : generan el código máquina a partir de una única

lectura del código fuente.

• Compiladores de varias pasadas : necesitan leer el código fuente varias veces antes

de poder producir el código máquina.

• Compiladores JIT (Just In Time): forman parte de un intérprete (traductor que realiza la

operación de compilación paso a paso), y compilan partes del código según se

necesitan.

Partes de un compilador

Normalmente los compiladores están divididos en dos partes:

� Front End: es la parte que analiza el código fuente, comprueba su validez, genera el

árbol de derivación y rellena los valores de la tabla de símbolos. Esta parte suele ser

independiente de la plataforma o sistema para el cual se vaya a compilar.

� Back End: es la parte que genera el código máquina, específico de una plataforma, a

partir de los resultados de la fase de análisis, realizada por el Front End.

Esta división permite que el mismo Back End se utilice para generar el código máquina de

varios lenguajes de programación distintos y que el mismo Front End que sirve para analizar el

Page 8: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

8

código fuente de un lenguaje de programación concreto sirva para generar código máquina en

varias plataformas distintas.

El código que genera el Back End normalmente no se puede ejecutar directamente, sino que

necesita ser enlazado por un programa enlazador (linker).

Arquitectura Real De Compiladores E Intérpretes La arquitectura real de compiladores e intérpretes es la siguiente:

Page 9: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

9

ANALIZADOR LÉXICO

Definición: El analizador léxico (scanner), lee un texto fuente y lo transforma en una secuencia

ordenada de elementos léxicamente válidos. Un caracter o conjunto de estos que constituya un

componente léxico se llama lexema (token). Como componentes léxicos consideramos:

palabras reservadas, separadores, operadores, identificadores, constantes y signos de

puntuación.

Funciones del analizador léxico

La fase de análisis léxico se halla bajo el control del análisis sintáctico.

Normalmente se implementa como una función de éste componentes léxicos que utiliza el

analizador sintáctico para hacer el análisis. Esta interacción suele aplicarse convirtiendo al

analizador léxico en una subrutina o corrutina del analizador sintáctico. Recibida la orden

“Dame el siguiente componente léxico”del analizador sintáctico, el léxico lee los caracteres de

entrada hasta que pueda identificar el siguiente componente léxico, el cual devuelve al

sintáctico según el formato convenido (figura anterior).

Analizador léxico lee la secuencia de caracteres del programa fuente, carácter a carácter, y los

agrupa para formar unidades con significado propio, los componentes léxicos (tokens en

ingles). Estos componentes léxicos representan:

• Palabras reservadas: if, while, do.

• Identificadores: asociados a variables, nombres de funciones, tipos definidos por el usuario,

etiquetas. Por ejemplo: posición, velocidad, tiempo.

• Operadores: = * + - / == > < & ! = .

Page 10: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

10

• Símbolos especiales: ; ( ) [ ] f g.

• Constantes numéricas: literales que representan valores enteros, en coma flotante, etc.,

982, 0xF678, -83.2E+2,…

• Constantes de caracteres: literales que representan cadenas concretas de caracteres, “hola

mundo",...

El analizador léxico opera bajo petición del analizador sintáctico devolviendo un componente

léxico conforme el analizador sintáctico lo va necesitando para avanzar en la gramática. Los

componentes léxicos son los símbolos terminales de la gramática.

Otras funciones:

• Manejo del fichero de entrada del programa fuente: abrirlo, leer sus caracteres, cerrarlo

y gestionar posibles errores de lectura.

• Eliminar comentarios, espacios en blanco, tabuladores y saltos de línea (caracteres no

validos para formar un token).

• Inclusión de ficheros: # include ...

• La expansión de macros y funciones inline: # define ...

• Contabilizar el número de líneas y columnas para emitir mensajes de error.

• Reconocimiento y ejecución de las directivas de compilación (por ejemplo, para depurar

u optimizar el código fuente).

• Rechazar un carácter o conjunto de estos que no concuerden con patrones

especificados

• Entendamos como patrón una expresión regular que se define en el lenguaje.

• Ignorar comentarios, espacios en blanco y tabuladores.

• Gestionar errores, contando los saltos de línea y asociando los mensajes de error con el

número de la línea del archivo fuente donde se producen.

• Guardar tokens junto con su atributo en una tabla de símbolos. Este atributo es

información adicional relevante, habitualmente con relación a los identificadores.

Componentes Toquen, Patrones, Lexemas

� Token: “nombre “que se da a cada componente léxico.

Page 11: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

11

� Patrón : es una regla que genera la secuencia de caracteres que puede representar a

un determinado componente léxico (una expresión regular).

� Lexema : cadena de caracteres que concuerda con un patrón que describe un

componente léxico. Un componente léxico puede tener uno o infinitos lexemas.

Además

o Un token se corresponde con un patrón

o Un token se puede corresponder con muchos lexemas

o Tokens más habituales:

o Palabras reservadas

o Identificadores

o Operadores

o Constantes

o Símbolos de puntuación: ; , . :

o Símbolos especiales: ( ) [ ]

o Pero, a la vez que el propio token, el scanner puede (debe) devolver más

información

o Si es un token CONSTANTE, su valor

o Si es un identificador, el string correspondiente

o Si es un símbolo de puntuación, cuál

o Esta información, se devuelve mediante “atributos”. Pero aún puede hacer algo

más:

o Puede detectar algunos (pocos) errores léxicos

No hay concordancia con ningún patrón

• Puede llevar a cabo algunas recuperaciones de errores

o Filtrado de caracteres “extraños”

o Completar algún patrón

o Reemplazar algún carácter

Los componentes léxicos se suelen definir como un tipo enumerado. Se codifican como

enteros. También se suele almacenar la cadena de caracteres que se acaba de reconocer (el

lexema), que se usara posteriormente para el análisis semántico.

Page 12: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

12

Es importante conocer el lexema (para construir la tabla de símbolos). Los componentes

léxicos se representan mediante una estructura registro con tipo de token y lexema.

Especificación de los componentes léxicos: expresio nes regulares.

Los componentes léxicos se especifican haciendo uso de expresiones regulares. Además de

las tres operaciones básicas: concatenación, repetición (*) y alternativas (j) vamos a usar los

siguientes meta símbolos:

Una o más repeticiones +

• r+ indica una o más repeticiones de r

• (0j1)+ = (0j1) (0j1)*

Cualquier carácter .

• .*b.* indica cualquier cadena que contiene una letra b

Un rango de caracteres [ ] (clase)

• [a-z] indica cualquier carácter entre la a y z minúsculas

• [a-zA-Z] indica cualquier letra del abecedario minúscula o mayúscula

• [0-9] indica cualquier digito de 0 a 9

• [abc] indica ajbj

Cualquier carácter excepto un conjunto dado

• ~ (ajb) indica cualquier carácter que no sea una a o b

Opcionalidad ?

r? indica que la expresión r puede aparecer o no. En el caso de que aparezca solo lo haría una

vez.

Page 13: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

13

Aspectos prácticos en la implementación de un anali zador léxico

Principio de máxima longitud

Se da prioridad al componente léxico de máxima longitud.

Por ejemplo: <> se interpreta como el operador “distinto de", en vez de “menor que" y “mayor

que".

Palabras reservadas versus identificadores

Un lenguaje de programación puede tener del orden de 50 palabras reservadas. Para evitar

tener un AFD demasiado grande las palabras reservadas se reconocen como identificadores

(una palabra reservada también es una letra seguida de mas letras) y se comprueba antes de

decidir el tipo de token si se trata de una palabra reservada o de un identificador consultando

una tabla previamente inicializada con las palabras reservadas. Este método es recomendable

cuando el número de palabras reservadas es grande.

Entrada de los identificadores en la Tabla de Símbo los

En lenguajes sencillos con solo variables globales y declaraciones, es normal implementar el

scanner para que introduzca los identificadores en la Tabla de Símbolos conforme los va

reconociendo, si es que no han sido ya introducidos. Después, el analizador sintáctico se

encarga de introducir información adicional sobre su tipo, etc. conforme la va obteniendo

durante el análisis sintáctico.

Si se trata de un lenguaje estructurado, el scanner no introduce los identificadores en la Tabla

de Símbolos, porque en este tipo de lenguajes, los identificadores pueden tener diferentes

significados según su contexto (como una variable, como una función, como un campo de una

estructura). Es el análisis sintáctico, cuando ya ha recogido información sobre su ámbito,

cuando introduce los identificadores en la Tabla de Símbolos.

Gestión del búfer de entrada

Page 14: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

14

El proceso de lectura de los caracteres de la entrada y formar los componentes léxicos es lo

más costoso en tiempo en el proceso de traducción. Es importante implementarlo

eficientemente.

Se utiliza un búfer dividido en dos mitades de tamaño N caracteres, donde N es un bloque de

disco (1024, 4096). Se leen N caracteres de la entrada en cada mitad del búfer con una única

orden de lectura. Se mantienen dos apuntadores. Uno marca el

inicio del lexema y el otro el carácter actual que se mueve hasta encontrar una subcadena que

corresponde con un patrón. Una vez reconocido un componente léxico ambos apuntadores se

colocan en la misma posición y justo detrás del lexema reconocido.

Tratamiento de errores léxicos

Los errores léxicos se detectan cuando el analizador léxico intenta reconocer componentes

léxicos y la cadena de caracteres de la entrada no encaja con ningún patrón. Son situaciones

en las que usa un carácter invalido (@, $,",>,...), que no pertenece al vocabulario del lenguaje

de programación, al escribir mal un identificador, palabra reservada u operador.

Errores léxicos típicos son:

• Nombre ilegales de identificadores: un nombre contiene caracteres inválidos.

• Números incorrectos: un numero contiene caracteres inválidos o no está formado

correctamente, por ejemplo 3,14 en vez de 3.14 o 0.3.14.

• Errores de ortografía en palabras reservadas: caracteres omitidos, adicionales o

cambiados de sitio, por ejemplo la palabra hwile en vez de while.

• Fin de archivo: se detecta un fin de archivo a la mitad de un componente léxico.

En general, la recuperación de errores léxicos es sencilla y siempre se traduce en la

generación de un error de sintaxis que sería detectado más tarde por el analizador sintáctico

cuando el analizador léxico devuelve un componente léxico que el analizador sintáctico no

espera en esa posición.

Los métodos de recuperación de errores léxicos se basan bien en saltarse caracteres en la

entrada hasta que un patrón se ha podido reconocer.

Page 15: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

15

Una buena estrategia para la recuperación de errores léxicos:

Si en el momento de detectar el error ya hemos pasado por algún estado final ejecutamos la acción

correspondiente al último estado final visitado con el lexema formado hasta que salimos de él; el

resto de caracteres leídos se devuelven al flujo de entrada y se vuelve al estado inicial.

Si no hemos pasado por ningún estado final, advertimos que el carácter encontrado no se

esperaba, lo eliminamos y proseguimos con el análisis.

Por ejemplo, ante la entrada 73:a:

• Devolvemos el componente léxico entero con lexema 73;

• Devolvemos al búfer de entrada los caracteres :a y

• Volvemos al estado inicial.

En la siguiente llamada al analizador léxico se producirá un nuevo error, en este caso

descartamos el carácter leído (el punto) y seguimos con el análisis. En la siguiente llamada

detectamos el identificador a.

Si se sabe que el siguiente componente léxico es una palabra reservada (en la gramática

esperamos una palabra reservada) es posible corregir la palabra mal escrita. Por ejemplo, si se

ha escrito hwile en vez de while o fi en vez de if, intercambiando caracteres adyacentes.

Implementación de Analizadores léxicos

1. Usar un generador automático de analizadores léxicos (LEX)

Ventaja: comodidad y rapidez en el desarrollo

Desventaja: ineficiencia del analizador resultante y mantenimiento complicado

2. Escribir el AL en un lenguaje de alto nivel

Ventaja: más eficiente

Desventaja: hay que hacerlo todo

Page 16: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

16

3. Hacerlo en lenguaje ensamblador

Ventaja: máxima eficiencia

Desventaja: muy complicado de desarrollar

ANALIZADOR SINTÁCTICO

Definición: Es la fase del analizador que se encarga de chequear la secuencia de tokens que

representa al texto de entrada, en base a una gramática dada. En caso de que el programa de

entrada sea válido, suministra el árbol sintáctico que lo reconoce en base a una representación

computacional. Este árbol es el punto de partida de la fase posterior de la etapa de análisis: el

analizador semántico.

Para obtener en lógica el rigor y precisión deseados, se introduce un lenguaje formal. Éste es

un lenguaje artificial, con unas reglas gramaticales explícitas que indican qué sucesiones de

signos del alfabeto son fórmulas y unas reglas semánticas también explícitas, que determinan

cuando una fórmula es verdadera bajo una determinada interpretación. Éste lenguaje, pues, se

utiliza como vehículo para el razonamiento.

El análisis sintáctico es una aplicación que resulta del estudio de la Teoría de Autómatas y

Lenguajes Formales. El análisis sintáctico es la base del Demostrador de Teoremas, ya que le

permite analizar los componentes léxicos y sintácticos que forman una fórmula para después

poderla evaluar, o no en caso de error, correctamente.

Visión General: Todo lenguaje de programación obedece a unas reglas que describen la

estructura sintáctica de los programas bien formados que acepta. Se puede describir la sintaxis

de las construcciones de los lenguajes de programación por medio de gramáticas de contexto

libre.

Las gramáticas formales ofrecen ventajas significativas a los diseñadores de lenguajes y a los

desarrolladores de compiladores:

• Las gramáticas son especificaciones sintácticas y precisas de lenguajes de

programación.

• A partir de una gramática se puede generar automáticamente un analizador sintáctico.

Page 17: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

17

• El proceso de generación automática anterior puede llevar a descubrir ambigüedades.

• Una gramática proporciona una estructura a un lenguaje de programación, siendo más

fácil generar código y detectar errores.

• Es más fácil ampliar/modificar el lenguaje si está descrito con una gramática.

El analizador sintáctico dirige el proceso de compilación, de manera que el resto de fases

evolucionan a medida que el sintáctico va reconociendo la secuencia de entrada por lo que, a

menudo, el árbol ni siquiera se genera realmente.

En la práctica, el analizador sintáctico también:

• Incorpora acciones semánticas en las que colocar el resto de fases del compilador

(excepto el analizador léxico): desde el análisis semántico hasta la generación de

código.

• Informa de la naturaleza de los errores sintácticos que encuentra e intenta recuperarse

de ellos para continuar la compilación.

• Controla el flujo de tokens reconocidos por parte del analizador léxico.

El papel de Analizador sintáctico

El analizador obtiene una cadena de componentes léxicos del analizador léxico, como se

muestra en la siguiente figura, y comprueba si la cadena puede ser generada por la gramática

del lenguaje fuente. El analizador sintáctico informará de cualquier error de sintaxis de manera

inteligente. También debería recuperarse de los errores que ocurren frecuentemente para

poder continuar procesando el resto de su entrada.

Page 18: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

18

Posición del Analizador sintáctico en el modelo del compilador

Manejo de errores sintácticos

• Los errores en la programación pueden ser de los siguientes tipos:

• Léxicos, producidos al escribir mal un identificador, una palabra clave o un operador.

• Sintácticos, por una expresión aritmética o paréntesis no equilibrados.

• Semánticos, como un operador aplicado a un operando incompatible.

• Lógicos, puede ser una llamada infinitamente recursiva.

• De corrección, cuando el programa no hace lo que el programador realmente deseaba.

Gramáticas

Una Gramática es la definición de un lenguaje. Lenguaje es la colección (finita o no) de

palabras de un alfabeto (representado por å ), por ejemplo, cualquier subconjunto de å *

(conjunto de todas las posibles palabras definidas sobre un alfabeto). El alfabeto del lenguaje L

de la lógica proposicional contiene dos tipos de signos: los conectores y las letras

proposicionales. Se utilizarán: ¬, v, Ù , ® , « como conectores y p, q, r, s, t, ... como letras

proposicionales. Se utilizarán también los paréntesis como signos impropios.

Las fórmulas de L se construyen siguiendo unas sencillas reglas de formación. Dichas reglas

extraen del conjunto de filas de signos del alfabeto aquellas a las que llamamos fórmulas.

La definición de Gramática formal viene dada por la:

Cuádrupla G= ( å t, å n, S, P).

Donde:

å t: es el alfabeto de símbolos Terminales.

å n: es el alfabeto de símbolos No Terminales.

S: es el axioma o símbolo inicial de la gramática.

P: es conjunto finito de producciones xAy::=z / x,y,z eå * Aeån.

Page 19: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

19

Permite, por tanto, la especificación formal de las reglas de generación de los programas.

Derivaciones: El funcionamiento de las gramáticas se basa en el concepto de derivación.

Comenzando por una cadena formada únicamente por el símbolo inicial, se aplican

repetidamente las reglas de P hasta llegar a una cadena formada únicamente por terminales.

Aplicar una regla consiste simplemente en encontrar, dentro de la cadena actual, la parte

izquierda de la regla y sustituirla por la parte derecha correspondiente

Arboles de Ambigüedad

Supongamos que tenemos la gramática:

con las siguientes producciones

La sentencia id*id+id, tiene distintas derivaciones:

Page 20: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

20

Sin embargo, en cierto sentido, todas estas derivaciones representan una misma \estructura".

De alguna manera nos transmiten la idea de que se está sumando el producto de los dos

primeros id con el tercero. Podemos representar esto de forma compacta mediante un _árbol

como el siguiente:

Esto es lo que se conoce como _árbol de análisis o de derivación. Intuitivamente, lo que

hacemos es representar \en paralelo" las reglas necesarias para derivar la cadena de entrada.

La raíz es el símbolo inicial de la gramática y cada nodo interior representa una producción:

está etiquetado con un no terminal (A) y sus hijos de izquierda a derecha están etiquetados con

X1; X2; : : : ;Xn de modo que (A) � X1X2 : : :Xn es una regla de la gramática.

Puede suceder que una misma cadena tenga asociado más de un _árbol de derivación. Por

ejemplo, la cadena anterior tiene asociados dos _arboles, cada uno con un significado distinto:

Page 21: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

21

Decimos que una sentencia es ambigua (con respecto a una gramática) si tiene más de un

árbol sintáctico.

Clasificación de las gramáticas

La Jerarquía de Chomsky consta de cuatro niveles:

• Gramáticas de tipo 0 (sin restricciones), que incluye a todas las gramáticas formales.

Estas gramáticas generan todos los lenguajes capaces de ser reconocidos por una

máquina de Turing. Los lenguajes son conocidos como lenguajes recursivamente

enumerables. Nótese que esta categoría es diferente de la de los lenguajes recursivos,

cuya decisión puede ser realizada por una máquina de Turing que se detenga.

• Gramáticas de tipo 1 (gramáticas sensibles al contexto) generan los lenguajes sensibles

al contexto. Estas gramáticas tienen reglas de la forma con A un no

terminal y α, β y γ cadenas de terminales y no terminales. Las cadenas α y β pueden

ser vacías, pero γ no puede serlo. La regla está permitida si S no aparece en la

parte derecha de ninguna regla. Los lenguajes descritos por estas gramáticas son

exactamente todos aquellos lenguajes reconocidos por una máquina de Turing no

determinista cuya cinta de memoria está acotada por un cierto número entero de veces

sobre la longitud de entrada.

• Gramáticas de tipo 2 (gramáticas libres del contexto) generan los lenguajes

independientes del contexto. Las reglas son de la forma con A un no terminal y

γ una cadena de terminales y no terminales. Estos lenguajes son aquellos que pueden

ser reconocidos por un autómata con pila.

• Gramáticas de tipo 3 (gramáticas regulares) generan los lenguajes regulares. Estas

gramáticas se restringen a aquellas reglas que tienen en la parte izquierda un no

terminal, y en la parte derecha un solo terminal, posiblemente seguido de un no

terminal. La regla también está permitida si S no aparece en la parte derecha de

ninguna regla. Estos lenguajes son aquellos que pueden ser aceptados por un autómata

finito. También esta familia de lenguajes pueden ser obtenidas por medio de

expresiones regulares.

Nótese que el conjunto de gramáticas correspondiente a los lenguajes recursivos no es un miembro de la jerarquía.

Page 22: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

22

Tipos de Gramáticas

Cada lenguaje regular es a su vez libre del contexto, asimismo un lenguaje libre del contexto es

también dependiente del contexto, éste es recursivo y a su vez, recursivamente enumerable.

Las inclusiones son, sin embargo, propias, es decir, existen en cada nivel lenguajes que no

están en niveles anteriores.

Las gramáticas de tipo 2 o Libres de contexto, son las mas útiles para lenguajes de

programación, en la actualidad son la manera estándar de representar la estructura de los

lenguajes desprogramación.

Gramáticas independientes del contexto

La gramática independiente del contexto consta de terminales, no terminales, un símbolo inicial

y producciones.

1. Los terminales son los símbolos básicos con que se forman las cadenas.

2. Los no terminales son variables sintácticas que denotan conjuntos de cadenas. Los no

terminales definen conjuntos de cadenas que ayudan a definir el lenguaje generado por la

gramática.

3. En una gramática, un no terminal es considerado como el símbolo inicial, y el conjunto de

cadenas que representan es el lenguaje definido por la gramática.

4. Las producciones de una gramática especifican cómo se pueden combinar los terminales

y los no terminales para formar cadenas. Cada producción consta de un terminal,

seguido por algún símbolo y seguida por una cadena de no terminales y terminales.

Notación BNF

Page 23: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

23

Utilizada para representar las gramáticas independientes de contexto, que es una escritura

gramatical para especificar lenguajes de programación.

En esta notación se deben seguir las siguientes convenciones:

No terminales: Paréntesis angulares � <>

Terminal: Cadenas de caracteres

El símbolo : := se lee “se define como”, ó se ”reescribe como”, se utiliza en lugar de la flecha.

Varias producciones de tipo:

<A> : := <B1>

<A> : := <B2>

<A> : := <Bn>

se pueden escribir como:<A> : := <B1> | < B2> |…| <B n>

GRAMÁTICAS REGULARES GRAMÁTICAS INDEPENDIENTES DEL CONTEXTO

• En el lado derecho existe un nodo terminal

seguido de un no terminal, o un solo terminal, y

en el lado izquierdo existe un nodo no terminal.

• Poseen constantes a, b, c.

• Genera lenguaje de tipo 3 o lenguaje regular.

• Su importancia reside en que los lenguajes

generados por ellas son exactamente aquellos

que reconocen los autómatas finitos.

• Existe exactamente un no terminal

• La parte de la cadena no generada siempre

aparece al final.

• Proporciona un plantilla o patrón para las

cadenas de lenguajes.

• Si en cada regla α → β cumple:

α ϵ Vn, y

β ϵ Vt, p

β ϵ (Vt * Vn)

• Poseen variables A, B, C

• Poseen cadenas de caracteres.

• Genera lenguaje de tipo 2 o

lenguaje independiente de

contexto.

• No tiene restricciones con respecto

a sus reglas de escritura, su lado

izquierdo de cada regla sea un no

terminal: S →aMa.

• Se utilizan cuando un autómata

finito no reconoce ciertos

lenguajes.

• Se escriben frecuentemente

utilizando una notación conocida

con BNF (Backus-Naur).

• Para determinar si están bien

escritas se utilizan los árboles de

análisis sintáctico.

• Cualquier gramática libre de

contexto que no contenga la

cadena vacía puede ser generado:

L – {λ} ó L.

• El no terminal puede ser uno o

Page 24: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

24

- Gramáticas Lineales - A::=α

por la izquierda → - A::=Va

- S::=λ

Se clasifican en: - Gramáticas Lineales - A::=a

por la derecha → - A::=aV

- S::=λ

Cumple con los requerimientos para ser una

gramática de tipo 2.

varios que puede tomar.

• Proporcionar un mecanismo simple

y exacto para describir los métodos

por los cuales las frases (lenguaje

natural) son construidas de

bloques más pequeñas.

• Si cada regla: α→β cumple α ϵ Vn.

• Cumple los requerimientos para

ser una gramática tipo 1.

Diferencias entre Gramáticas Regulares y gramáticas Libes de contexto

Ejercicios resueltos en clases:

� Sea la gramática: S → AA

A → AAA | a | bA |Ab

Determinar la cadena b2aba

2ba → bbabaaba

S →AA→ bAA → bbAA → bbaA → bbabA → bbabAAA → bbabaAA → bbabaAbA→ bbabaabA → babaaba

Arboles de derivación

� Sea la gramática: S → AB

A → aA | a

B → bB | b

Determinar la cadena aabbb elaborar el árbol de derivación.

� S →AB→ aAB → aaB →aabB → aabbB → aabbb

� S → AB → aAbB → aabbB → aabbb

� S → AB → abB → AbbB → Abbb → aAbbb →aabbb

Page 25: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

25

Árbol de Derivación de la cadena aabbb

Se puede concluir que la gramática anteriormente definida no es ambigua, ya que a pesar de tener

derivaciones diferentes, posee un solo árbol de derivación.

Sea la gramática: S → SbS | ScS | a , construir el árbol de derivación para la cadena abaca

Derivaciones:

1. S → SbS → abS → abScS → abaca

2. S → SbS → SbScS → abaca

3. S → ScS → SbScS → abaca

Árboles de Derivación para la cadena abaca

Ambigüedad: La presencia de dos derivaciones por la izquierda distintos se corresponde con la

existencia de dos árboles de derivación distintos por lo tanto una gramática ambigua se caracteriza por

tener dos o más derivaciones por la izquierda para la misma cadena.

Page 26: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

26

Derivación por la izquierda

1. S → ScS → SbScS → abScS → abacS → abaca

El no terminal se expande siempre del extremo izquierdo.

Derivación por la derecha

2. S → ScS → SbSca → Sbaca → abaca

El no terminal es el que se expande por la derecha.

• Toda derivación en una gramática regular, es a la vez una derivación por la izquierda y por la

derecha sin tener en cuenta si tenemos una gramática regular por la derecha o por la izquierda.

Simplificación de Gramáticas Independientes de Contexto

Para realizar la simplificación de estas gramáticas debemos hacer lo siguiente:

1. Eliminar las producciones en donde se encuentre contenida la palabra vacía (ε ó λ).

Gramática:

1. A → bAA | aC | B

2. B → aSS | BC

3. C→ CC | λ

4. S → SS | C A

Para ello reemplazamos la cadena vacía en todos los no terminales que la produzcan así:

1. A → bAA | aC | B

A → bAA | a λ | B | a λ

A → bAA | a | B | a

2. B → aSS | BC | B λ

B → aSS | BC | B

3. C→ CC | C λ

C→ CC | C

4. S → SS |CA| λA

S → SS | C A | A

Page 27: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

27

La gramática resultante será:

A → bAA | a | B

B → aSS | BC | B

C→ CC | C

S → SS | C A | A

2. Eliminar las producciones unitarias

Se considera una producción unitaria cuando es de la forma B → B

Identificamos las producciones unitarias y luego reemplazamos el nodo o no terminal unitario con la

parte derecha de todas las producciones que produce así:

A → bAA | a | B

B → aSS | BC | B

C→ CC | C

S → SS | C A | A

La gramática resultante será:

A → bAA | a | aSS | BC

B → aSS | BC

C→ CC

S → SS | C A | bAA | a | aSS | BC

3. Eliminar no terminales no generativos

Para esta gramática el no terminal no generativo es C ya que no produce terminal alguno, entonces

eliminamos todas las producciones que lo contengan:

A → bAA | a | aSS | BC

B → aSS | BC

C→ CC

S → SS | C A | bAA | a | aSS | BC

La gramática que nos queda es:

A → bAA | a | aSS

B → aSS

C→ CC

S → SS | bAA | aSS | a

4. Eliminar los símbolos no alcanzables

Page 28: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

28

Los símbolos no alcanzables son aquellos que no son producidos por algún axioma o no terminal,

para nuestro ejercicio los símbolos no alcanzables son los no terminales B y C.

A → bAA | a | aSS

B → aSS

C→ CC

S → SS | bAA | aSS | a

Entonces la gramática simplificada nos queda:

A → bAA | a | aSS

S → SS | bAA | aSS | a

NOTA: Para una mejor eficiencia de la simplificación se deben aplicar todos los pasos

anteriormente mencionado en orden.

Ejercicios resueltos en clases:

G = ({0,1}, {A, A, B, C}, S, P)

P S → AB |0S1| A |CX

A → 0AB | λ

B → B1 | λ

1. Eliminar los no terminales inútiles 2. Eliminar producciones vacías λ

S → AB |0S1| A S → AB |0S1|A|B| λ

A → 0AB | λ A → OAB |0A|OB| O

B → B1 | λ B → B1 | 1

3. Eliminar producciones unitarias

S → AB |0S1|OAB|OA|OB|O|B1|1| λ

A → OAB |0A|OB| O

B → B1 | 1

G = ({i,+}, {Z, E, F, G, P, Q, S, T}, Z, P)

P Z → E + T | T

E → E | S + F | T

F → F | FP | P

P → G

G → G | GG | F

T → T + i | i

Q → E | E + F | T | S

S → i

Page 29: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

29

1. Eliminar los no terminales inútiles 2. Eliminar producciones vacías λ

Z→ E + T Z→ E + T

E → E | S + F | T E → T

F → F | FP | P T → T + i | i

P → G

G → | GG | F

T → T + i | i

S → i

3. Eliminar producciones unitarias

Z → T + T Z → T + T

T → T + i | i ó E → T + i | i

T → T + i | i

1. FORMA NORMAL DE CHOMSKY

Se aplica: A → BC No se puede aplicar cuando:

A → a - Hay producciones vacías

A, B, C ϵ V, a ϵ ∑ - Hay producciones unitarias

EJERCICIO

� B → aBC

E → aBEd

1. Eliminar producciones vacías λ 2. Reemplazamos en la gramática

Da → a B → DaBC

Dd → d E → DaBEDd

Base (2) → Longitud de la parte derecha sea mayor que dos

A → CBFH A → D1FH

D1 → CB

A → D1D2

D2 → FH

Ejercicio resuelto en clases:

Dada la siguiente expresión regular, determinar el AFD, la gramática y el árbol de derivación para una

cadena valida.

Page 30: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

30

Expresión regular → letra+_numero

+letra

+ | _letra

*

Gramática:

<inicio> → _ <guion>

<inicio> → l <letra>

<guion> → l <letra> | n <numero>

<letra> → n <numero> | l_<guion> | λ

<numero> → l <letra>

• Derivación de la cadena _a

<inicio> → _<guion> <inicio>

<guion> → _a <letra>

<letra> → _a λ _ <guion>

<letra> → _a

a <letra>

λ

Ejercicio resuelto en clases: Simplificar la gramática: S → A | Aa

A → B

B → C | b

C → D | ab

D → b

1. Unitario(S) → {S, A, B, C, D} 2. S → Aa | b | ab 3. S → Aa | b | ab

Unitario (A) → {A, B, C, D} A → b | ab A→ b | ab

Unitario (B) → {B, C, D} C → ab | b

Unitario (C) → {C, D} D → d

Page 31: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

31

Aplicar la forma normal de Chomsky en la siguiente gramática:

S → bA | aB S → DbA | DaB

A → bAA | aS |a A → DbD1 | DaS |Da

B → aBB | bS | b B →DaD2 | bS | b

Da → a

Db → b

D1→ AA

D2 → BB

Simplificar las gramáticas dadas:

Gramática 1: S → AB| Cb

A → f | Al

B → t | CF

D → a | t

1. Términos no Generativos 2. Términos no Alcanzables

S → AB S → AB

A → f | Al A → f | Al

B → t B → t

D → a | t

Gramática 2: S → aA| λ

A → aA | bB | λ

B → bB

1. Producciones vacías 2. Términos no Generativos 3 Términos no alcanzables

S → aA| λ S → aA| λ S → aA| λ

A → aA | bB | a A → aA | a A → aA | a

B → bB B → bB

Gramática 3: S → a| aA | B | E

A → aB | λ

B → Aa

E → bED

D → ccc

Page 32: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

32

1. Producciones vacías 2. Producciones unitarias

S → a| aA | B | E S → a| aA | Aa |bED

A → aB | a A → aB | a

B → Aa | a B → Aa | a

E → bED E → bED

D → ccc D → ccc

3. Términos no Generativos 4. Términos no alcanzables

S → a | aA| Aa S → a |aA| Aa

A → aB |a A → aB | a

B → Aa | a B →Aa | a

E → bED

D → ccc

Gramática 4: S → ABaC

A → AB

B → b |λ

C → D | λ

D → d

2. Producciones vacías 2. Producciones unitarias 3. Términos no alcanzables

S → ABaC |AaC | ABa | Aa S → ABaC |AaC | ABa | Aa S→ABaC|AaC|ABa|Aa

A → AB | A A → AB A→AB

B → b B → b B→b

C → D C → D C→d

D → d D → d

Análisis

Es el proceso que permite determinar si una cadena puede ser generada por una gramática. En

nuestro caso es el proceso que permite determinar si una fórmula está bien generada o no.

Este proceso es fundamental en el Demostrador de Teoremas ya que una cadena que no sea

una fórmula no servirá para realizar los razonamientos o pruebas. Por tanto es importante

definir precisamente un analizador para esta tarea.

Desde el punto de vista formal un analizador es un Autómata equivalente a una gramática, que

reconoce la estructura de una cadena de componentes léxicos.

Page 33: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

33

Objetivos del analizador sintáctico (AS)

El AS es la parte principal de un compilador. Las funciones principales de AS son:

• Comprobar si el programa es sintácticamente correcto.

• Generar las estructuras de datos (árboles sintácticos u otras estructuras) que

representan el programa y sirven para el analizador semántico y el generador de

código.

• En el caso de compilación dirigida por sintaxis -- llamar al analizador semántico y al

generador de código.

Otras:

• Reaccionar frente a los errores e intentar acotar la propagación de los errores (intentar

evitar que un error produzca muchos mensajes de error).

• Hacer los siguientes pasos del compilador más independientes de la sintaxis del lenguaje.

Comportamiento del analizador sintáctico

El proceso de análisis sintáctico y la ejecución son ahora dos pasos completamente separados,

no se procederá a la ejecución del código de cualquier archivo hasta que éste en su totalidad,

así como todo el código requerido se haya analizado completa y satisfactoriamente.

Uno de los nuevos requisitos introducidos con esta separación es que todos los archivos

requeridos y de inclusión tienen que ser sintácticamente completos ahora. Ya no es permitida

la separación de diferentes segmentos de una estructura de control a través de varios archivos.

Esto quiere decir que ahora no puede iniciar un ciclo for o while, una sentencia if o un bloque

switch en un archivo, y tener el final del ciclo, sentencias else, endif, case o break en un archivo

diferente.

Aun es perfectamente legal incluir código adicional al interior de ciclos u otras estructuras de

control, únicamente las palabras claves de control y los corchetes correspondientes {___}

tienen que estar en la misma unidad de compilación (archivo o cadena procesada por eval ()).

Page 34: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

34

Esto no debe generar una repercusión significativa ya que separar el código de esta manera

debe ser considerado como muy mal estilo, en cualquier caso.

Algo más que ya no es posible, aunque es rara veces visto en código PHP 3, es devolver

valores desde un archivo requerido devolver un valor desde un archivo de inclusión es posible

aun.

Un analizador sintáctico, tal y como se ha enfocado en esta librería es una clase que recibe lo

siguiente:

1. Una clase donde se almacenarán los atributos de cada símbolo.

2. Una gramática libre de contexto donde el símbolo de inicio solo produce una variable.

3. Una función de síntesis para cada producción, encargada de sintetizar los atributos

necesarios.

4. Para cada producción una opcional regla para deshacer ambigüedades.

5. Una instancia de una clase de léxico que derive de la clase abstracta de léxico básico.

6. Una opcional función de error para manejarlos y recuperarse en la medida de lo posible.

El analizador ira leyendo del léxico y aplicando las reducciones necesarias (llamando a las

funciones de síntesis proporcionadas). El resultado habitual es un árbol sintáctico construido de

manera ascendente. Cada nodo de ese árbol es del tipo genérico que se le pasa como

parámetro al parser mencionado en la lista anterior.

Función del análisis sintáctico.

Analizar sintácticamente una tira o cadena de tokens no es más que encontrar para ella el árbol

sintáctico o de derivación que tiene como raíz el axioma de la gramática, y como nodos

terminales la sucesión ordenada de símbolos que componen la cadena analizada.

En caso de no existir este árbol sintáctico, la cadena no pertenecerá al lenguaje, y el analizador

sintáctico ha de emitir el correspondiente mensaje de error.

Page 35: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

35

Existen dos formas de analizar sintácticamente una cadena:

� Análisis descendente : Partiendo del axioma inicial de la gramática se va descendiendo

utilizando las derivaciones izquierdas, hasta llegar a construir la cadena analizada.

� Análisis ascendente : Se va construyendo el árbol desde sus nodos terminales. Es decir, se

construye desde los símbolos de la cadena hasta llegar al axioma de la gramática. En este

caso, se emplean normalmente las derivaciones más a la derecha hasta la localización de la

raíz.

Los principales métodos de análisis sintáctico son:

� Análisis descendente:

• Análisis descendente con retroceso.

• Análisis de gramáticas LL .

� Análisis ascendente:

• Análisis ascendente con retroceso.

• Análisis de gramáticas de precedencia simple.

• Análisis de gramáticas de precedencia generalizada.

• Análisis de gramáticas por el método mixto.

• Análisis de gramáticas de precedencia de operador.

• Análisis de gramáticas LR.

Los análisis con retroceso se basan en la prueba sistemática de todas las alternativas posibles,

dando marcha atrás tan pronto como se detecte que el camino seguido es erróneo.

Pueden usarse para cualquier gramática de contexto libre, aunque tienen tres grandes

inconvenientes:

Primero, emplean mucho más tiempo para el análisis que los demás analizadores,

dependiendo éste incluso de la ordenación de las reglas gramaticales.

Page 36: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

36

Segundo, no dan un buen diagnóstico de los errores que encuentran; Tercero, complican la

generación de código cuando ésta se realiza al par que el análisis sintáctico.

Los métodos más eficientes de análisis (tanto ascendente como descendente) no funcionan

para todas las gramáticas de contexto libre, sino sólo para las gramáticas que cumplen unas

determinadas condiciones.

Afortunadamente, en la mayoría de los casos, pueden encontrase para los lenguajes de

programación gramáticas de tipo LL o LR que los generen.

Para representar el árbol sintáctico que conduce hasta una cadena se asigna a cada regla de

la gramática un número. Se define el parse como la secuencia ordenada de números (de

reglas) aplicadas para construir dicho árbol.

Hay dos tipos de parse, que son:

• El parse-izquierdo : Son los números de las reglas de derivación izquierda utilizadas para

generar la cadena a partir del axioma, por tanto correspondiente a un análisis descendente.

• El parse-derecho : Son los números de las reglas de derivación derecha utilizadas para

generar la cadena a partir del axioma, en orden inverso. El tomar el orden inverso viene

condicionado por ser el análisis ascendente el que normalmente utiliza las reglas de derivación

derecha, con lo que el orden en el que aparecen al realizar el análisis es invertido.

Simultáneamente a la fase de análisis sintáctico, además de reconocer las secuencias de

tokens, y analizar su estructura, pueden realizarse una serie de tareas adicionales, como:

• Recopilar información de los distintos tokens y almacenarla en la tabla de símbolos.

• Realizar algún tipo de análisis semántico, tal como la comprobación de tipos.

• Generar código intermedio.

• Avisar de los errores que se detecten.

Análisis Sintáctico (LL1):

Page 37: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

37

Definición: Aquel en el que se agrupan todas las clausuras con idéntica parte izquierda. Es

decir todas las clausuras del tipo:

A -> B C D

A -> E F G

......

A ->X Y Z

Si no existen dos clausuras con idéntica parte izquierda que pueden empezar con un mismo

símbolo terminal, y no existe recursividad a la izquierda, el lenguaje es LL1.

Para Aplicar el Algoritmo LL (1) se procede de la siguiente forma:

1. Se debe eliminar de la gramática recursividad por la izquierda (en caso de que la tuviera).

2. Realizar factorización a la izquierda en caso de que la gramática lo requiera.

3. Determinar el conjunto de primeros y siguientes.

4. Construir la tabla de Análisis LL(1).

5. Validar una cadena.

Para comprender cada uno de los pasos del algoritmo descrito anteriormente tomaremos como

ejemplo la siguiente gramática:

Ejercicio resuelto en clases: S -> if E then S | while E do S | I := E I -> * I | id A A -> Ɛ | [E] E -> I | cte

REGLAS PARA DETERMINAR LOS PRIMEROS

1. Si X es terminal, entonces PRIMERO(X) es {X}.

2. Si X -> £ es una producción, entonces agregar £ al PRIMERO(X).

3. Si X es no terminal y X -> Y1 Y2 ... Yk es una producción, entonces agregar µ en

PRIMERO(X) si para algún i, µ está en PRIMERO(Yi), y £ está en todos los

PRIMERO(Y1),...,PRIMERO(Yi-1).

Page 38: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

38

PRIMERO( S ) = { if, while } U P( I ) =>{ if, while,*id } PRIMERO( I ) = { *, id } => { *,id } PRIMERO( A ) = { Ɛ, [ } => { Ɛ,[ } PRIMERO( E ) = { cte } U P( I ) => {*, id , cte }

Reglas para obtener el conjunto de los siguientes:

La idea es que los siguientes de un no terminal (A) son aquellos terminales que pueden

llegar a aparecer detrás de (A) en alguna forma sentencial. En el caso del símbolo inicial

y de aquellos que pueden aparecer al inicial de una forma sentencial, además incluimos el

fin de la entrada.

Para encontrar los siguientes de cada no terminal, podemos emplear las siguientes ecuaciones:

1. Como inicialización, asociamos a todos los no terminales distintos del inicial el conjunto

vacío y al símbolo inicial le asociamos el conjunto f ($), para que se cumpla la ecuación

(1).

2. Para cumplir con la ecuación (2), añadimos al conjunto de siguientes de cada no

terminal (A) los primeros de los β tales que α(A) β constituye la parte derecha de alguna

regla.

Page 39: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

39

3. Recorremos ordenadamente las reglas aplicando la ecuación (3). Para ello, por cada

regla (A)� α(B) β con β anulable, añadimos los siguientes de (A) a los de (B) Este paso

tendremos que repetirlo hasta que no cambie ninguno de los conjuntos de siguientes en

un recorrido completo de las reglas.

Vamos a aplicar el algoritmo a nuestro ejemplo. En primer lugar, inicializamos los siguientes al conjunto vacio salvo para el símbolo inicial:

Después, añadimos los símbolos que aparecen inmediatamente después de cada no terminal:

Ahora aplicamos ordenadamente la ecuación (3) hasta que no haya modificaciones. Así

Con la regla (E)�(I), aumentamos los siguientes de (I):

Page 40: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

40

A partir de (I)�id(A), aumentamos los siguientes de (A):

Si volvemos a repasar las reglas, vemos que los siguientes no varían, así que podemos dar por

terminado el proceso.

SLR:

La mayor inconveniencia de los analizadores LL1 es el conjunto bastante limitado de los

lenguajes LL1. Un conjunto bastante más amplio, que incluye casi todos los lenguajes de

programación, es el SLR.

Definición: Un lenguaje de tipo SLR si existe un analizador SLR que permita analizarlo.

Comentario: Al igual que el analizador SLR, éste se construye siguiendo un algoritmo, por lo

tanto no podemos hablar de tautología.

Un algoritmo para construir SLR, orientado a “ejecutarse” a mano se encuentra descrito en el

apartado 2.

¿Qué hace?

En general, un analizador sintáctico es un autómata a pila. El analizador sintáctico:

Inicializa el compilador y AS en particular.

Page 41: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

41

• Para cada símbolo de entrada llama al analizador morfológico y el AM proporciona el

siguiente símbolo de entrada.

• Analizando el símbolo, la pila y el estado del autómata, el AS produce las estructuras

necesarias para la siguiente etapa y en el caso de compilación dirigida por la sintaxis --

invoca llamadas directas al analizador semántico y al generador de código.

• Escribe mensajes de errores y trata de limitar el efecto de estos errores.

Elección de algoritmos. Consideración de las diferencias, ventajas y desventajas.

Plan de desarrollo

Etapas:

1. Elegir el tipo del analizador sintáctico.

2. Trasformar el lenguaje al tipo correspondiente. Calcular las tablas correspondientes.

3. Escribir el programa del analizador sintáctico sin acciones ni producción de código.

4. Añadir al analizador atributos y acciones (en la parte del analizador sintáctico solo la

producción de la salida, tal como se explica en los requisitos de prueba).

5. Añadir al analizador generador de código.

Propiamente hablando, sólo el segundo y el tercer punto forman el analizador sintáctico por sí.

El resto es analizador semántico y el generador de código. En todos los modos se requiere una

vista general al definir como vamos a realizar el analizador sintáctico.

ANALIZADOR SEMÁNTICO.

Introducción.

Es aquel que se ocupa de analizar si la sentencia de caracteres tiene algún significado. Se

pueden encontrar sentencias que son sintácticamente correctas pero que no se pueden

Page 42: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

42

ejecutar porque carecen de sentido. El análisis semántico se hace a la par que el análisis

sintáctico introduciendo en éste unas rutinas semánticas.

El análisis semántico utiliza como entrada el árbol sintáctico detectado por el análisis sintáctico

para comprobar restricciones de tipo y otras limitaciones semánticas y preparar la generación

de código. El instrumento más utilizado para conseguirlo es la gramática de atributos.

Gramáticas sem ánticas

Una gramática semántica es una gramática libre de contexto en la cual la elección de símbolos

no terminales y de reglas de producción son regidos tanto por funciones sintácticas como

semánticas. Muchos símbolos no terminales representan elementos semánticos; esto es, que

su posición depende también del tema que se este tratando. Una gramática semántica puede

ser usada por un sistema de análisis gramatical de la misma forma en que pudiera ser usada

una gramática estrictamente sintáctica. Las principales ventajas son las siguientes:

• Cuando el análisis gramatical esta completo, el resultado puede ser usado

inmediatamente sin otra etapa de proceso que pudiera ser requerida si una

interpretación semántica no hubiese sido realizada durante el análisis.

• Muchas ambigüedades que pueden surgir durante un análisis gramatical

estrictamente sintáctico son evitadas al tomar en cuenta criterios semánticos

durante el análisis.

Sin embargo, existen también algunas desventajas en el uso de gramáticas

semánticas.

• El número de reglas requeridas pueden llegar a ser demasiado grande ya que

faltaría muchas generalizaciones sintácticas.

• Debido a lo anterior, el análisis gramatical puede ser muy lento y ocupar mucho

espacio en memoria.

Se puede observar que las gramáticas semánticas pueden ser útiles para producir interfaces de

subconjuntos de lenguaje natural.

Page 43: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

43

Gramática con atributos.

Una gramática con atributos es una gramática de contexto libre cuyos símbolos pueden

tener asociados atributos y las producciones pueden tener asociadas reglas de evaluación

de los atributos.

• Cada símbolo puede tener asociado un número finito de atributos.

• Cada producción puede tener asociada un número finito de reglas de evaluación

de los atributos.

• Los valores de los atributos deberán estar asociados con un dominio de valores.

• Por lo general las gramáticas con atributos se escriben en forma tabular, con cada

regla Gramatical enumerada con el conjunto de ecuaciones de atributo o reglas

semánticas asociadas con esa regla de manera siguiente:

Regla Gramatical Regla A sociada

Regla 1 Ecuaciones de atributo asociadas

… ….

Regla n Ecuaciones de atributo asociadas

Representación de las gramáticas con atributos

Atributos Heredados y Atributos Sintetizados

Atributos Sintetizados Atributos Heredados

Si b está asociado con el símbolo no terminal A

Si b está asociado con el símbolo no terminal α

Page 44: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

44

Evaluación: Las reglas de evaluación de los atributos sintetizados se realizan cuando se aplican reducciones en el análisis sintáctico.

Evaluación: La Evaluación de un atributo heredado depende de los atributos asociados con los símbolos precedentes en la derivación.

Requisitos para la evaluación:

Las reglas de evaluación de los atributos deben definirse en función de los atributos asociados con los atributos gramaticales a la derecha.

Realizar un análisis ascendente

Requisitos para la evaluación:

Realizar un análisis descendente.

Diferencias entre Atributos Sintetizados y Atributos Heredados

Ejemplo:

Gramática de Atributos Sintetizados

exp � exp + term | exp – term | term

term � term * factor | factor

factor � (exp) | numero

Se pide obtener la gramática con atributos, dada la expresión: (34-3)*42 y expresar la

semántica de su valor en un árbol de análisis gramatical.

Empezamos obteniendo la derivación por la izquierda para la expresión dada:

exp � term� term * factor �factor * factor � (exp) * facto � (exp – term ) * factor

� (term – term ) * factor � (factor – term) * factor� (numero – term ) * factor

� (numero –factor) * factor � (numero –numero) * factor � ( numero –numero) *numero

Luego de ello podemos construir el árbol de análisis gramatical para (34-3)*42, mostrando

cálculos del atributo val.

Page 45: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

45

Árbol de análisis gramatical

Nótese que el atributo principal de una exp o term o factor es su valor numérico (val) el

cual va ha formar parte de las ecuaciones para las reglas semánticas que se muestran a

continuación:

Page 46: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

46

Regla Gramatical Regla Semántica

exp1 � exp2 + term exp1. val= exp2.val + term.val

exp1 � exp2 – term exp1.val = exp2.val – term.val

exp � term exp.val = term.val

term1 � term2 * factor term1.val = term2.val * factor.val

term � factor term.val = factor.val

factor � (exp) factor.val = (exp) .val

factor � numero factor.val = numero.val

Ejemplo de Gramática con atributos heredados

Realizar un análisis descendente, dada la siguiente gramática y las reglas de evaluación.

Page 47: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

47

BIBLIOGRAFÍA

FUENTES PRIMARIAS:

• ALFONSECA M. Manuel, ”Compiladores e Interpretes”. Editorial PEARSON

EDUCACION 2006.

• ALFRED V, Aho; RAVI, Sethi; JFFREY D, Ullman “Compiladores Principios, técnicas

y herramientas”. Addison-Wesley. Iberoamericana 1990.

• KELLEY, Dean. “Teoria de Automatas y Lenguajes Formales”. Editorial PRENTICE

HALL 1995.

• KENET LOUDEN Construcción de Compiladores Principios y Práctica._ UNED 2004.

FUENTES SECUNDARIAS:

• AHO, Alfred y ULLMAN, Jeffrey.2005, Compiladores Principios, Técnicas y

Herramientas, Edición Electrónica

• ANTONIO, Reyes Oscar; POSADAS, Martínez Wendi; RODRÍGUEZ, Alemán

Magdalena; ROSAS, Gómez Lizzeth Del Carmen; ROSAS, Hernández José

Alberto. LENGUAJES Y AUTÓMATAS CS51. [en linea]. Colombia Bogota,

[http://mx.geocities.com/amigos2365/anteproyecto.html], [Consulta: 15 Octubre

2008].

• BONILLA,Oscar.[http://74.125.45.104/search?q=cache:A9YLY0RcmuUJ:oscarbonil

la.com/courses/compilrs/materials/06_Analisis_Sintactico.ppt+analizador+sint%C3

%A1ctico&hl=es&ct=clnk&cd=11]

• Ceballos, Carmona Miguel Ángel. 2002. Compiladores. [en línea]. Irapuato, Gto,

[http://www.monografias.com/trabajos11/compil/compil2.shtml] ,[Consulta: 14

Octubre 2008].

• GÁLVEZ ROJAS, Sergio Y MORA MATA, Miguel Ángel. .2005, Java a Tope:

Traductores y Compiladores con Lex/Yacc, JFlex/Cup y JavaCC, Edición

Electrónica

•Universidad de Huelva, Procesadores de lenguaje, http://www.uhu.es/470004004/docs/tema4_ejercicios.pdf

Page 48: Materia unidad compiladores

Elaborado por: Beatriz Pasaca| Noveno “A”

48

•Universidad Nacional de Río Cuarto, Departamento de Computación, Autómatas y Lenguajes, Practicó 6 - Parsing y Gramáticas LR 2009, http://dc.exa.unrc.edu.ar/nuevodc/materias/automatas/reposit/1173382040/ayl_prac_6.pdf