Вы находитесь на странице: 1из 48

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 1
• Depurador
• Ensamblador

Elaborado por: Beatriz Pasaca| Noveno “A”


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

2
Gcódigo máquina

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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.

Elaborado por: Beatriz Pasaca| Noveno “A”


-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
5
método) ha sido previamente declarado y además, si es visible desde el ámbito actual.

Elaborado por: Beatriz Pasaca| Noveno “A”


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 6
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

Elaborado por: Beatriz Pasaca| Noveno “A”


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 7
varios lenguajes de programación distintos y que el mismo Front End que sirve para analizar el

Elaborado por: Beatriz Pasaca| Noveno “A”


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:

Elaborado por: Beatriz Pasaca| Noveno “A”


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, 9
etiquetas. Por ejemplo: posición, velocidad, tiempo.
• Operadores: = * + - / == > < & ! = .

Elaborado por: Beatriz Pasaca| Noveno “A”


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

10
Componentes Toquen, Patrones, Lexemas
 Token: “nombre “que se da a cada componente léxico.

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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: expresiones 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 ?
12

r? indica que la expresión r puede aparecer o no. En el caso de que aparezca solo lo haría una
vez.
Elaborado por: Beatriz Pasaca| Noveno “A”
Aspectos prácticos en la implementación de un analizador 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ímbolos

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

Gestión del búfer de entrada

Elaborado por: Beatriz Pasaca| Noveno “A”


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

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.
Elaborado por: Beatriz Pasaca| Noveno “A”
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 15

Desventaja: hay que hacerlo todo

Elaborado por: Beatriz Pasaca| Noveno “A”


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


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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

17

Elaborado por: Beatriz Pasaca| Noveno “A”


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.


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


Elaborado por: Beatriz Pasaca| Noveno “A”
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

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

Elaborado por: Beatriz Pasaca| Noveno “A”


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:

20

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Nótese que el conjunto de gramáticas correspondiente a los lenguajes recursivos no es un


miembro de la jerarquía.

Elaborado por: Beatriz Pasaca| Noveno “A”


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,
22
seguido por algún símbolo y seguida por una cadena de no terminales y terminales.

Notación BNF
Elaborado por: Beatriz Pasaca| Noveno “A”
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 • Poseen variables A, B, C


seguido de un no terminal, o un solo terminal, y • Poseen cadenas de caracteres.
en el lado izquierdo existe un nodo no terminal. • Genera lenguaje de tipo 2 o
• Poseen constantes a, b, c. lenguaje independiente de
• Genera lenguaje de tipo 3 o lenguaje regular. contexto.
• Su importancia reside en que los lenguajes • No tiene restricciones con respecto
generados por ellas son exactamente aquellos a sus reglas de escritura, su lado
que reconocen los autómatas finitos. izquierdo de cada regla sea un no
• Existe exactamente un no terminal terminal: S →aMa.
• La parte de la cadena no generada siempre • Se utilizan cuando un autómata
aparece al final. finito no reconoce ciertos
• Proporciona un plantilla o patrón para las lenguajes.
cadenas de lenguajes. • Se escriben frecuentemente
• Si en cada regla α → β cumple: utilizando una notación conocida
con BNF (Backus-Naur).
α ϵ Vn, y • Para determinar si están bien
escritas se utilizan los árboles de
análisis sintáctico.
β ϵ Vt, p
• Cualquier gramática libre de
contexto que no contenga la 23
β ϵ (Vt * Vn) cadena vacía puede ser generado:
L – {λ} ó L.
• El no terminal puede ser uno o
Elaborado por: Beatriz Pasaca| Noveno “A”
varios que puede tomar.
• Proporcionar un mecanismo simple
y exacto para describir los métodos
- Gramáticas Lineales - A::=α por los cuales las frases (lenguaje
natural) son construidas de
por la izquierda → - A::=Va bloques más pequeñas.
• Si cada regla: α→β cumple α ϵ Vn.
- S::=λ • Cumple los requerimientos para
ser una gramática tipo 1.
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.

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 b2aba2ba → 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 24
 S → AB → abB → AbbB → Abbb → aAbbb →aabbb

Elaborado por: Beatriz Pasaca| Noveno “A”


Á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


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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 26

4. S → SS |CA| λA
S → SS | C A | A
Elaborado por: Beatriz Pasaca| Noveno “A”
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
27
S → SS | bAA | aSS | a

4. Eliminar los símbolos no alcanzables

Elaborado por: Beatriz Pasaca| Noveno “A”


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
28
T→T+i|i
Q→E|E+F|T|S
S→i

Elaborado por: Beatriz Pasaca| Noveno “A”


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:


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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 30
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

Elaborado por: Beatriz Pasaca| Noveno “A”


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 | λ
31
B → Aa
E → bED
D → ccc

Elaborado por: Beatriz Pasaca| Noveno “A”


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.

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

Elaborado por: Beatriz Pasaca| Noveno “A”


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 ()). 33

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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:
35
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.

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Análisis Sintáctico (LL1):

Elaborado por: Beatriz Pasaca| Noveno “A”


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 37

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


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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í

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

Elaborado por: Beatriz Pasaca| Noveno “A”


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?

40
En general, un analizador sintáctico es un autómata a pila. El analizador sintáctico:
Inicializa el compilador y AS en particular.

Elaborado por: Beatriz Pasaca| Noveno “A”


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

Elaborado por: Beatriz Pasaca| Noveno “A”


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.

42
Se puede observar que las gramáticas semánticas pueden ser útiles para producir interfaces de
subconjuntos de lenguaje natural.

Elaborado por: Beatriz Pasaca| Noveno “A”


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 Asociada

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 Si b está asociado con el 43


terminal A símbolo no terminal α

Elaborado por: Beatriz Pasaca| Noveno “A”


Evaluación: Las reglas de evaluación de Evaluación: La Evaluación de
los atributos sintetizados se realizan un atributo heredado depende
cuando se aplican reducciones en el de los atributos asociados con
análisis sintáctico. los símbolos precedentes en la
derivación.

Requisitos para la evaluación: Requisitos para la evaluación:

Las reglas de evaluación de los Realizar un análisis


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

Realizar un análisis ascendente

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

Elaborado por: Beatriz Pasaca| Noveno “A”


Á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:

45

Elaborado por: Beatriz Pasaca| Noveno “A”


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.

46

Elaborado por: Beatriz Pasaca| Noveno “A”


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 47

Elaborado por: Beatriz Pasaca| Noveno “A”


•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

48

Elaborado por: Beatriz Pasaca| Noveno “A”

Вам также может понравиться