II26Procesadoresdelenguaje 4 Ingenier´ıaInform´atica o

13  Download (0)

Full text

(1)

II26 Procesadores de lenguaje

Estructura de los compiladores e int´ erpretes Esquema del tema

1. Introducci´ on

2. Etapas del proceso de traducci´ on 3. La interpretaci´ on

4. La arquitectura real de compiladores e int´ erpretes 5. Algunos conceptos de teor´ıa de lenguajes formales 6. Resumen del tema

1. Introducci´ on

Tanto los compiladores como los int´ erpretes son programas de gran complejidad. Afortunada- mente, se sabe suficiente acerca de c´ omo estructurarlos y hay suficientes herramientas formales para que la complejidad se reduzca a niveles razonables. En este tema veremos en qu´ e fases se divide un compilador o un int´ erprete. Veremos tambi´ en qu´ e tienen en com´ un y c´ omo difieren entre s´ı compiladores e int´ erpretes.

La principal herramienta formal para el desarrollo de compiladores e int´ erpretes es el concepto de lenguaje formal. Es necesario tener cierta familiaridad con los fundamentos de esta teor´ıa y, en especial, con los lenguajes regulares e incontextuales. En este tema haremos un ligero repaso a los aspectos m´ as relevantes para la asignatura.

2. Etapas del proceso de traducci´ on

Podemos modelar el proceso de traducci´ on entre dos lenguajes como el resultado de dos etapas.

En la primera etapa se analiza la entrada para averiguar qu´ e es lo que se intenta comunicar. Esto es lo que se conoce como an´ alisis. El fruto de esta etapa es una representaci´ on de la entrada que permite que la siguiente etapa se desarrolle con facilidad. La segunda etapa, la s´ıntesis, toma la representaci´ on obtenida en el an´ alisis y la transforma en su equivalente en el lenguaje destino.

En el caso de la interpretaci´ on, se utiliza la representaci´ on intermedia para obtener los resul- tados deseados.

2.1. An´ alisis

El objetivo de esta etapa es obtener una representaci´ on de la entrada que nos permita realizar la s´ıntesis o la interpretaci´ on con comodidad. La representaci´ on que nosotros utilizaremos es la que se llama ´ arbol de sintaxis abstracta. Un ejemplo ser´ıa la traducci´ on siguiente:

valor= valor+inc; /* Actualizamos */ é idvalor

idvalor idinc

suma

asignaci´ on

(2)

de hacerlo ser´ıa comenzando por describir cu´ ales son las unidades elementales tales como identi- ficadores, palabras reservadas, operadores, etc. que se encuentran en la entrada. Despu´ es podr´ıas describir c´ omo se pueden combinar esas unidades en estructuras mayores tales como expresiones, asignaciones, bucles y dem´ as. Finalmente, especificar´ıas una serie de normas que deben cumplirse para que el programa, adem´ as de estar “bien escrito”, tenga significado. Estas normas se refieren a aspectos tales como que las variables deben declararse o las reglas que se siguen para decidir los tipos de las expresiones.

Las tres fases que hemos mencionado tienen su reflejo en las tres fases en que se divide el an´ alisis:

An´ alisis l´ exico: se encarga de la divisi´ on de la entrada en componentes l´ exicos.

An´ alisis sint´ actico: se encarga de encontrar las estructuras presentes en la entrada.

An´ alisis sem´ antico: se encarga de comprobar que se cumplen las restricciones sem´ anticas del lenguaje.

2.1.1. An´ alisis l´ exico

En esta fase se analiza la entrada car´ acter a car´ acter y se divide en una serie de unidades elementales: los componentes l´ exicos. Cada uno de estos componentes se clasifica con su categor´ıa y puede recibir un etiquetado con cierta informaci´ on (por ejemplo un entero tendr´ıa una etiqueta indicando su valor). El criterio que se emplea para clasificar cada componente es su pertenencia o no a un lenguaje (generalmente regular). Esta fase adem´ as se encarga de filtrar elementos tales como los blancos y los comentarios.

En nuestro ejemplo, tendr´ıamos como categor´ıas los identificadores, la suma, la asignaci´ on y el punto y coma. Podemos suponer que los identificadores son secuencias de letras y d´ıgitos que comienzan por una letra. Adem´ as, hay otros componentes que “se filtran” o, m´ as formalmente, son omitidos: los blancos y los comentarios. Teniendo en cuenta esto, nuestro analizador l´ exico ve:

v a l o r = v a l o r + i n c ; / * A c t u a l i z

a m o s * /

Y lo que pasa al sint´ actico es:

idvalor asig idvalor suma idinc pyc

Donde hemos asumido que id es el componente l´ exico que representa los identificadores; asig repre- senta la asignaci´ on; suma, las sumas y pyc, el punto y coma. Como puedes ver, han desaparecido tanto los blancos como los comentarios.

2.1.2. An´ alisis sint´ actico

Partiendo de lo que ha recibido del analizador l´ exico, la tarea del analizador sint´ actico consiste en ir descubriendo las estructuras presentes en el c´ odigo de acuerdo con una gram´ atica incontextual.

A partir de las estructuras que ha encontrado, el analizador sint´ actico construye un ´ arbol sint´ actico (que no hay que confundir con el ´ arbol de sintaxis abstracta comentado antes).

Para especificar las construcciones que se permiten, se suelen emplear gram´ aticas incontex- tuales, que veremos luego. En nuestro caso podemos pensar que las reglas que se siguen son que una asignaci´ on se compone de un identificador, seguido de un s´ımbolo de asignaci´ on, segui- do de una expresi´ on y de un punto y coma. Esto se escribe en la gram´ atica en forma de regla:

hAsigi → id asig hExpri pyc. An´ alogamente, podemos decir que una expresi´ on es bien un identifica- dor, bien la suma de dos expresiones. En reglas:

hExpri → id

hExpri → hExpri suma hExpri

(3)

El ´ arbol sint´ actico nos permite expresar c´ omo se puede “fabricar” (formalmente, derivar) la entrada a partir de las reglas. En nuestro caso, el ´ arbol tiene un aspecto similar a:

hExpri hExpri hExpri

hAsigi

idvalor asig idvalor suma idinc pyc

Es interesante darse cuenta de que, tanto en las reglas como en la construcci´ on del ´ arbol, se hace caso omiso de los posibles atributos de los componentes l´ exicos; ´ unicamente se tiene en cuenta su categor´ıa.

2.1.3. An´ alisis sem´ antico

La ´ ultima fase del an´ alisis, el an´ alisis sem´ antico, toma como entrada el ´ arbol sint´ actico y comprueba si, adem´ as de las restricciones sint´ acticas, se cumplen otras restricciones impuestas por el lenguaje y que no pueden ser comprobadas mediante una gram´ atica incontextual. Algunos ejemplos de estas restricciones son la necesidad de declarar las variables antes de usarlas, las reglas de tipos o la coincidencia entre los par´ ametros de las funciones en las definiciones y las llamadas.

Como salida de esta fase, se obtiene una representaci´ on sem´ antica, por ejemplo el ´ arbol de sintaxis abstracta comentado antes. Adem´ as, se ha comprobado que tanto valor como inc est´ an declaradas y con tipos compatibles.

2.2. S´ıntesis

Una vez analizado el programa de entrada, es necesario generar c´ odigo, a ser posible eficien- te, para la m´ aquina objetivo. Supongamos que tenemos L lenguajes fuente y queremos escribir compiladores para M m´ aquinas distintas. La aproximaci´ on inmediata, escribir un compilador para cada par lenguaje-m´ aquina, supone escribir L × M compiladores. Sin embargo, si los lenguajes son razonablemente parecidos (como Pascal y C), existe una aproximaci´ on mejor: escribir L traducto- res desde los lenguajes fuente a un lenguaje intermedio y despu´ es escribir M traductores de este lenguaje intermedio a los lenguajes m´ aquina correspondientes:

Java

ML

Pascal

C

C++

Sparc

PowerPC

Pentium

Alpha

Java

ML

Pascal

C

C++

Sparc

PowerPC

Pentium

Alpha L. interm.

Esta aproximaci´ on tiene diversas ventajas. La m´ as obvia es la reducci´ on del n´ umero de tra-

ductores que se necesitar´ an. Adem´ as, si se quiere a˜ nadir un nuevo lenguaje a nuestra colecci´ on,

no es necesario crear M compiladores para ´ el, basta con un traductor al lenguaje intermedio. Esto

permite que se desarrollen nuevos lenguajes con comodidad. M´ as importante, si aparece una nueva

arquitectura, basta con desarrollar un traductor del lenguaje intermedio a esta nueva m´ aquina.

(4)

Otro aspecto de gran importancia es que la representaci´ on intermedia suele elegirse de modo que no necesite el gran nivel de detalle que exige el c´ odigo m´ aquina y permita abstraer problemas como el n´ umero limitado de registros disponibles o la gran variedad de instrucciones de m´ aquina donde elegir. Esto simplifica notablemente la generaci´ on de c´ odigo desde el AST.

Desgraciadamente, esta aproximaci´ on no es gratis ni universal. Encontrar un lenguaje interme- dio que sea adecuado para todas las arquitecturas y lenguajes no es tarea sencilla y probablemente no sea posible. En la pr´ actica se emplea para lenguajes similares y m´ aquinas destino que com- partan una serie de caracter´ısticas comunes. Por otro lado, determinadas caracter´ısticas del par (lenguaje fuente, m´ aquina destino) pueden no aprovecharse. Por ejemplo, si sabemos que la m´ a- quina destino tiene instrucciones especializadas para saltos mediante una tabla, el c´ odigo generado para las estructuras switch-case deber´ıa reflejarlo. Esto no es posible si el lenguaje intermedio no recoge este tipo de construcciones.

Pese a todo, las ventajas superan a los inconvenientes y la generaci´ on de c´ odigo se divide habitualmente en dos etapas:

Generaci´ on de c´ odigo intermedio.

Generaci´ on de c´ odigo objeto: se traduce el c´ odigo intermedio a c´ odigo de m´ aquina.

2.2.1. Generaci´ on de c´ odigo intermedio

En esta etapa se traduce la entrada a una representaci´ on independiente de la m´ aquina pero f´ acilmente traducible a lenguaje ensamblador. Esta representaci´ on puede tomar diversas formas que pueden entenderse como visiones idealizadas del lenguaje ensamblador de una m´ aquina virtual.

Algunas de las representaciones m´ as comunes son:

Arboles de representaci´ ´ on intermedia (distintos de los ´ arboles de sintaxis abstracta), c´ odigo de tres direcciones,

c´ odigo de dos direcciones, c´ odigo de pila,

representaciones en forma de grafo, mixtas, etc. . .

En nuestro caso, usando la m´ aquina virtual de las pr´ acticas, la sentencia valor= valor+inc;

puede traducirse por algo similar a:

lw $r0, -2($fp) # Carga valor en $r0 lw $r1, -1($fp) # Carga inc en $r1 add $r0, $r0, $r1 # Hace la suma

sw $r0, -2($fp) # Guarda el resultado en valor 2.2.2. Generaci´ on de c´ odigo objeto

Una vez obtenido el c´ odigo intermedio, es necesario generar el c´ odigo objeto. Lo habitual es que no se genere el c´ odigo objeto directamente sino que se genere c´ odigo en ensamblador y despu´ es se utilice un ensamblador. De cualquier forma, esta fase es totalmente dependiente de la arquitectura concreta para la que se est´ e desarrollando el compilador. En particular, hay que enfrentarse a problemas como:

Selecci´ on de instrucciones teniendo en cuenta su eficiencia.

Elecci´ on de los modos de direccionamiento adecuados.

Utilizaci´ on eficiente de los registros.

Empleo eficiente de la cach´ e.

(5)

Otros. . .

Las instrucciones que genera gcc para nuestro ejemplo son:

movl -8(%ebp), %edx leal -4(%ebp), %eax addl %edx, (%eax)

Como ves, se tiene en cuenta el tama˜ no de las variables (por eso se emplea −4 y −8 en lugar de

−2 y −1), se utilizan registros de la m´ aquina concreta y se emplean instrucciones especiales como leal para aprovechar mejor el procesador.

2.2.3. Optimizaci´ on

Tanto a la hora de generar c´ odigo intermedio como c´ odigo objeto es habitual encontrarse con que el resultado de la traducci´ on es muy ineficiente. Esto es debido a que la traducci´ on se realiza de manera local, lo cual provoca la aparici´ on de c´ odigo redundante. Por ejemplo, la sentencia a[i]=a[i]+1 genera el siguiente c´ odigo intermedio:

addi $r0, $zero, 0 # Direcci´ on de a lw $r1, 3($zero) # Acceso a i

add $r0, $r0, $r1 # Sumamos i a la direcci´ on de a lw $r0, 0($r0) # Valor de a[i]

addi $r1, $zero, 1 # Valor entero (1) add $r0, $r0, $r1 # Hacemos la suma addi $r1, $zero, 0 # Direcci´ on de a lw $r2, 3($zero) # Acceso a i

add $r1, $r1, $r2 # Sumamos i a la direcci´ on de a sw $r0, 0($r1) # Guardamos el resultado

Sin embargo, es f´ acil darse cuenta de que no hace falta calcular dos veces la direcci´ on de a[i], con lo que se pueden ahorrar, al menos, tres instrucciones.

Ejercicio 1

Escribe el c´ odigo de menor longitud que se te ocurra para el ejemplo anterior. No puedes utilizar instrucciones distintas de las mostradas.

En otras ocasiones, es posible utilizar instrucciones especializadas para mejorar la velocidad.

Por ejemplo, si la instrucci´ on anterior a valor= valor+inc; es inc=1;, gcc -O genera

incl %ebx

Por esto, es habitual incluir m´ odulos encargados tanto de la optimizaci´ on del c´ odigo intermedio como del c´ odigo objeto.

2.3. Otros m´ odulos del compilador

Aunque en principio ser´ıa posible escribir el compilador como la concatenaci´ on de las distintas fases que se han descrito, existen dos m´ odulos que no forman parte de esta secuencia pero tienen un papel fundamental para el proceso de compilaci´ on: la tabla de s´ımbolos y el m´ odulo de gesti´ on de errores.

2.3.1. La tabla de s´ımbolos

A lo largo del proceso de an´ alisis se va generando gran cantidad de informaci´ on que se puede

considerar ligada a los objetos que se van descubriendo en el programa: variables, constantes,

funciones, etc. El acceso a esta informaci´ on se realiza mediante los nombres de estos objetos. Esto

(6)

hace necesario tener alguna manera de, a partir de un identificador, encontrar sus propiedades. La estructura de datos que guarda esta informaci´ on se denomina tabla de s´ımbolos y puede interactuar con pr´ acticamente todas las fases de la compilaci´ on.

Algunos ejemplos de la informaci´ on guardada son:

Constantes: tipo, valor.

Variables: tipo, direcci´ on en memoria, tama˜ no.

Funciones: n´ umero y tipo de los argumentos, tipo devuelto, direcci´ on.

Es importante tener en cuenta que la informaci´ on asociada con un identificador puede variar a lo largo del programa. As´ı, por ejemplo, el identificador i se refiere a dos variables distintas en el siguiente c´ odigo:

int main() { int i=1;

{ float i=2.0;

printf("%f\n", i); /* i es un float */

}

printf("%d\n", i); /* i es un int */

}

Una cuesti´ on de gran importancia ser´ a encontrar una estructura de datos eficiente para acceder a los elementos de la tabla.

2.3.2. Gesti´ on de errores

Es un hecho que al programar se cometen errores. Es m´ as, se puede decir casi con total seguridad que un compilador encuentra m´ as programas err´ oneos que correctos. As´ı pues, es importante que el compilador ayude en la detecci´ on y correcci´ on de errores. Para ello, el compilador debe, ante un error:

Diagnosticarlo de la manera m´ as clara posible.

Detener la generaci´ on de c´ odigo.

Intentar recuperarse para poder continuar el an´ alisis.

Veremos que, en general, no es posible detectar los errores, s´ olo sus s´ıntomas. Por ejemplo, ante la entrada a:= b 1; no es posible saber si falta un + entre la b y el 1 o si sobra un espacio o si sobra el uno. . . Sin embargo, ser´ a posible asegurar que la presencia de un error ha sido detectada lo antes posible; en nuestro ejemplo, detectaremos el error al ver el 1.

2.4. M´ odulos externos al compilador

Aunque con lo que hemos visto hasta ahora podr´ıamos considerar que tenemos el compilador completo, hay otros m´ odulos que tambi´ en se utilizan en el proceso de construcci´ on de progra- mas y que en muchos casos son programas independientes invocados por el propio compilador.

Comentaremos brevemente tres: el preprocesador, en enlazador y el soporte en ejecuci´ on.

2.4.1. El preprocesador

En algunos lenguajes (probablemente el m´ as conocido es C), existe una fase anterior al an´ alisis

l´ exico: el preproceso. En esta fase se realizan acciones tales como expansi´ on de macros o inclusi´ on

de ficheros. Es interesante darse cuenta de que el preprocesador se comporta como un compilador

muy restringido en sus capacidades y que tiene sus propias fases de an´ alisis y s´ıntesis.

(7)

2.4.2. El enlazador

En muchos casos, el resultado de la compilaci´ on no es un programa completo sino una parte de ´ el: un fichero objeto. Una vez se han compilado todas las partes del programa, hay que unirlas, probablemente tambi´ en empleando alguna biblioteca, para crear el programa final. El programa encargado de esto es el enlazador (linker en ingl´ es).

2.4.3. Soporte en ejecuci´ on

Adem´ as de las acciones especificadas por el programador, el c´ odigo final tiene que hacer una serie de acciones necesarias para el buen funcionamiento del programa. La parte a˜ nadida al pro- grama para hacer esto es lo que se conoce como soporte en ejecuci´ on. Algunas de sus funciones son preparar la memoria al comienzo de la ejecuci´ on, gestionar la memoria o la pila durante la ejecuci´ on y preparar la finalizaci´ on de la ejecuci´ on de una manera razonable. Seg´ un los casos, este c´ odigo puede estar contenido en funciones de la biblioteca est´ andar del lenguaje, puede ser a˜ nadido al programa o generado autom´ aticamente.

3. La interpretaci´ on

Mientras que el objetivo de los compiladores es obtener una traducci´ on del programa fuente a otro lenguaje, los int´ erpretes tienen como objeto la obtenci´ on de los resultados del programa.

Para ello deben realizar dos tareas: analizar su entrada y llevar a cabo las acciones especificadas por ella.

La parte de an´ alisis puede realizarse de manera id´ entica a como se lleva a cabo en los compila- dores. Es la parte de s´ıntesis la que se diferencia sustancialmente. En el caso de la interpretaci´ on, se parte del ´ arbol de sintaxis abstracta y se recorre, junto con los datos de entrada, para obtener los resultados.

En el caso del ´ arbol

idvalor

idvalor idinc suma asignaci´ on

El recorrido consistir´ıa en:

Analizar el nodo asignaci´ on.

Visitar su hijo derecho (la suma) para obtener el valor que hay que asignar:

• Visitar el hijo izquierdo de la suma, recuperar el valor actual de valor.

• Visitar el hijo derecho de la suma, recuperar el valor actual de inc.

• Hacer la suma.

Guardar el resultado de la suma en valor.

Actualmente se est´ a poniendo de moda una especie de h´ıbrido entre la compilaci´ on y la inter-

pretaci´ on que consiste en compilar a un lenguaje intermedio para una m´ aquina virtual y despu´ es

interpretar este lenguaje. Esta aproximaci´ on es la que se sigue, por ejemplo, en Java, Python o la

plataforma .NET.

(8)

4. La arquitectura real de compiladores e int´ erpretes

Siguiendo la descripci´ on de las fases hecha m´ as arriba, vemos que la estructura de un compilador es similar a la siguiente:

Programa fuente

Arbol de sintaxis abstracta ´

Programa objeto An´ alisis

l´ exico

An´ alisis sint´ actico

An´ alisis sem´ antico

Generaci´ on de c´ odigo intermedio

Optimizaci´ on de c´ odigo intermedio

Generaci´ on de c´ odigo objeto

Optimizaci´ on de c´ odigo objeto An´ alisis

S´ıntesis

De manera an´ aloga, un int´ erprete tendr´ıa esta estructura:

Programa fuente

Arbol de sintaxis abstracta ´

Resultados An´ alisis

l´ exico

An´ alisis sint´ actico

An´ alisis sem´ antico

Generaci´ on de resultados An´ alisis

S´ıntesis

O, si utiliz´ aramos una m´ aquina virtual, el int´ erprete tendr´ıa esta estructura:

Programa fuente

Arbol de sintaxis abstracta ´

Resultados An´ alisis

l´ exico

An´ alisis sint´ actico

An´ alisis sem´ antico

Generaci´ on de c´ odigo intermedio

Optimizaci´ on de c´ odigo intermedio

Int´ erprete de m´ aquina virtual An´ alisis

S´ıntesis

(9)

Sin embargo, en la pr´ actica, la separaci´ on entre las distintas fases no est´ a tan marcada. Lo habitual es que el analizador sint´ actico haga las veces de “maestro de ceremonias”, pidiendo al analizador l´ exico los componentos l´ exicos a medida que los va necesitando y pasando al analizador sem´ antico la informaci´ on que va obteniendo. De hecho, lo m´ as normal es que este ´ ultimo (el analizador sem´ antico) no exista como un m´ odulo separado sino que est´ e integrado en el sint´ actico.

As´ı se elimina la necesidad de crear un ´ arbol de an´ alisis. Esta organizaci´ on suele llamarse traducci´ on dirigida por la sintaxis.

Si la memoria disponible es escasa, puede resultar imposible mantener una representaci´ on de todo el programa. En estos casos, se unen las fases de an´ alisis y de s´ıntesis. De esta manera, se va generando c´ odigo al mismo tiempo que se analiza el programa fuente. Esta forma de trabajar era habitual en compiladores m´ as antiguos y exige ciertas caracter´ısticas especiales al lenguaje, como las declaraciones “forward” de Pascal.

5. Algunos conceptos de teor´ıa de lenguajes formales

Al comentar las distintas fases del compilador, hemos mencionado la existencia de herramientas formales para la especificaci´ on de los analizadores l´ exico (lenguajes regulares) y sint´ actico (lengua- jes incontextuales). Ambos tipos de herramientas se incluyen dentro de la teor´ıa de los lenguajes formales. Ahora veremos una introducci´ on a esta teor´ıa.

5.1. Conceptos b´ asicos

Un alfabeto Σ es un conjunto finito de s´ımbolos. No nos interesa la naturaleza de los s´ımbolos.

Dependiendo de la aplicaci´ on que tengamos en mente, ´ estos pueden ser: caracteres, como al espe- cificar el analizador l´ exico; letras o palabras, si queremos trabajar con lenguaje natural; categor´ıas l´ exicas, al especificar el analizador sint´ actico; direcciones en el plano, al hacer OCR; . . .

Una cadena es una secuencia finita de s´ımbolos del alfabeto. Nuevamente, no estamos interesa- dos en la naturaleza precisa de las cadenas. Si estamos especificando el analizador l´ exico, podemos ver la entrada como una cadena; si trabajamos con lenguaje natural, la cadena puede ser una pregunta a una base de datos; para el analizador sint´ actico, una cadena es el programa una vez pasado por el analizador l´ exico; en OCR, una cadena es la descripci´ on de una letra manuscrita;. . . La cadena de longitud cero se denomina cadena vac´ıa y se denota con λ. Para referirnos al conjunto de cadenas de longitud k, utilizamos Σ

k

. Si nos referimos al conjunto de todas las cadenas, la clausura del alfabeto, escribimos:

Σ

=

[

k=0

Σ

k

= Σ

0

∪ Σ

1

∪ Σ

2

∪ . . .

La clausura positiva incluye todas las cadenas excepto la vac´ıa:

Σ

+

= Σ

− {λ} =

[

k=1

Σ

k

= Σ

1

∪ Σ

2

∪ Σ

3

∪ . . .

F´ıjate que tanto Σ

como Σ

+

son conjuntos infinitos de cadenas finitas.

Un lenguaje formal es un subconjunto de Σ

. Date cuenta que no entramos en si es finito o infinito. Tampoco en este caso esperamos que el lenguaje tenga relaci´ on alguna con los lenguajes humanos o que sus cadenas tengan significado alguno; un lenguaje formal es simplemente un conjunto de cadenas.

5.2. Gram´ aticas

Tenemos ahora un peque˜ no problema. Los lenguajes pueden ser infinitos, pero deseamos des-

cribirlos utilizando un tiempo y unos recursos finitos (una descripci´ on infinita probablemente ser´ıa

(10)

demasiado larga para este curso). Existen diversas maneras para describir lenguajes, aqu´ı recor- daremos las gram´ aticas formales.

¿Qu´ e es una gram´ atica formal? Simplemente una cu´ adrupla (N, Σ, P, hSi) donde N es un alfabeto de no terminales.

Σ es un alfabeto de terminales, disjunto con N .

P ⊆ (N ∪ Σ)

N (N ∪ Σ)

× (N ∪ Σ)

es un conjunto de producciones o reglas.

hSi es el s´ımbolo inicial (pertenece a N ).

Por comodidad, las producciones se representan como α → β si (α, β) ∈ P .

Convenios de notaci´ on

En lo sucesivo y con objeto de no tener que indicar expl´ıcitamente a qu´ e alfabeto (N o Σ) perte- nece cada s´ımbolo o cadena, asumiremos unos convenios notacionales que permitir´ an deducirlo inmediatamente:

Representamos los terminales con un tipo de letra mecanogr´ afico (a, c, +, :=), en negrita (identificador, entero) o entrecomillado (“|”, “<”).

Los no terminales se representan encerrados entre ´ angulos (hAi, hProgramai).

Un s´ ımbolo que puede ser terminal o no terminal, indistintamente, se representar´ a con alguna de las ´ ultimas letras may´ usculas del alfabeto latino (X, Y,. . . ).

La cadenas de s´ ımbolos terminales (elementos de Σ

) se representan con la ´ ultimas letras min´ usculas del alfabeto latino (u, v, w,. . . ).

Las cadenas de s´ ımbolos terminales o no terminales (elementos de (N ∪ Σ)

) se representan con letras min´ usculas del alfabeto griego (α, β, γ,. . . ).

5.2.1. ¿C´ omo funcionan las gram´ aticas?

El funcionamiento de las gram´ aticas se basa en el concepto de derivaci´ on. Comenzando por una cadena formada ´ unicamente por el s´ımbolo inicial, se aplican repetidamente las reglas de P hasta llegar a una cadena formada ´ unicamente 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.

Por ejemplo, sea la gram´ atica: G = ({hSi, hEi}, {id, +, *}, P, hSi), con P = {hSi → hEi,

hEi → hEi+hEi, hEi → hEi*hEi, hEi → id};

para derivar id+id*id podemos hacer:

hSi ⇒ hEi

⇒ hEi+hEi

⇒ hEi+hEi*hEi

⇒ id+hEi*hEi

⇒ id+id*hEi

⇒ id+id*id

(11)

Ejercicio 2

Encuentra otra derivaci´ on de id+id*id.

Ejercicio

*

3

¿Cu´ antas derivaciones distintas tiene la cadena id+id*id?

De una manera m´ as formal, tenemos las siguientes definiciones:

Derivaci´ on directa: η deriva directamente γ en G si η = ω

1

αω

2

, γ = ω

1

βω

2

y α → β ∈ P . Lo denotamos con η ⇒ γ.

Derivaci´ on: η deriva γ (en G) si hay una secuencia de cadenas (ξ

1

, ξ

2

, . . . , ξ

n

) de (N ∪ Σ)

tal que η = ξ

1

, γ = ξ

n

y ξ

i

⇒ ξ

i+1

para 1 ≤ i < n. Lo denotamos con η ⇒ γ.

Forma sentencial: Cadena α de (N ∪ Σ)

tal que hSi ⇒ α.

Sentencia: forma sentencial perteneciente a Σ

.

Lenguaje definido (o generado) por la gram´ atica G: L(G) = {x ∈ Σ

| hSi ⇒ x}.

Ejercicio 4

¿Cu´ al es el lenguaje definido por la gram´ atica del ejemplo anterior?

5.3. La jerarqu´ıa de Chomsky

Podemos ordenar jer´ arquicamente las gram´ aticas en funci´ on de su “potencia” por la “forma”

de sus producciones:

Gram´ atica de tipo 0 o no restringida: producciones de la forma α → β, donde α ∈ (N ∪ Σ)

N (N ∪ Σ)

y β ∈ (N ∪ Σ)

.

Gram´ atica de tipo 1 o sensible al contexto: producciones de la forma αhAiβ → αγβ, donde α, β ∈ (N ∪ Σ)

, γ ∈ (N ∪ Σ)

+

, hAi ∈ N .

Gram´ atica de tipo 2 o incontextual : producciones de la forma hAi → α, donde hAi ∈ N y α ∈ (N ∪ Σ)

.

Gram´ atica de tipo 3 o regular : producciones de la forma hAi → ahBi, hAi → a ´ o hAi → λ, donde hAi, hBi ∈ N y a ∈ Σ.

Tambi´ en clasificamos los lenguajes por su “tipo”: un lenguaje es del tipo i si existe una gram´ atica de ese tipo capaz de generarlo. Las gram´ aticas de tipo 0 son las que poseen mayor poder descriptivo, y las de tipo 3 las de menor: as´ı un lenguaje de tipo 3 tambi´ en es de tipo 2, un lenguaje de tipo 2 que no incluya la cadena vac´ıa es de tipo 1 y todo lenguaje de tipo 1 es tambi´ en de tipo 0. Por lo general, diremos que el tipo de un lenguaje es el tipo de la gram´ atica m´ as restringida capaz de generarlo.

Ejercicio 5

¿De qu´ e tipo son los siguientes lenguajes?

Las cadenas de bits con un n´ umero par de unos.

Las cadenas de bits que representan un m´ ultipo de tres.

Las cadenas de bits pal´ındromas.

Las cadenas de bits con m´ as ceros que unos.

Las cadenas de bits que representan un cuadrado perfecto.

(12)

5.4. Dos convenios m´ as

Para simplificar la exposici´ on, adoptaremos dos convenios adicionales. A la hora de escribir una gram´ atica, s´ olo mostraremos las reglas y asumiremos que el s´ımbolo inicial es el que est´ a a la parte izquierda de la primera regla.

Tambi´ en asumiremos que la gram´ atica no tiene ni s´ımbolos in´ utiles ni reglas in´ utiles. Esto es, para cada s´ımbolo o regla, habr´ a al menos una derivaci´ on de una sentencia del lenguaje que contenga ese s´ımbolo o regla.

5.5. An´ alisis sint´ actico

Tal como las hemos visto, las gram´ aticas son dispositivos generativos, es decir, nos dan un m´ e- todo para obtener todas las cadenas del lenguaje. Sin embargo, al plantearnos hacer un compilador o un int´ erprete, el problema al que nos enfrentamos es el inverso:

Dada una cadena x ∈ Σ

y una gram´ atica G, ¿pertenece x a L(G)?

F´ıjate que, si x pertenece al lenguaje de G, existe al menos una derivaci´ on de x en G. Nos interesar´ a saber cu´ al es esa derivaci´ on (o derivaciones), puesto que ser´ a lo que nos indique la “estructura”

de x.

Siguiendo la clasificaci´ on de las gram´ aticas, podemos asignar a cada tipo un dispositivo capaz de reconocer sus cadenas:

Tipo de lenguaje Dispositivo reconocedor Regular Aut´ omata de estados finitos Incontextual Aut´ omata de pila

Sensible al contexto Aut´ omata de memoria limitada linealmente No restringido M´ aquina de Turing

Estos dispositivos son s´ olo “m´ aquinas virtuales”. En la pr´ actica, tienen que ser emulados por programas. En el caso de los lenguajes regulares, se puede analizar la pertenencia de una cadena x a un lenguaje en tiempo O(|x|). En el caso de los lenguajes incontextuales, se necesita O(|x|

3

). Dado que esto es muy costoso, utilizaremos restricciones de los lenguajes incontextuales que permiten el an´ alisis con un coste lineal.

5.6. Lenguajes formales y compiladores

La divisi´ on que hemos hecho del problema del an´ alisis nos permite utilizar gram´ aticas regulares para el analizador l´ exico y gram´ aticas incontextuales para el sint´ actico. En principio, se podr´ıan utilizar gram´ aticas sensibles al contexto para especificar las restricciones sem´ anticas. Sin embargo, la complejidad de la escritura de estas gram´ aticas y el elevado coste temporal de los algoritmos de an´ alisis aconsejan que las restricciones se escriban utilizando como formalismo las gram´ aticas atri- buidas. Estas pueden verse como gram´ aticas incontextuales enriquecidas para admitir fragmentos de c´ odigo.

Es muy importante darse cuenta de cu´ ales son los alfabetos sobre los que se definen los lenguajes empleados en el analizador l´ exico y sint´ actico. El analizador l´ exico define lenguajes sobre el alfabeto de los caracteres de la m´ aquina. As´ı ser´ an lenguajes sobre los juegos de caracteres ASCII, ISOlatin, EBCDIC, Unicode, etc. El an´ alisis sint´ actico define lenguajes sobre el conjunto de las categor´ıas l´ exicas, ya que son lo que “ve” el analizador sint´ actico.

6. Resumen del tema

Dos etapas en la traducci´ on: an´ alisis y s´ıntesis.

An´ alisis:

(13)

• L´exico: de caracteres a componentes.

• Sint´ actico: de componentes a ´ arboles de an´ alisis.

• Sem´ antico: de ´ arboles de an´ alisis a AST.

S´ıntesis:

• En compilaci´ on:

◦ Generaci´ on de c´ odigo intermedio.

◦ Generaci´ on de c´ odigo objeto.

◦ Optimizaci´ on (mezclada con las anteriores).

• En interpretaci´ on, dos opciones:

◦ Generaci´ on de c´ odigo intermedio e interpretaci´ on del c´ odigo intermedio.

◦ Generaci´ on de resultados.

Lenguajes formales: conjuntos de cadenas sobre un alfabeto.

Especificaci´ on de lenguajes: mediante gram´ aticas.

Usaremos:

• An´ alisis l´ exico: lenguajes regulares.

• An´ alisis sint´ actico: lenguajes incontextuales.

Figure

Updating...

References

Related subjects :