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

4o Ingenier Informtica a a

II26 Procesadores de lenguaje


Anlisis semntico a a Esquema del tema
1. Introduccin o 2. Esquemas de traduccin dirigidos por la sintaxis o 3. El rbol de sintaxis abstracta a 4. Comprobaciones semnticas a 5. Interpretacin o 6. Introduccin a metacomp o 7. Algunas aplicaciones 8. Resumen del tema

1.

Introduccin o

Hay determinadas caracter sticas de los lenguajes de programacin que no pueden ser modeo ladas mediante gramticas incontextuales y que es necesario comprobar en una fase posterior al a anlisis sintctico. Por otro lado, las fases posteriores de la compilacin o interpretacin necesitan a a o o una representacin de la entrada que les permita llevar a cabo sus funciones de manera adecuada. o Estas dos vertientes deteccin de errores y representacin de la informacin estn muy o o o a relacionadas y se solapan en la prctica. Supongamos, por ejemplo, que nuestro lenguaje permite a asignaciones segn la regla u Asignacin id:= Expresin ; o o Es habitual que se impongan ciertas restricciones. En nuestro caso, estas podr ser: an El identicador de la parte izquierda debe estar declarado previamente. El tipo de la expresin debe ser compatible con el del identicador. o El analizador semntico deber comprobar que estas dos restricciones se cumplen antes de declaa a rar que la sentencia de asignacin est bien formada. Pero sucede que la informacin necesaria o a o para comprobarlas es util tambin para generar cdigo. Esto quiere decir que si tuviramos una e o e separacin estricta entre las fases del compilador, para generar cdigo deber o o amos volver a mirar el identicador para saber a qu objeto (variable, funcin, constante, etc.) corresponde y qu tipo e o e tiene y tambin deber e amos volver a comprobar los tipos de la expresin para generar el cdigo o o adecuado. Por otro lado, aunque en teor el arbol de anlisis ser suciente para fases posteriores de la a a a compilacin o interpretacin, es una representacin que contiene mucha informacin redundante. o o o o As el rbol correspondiente a a:= b+c; bien podr tener el aspecto del rbol de la izquierda, , a a a cuando el de la derecha contiene esencialmente la misma informacin y resulta ms cmodo para o a o

II26 Procesadores de lenguaje

trabajar: Asignacin o

ida :=

Expresin o

; asignacin o

Expresin o

Trmino e variablea suma variableb constantec

Trmino e

Factor

Factor

idc

idb El segundo rbol se conoce como rbol de sintaxis abstracta (o AST, de las iniciales en ingls). a a e Como hemos comentado, durante el anlisis semntico se recoge una serie de informaciones que a a resultan de utilidad para fases posteriores. Estas informaciones se pueden almacenar en el rbol, a decorndolo: a asignacin o

variable
nombre: a tipo: entero

suma
tipo: entero

variable
nombre: b tipo: entero

constante
nombre: c tipo: entero valor: 5

As el objetivo de la fase de anlisis semntico ser doble: por un lado detectaremos errores que a a a no se han detectado en fases previas y por otro lado obtendremos el AST decorado de la entrada. Para ello utilizaremos esquemas de traduccin dirigidos por la sintaxis, que permitirn asociar o a acciones a las reglas de la gramtica. Estas acciones realizarn comprobaciones y construirn el a a a AST que despus se recorrer para terminar las comprobaciones y ser la base para la interpretae a a cin o la generacin de cdigo. o o o

2.

Esquemas de traduccin dirigidos por la sintaxis o

Nuestro objetivo es especicar una serie de acciones que se realizarn durante el anlisis de a a la entrada y tener un mecanismo que nos permita obtener la informacin necesaria para realizar o estas acciones. Para esto aadimos a las gramticas dos elementos nuevos: n a Acciones intercaladas en las reglas. Atributos asociados a los no terminales de la gramtica. a

2.1.

Acciones intercaladas en las reglas

Supongamos que tenemos el no terminal A que deriva dos partes diferenciadas y queremos que se escriba el mensaje Cambio de parte tras analizarse la primera. Podemos describir esto

Anlisis semntico a a

de forma sencilla si aumentamos nuestra gramtica incluyendo la accin en la parte derecha de la a o regla de A : A Parte1 {escribe(Cambio de parte);} Parte2

Podemos entender esta regla extendida como la receta: analiza la primera parte, una vez encontrada, escribe el mensaje y despus analiza la segunda parte. e

2.2.

Atributos asociados a los no terminales

Si nuestras acciones se limitaran a escribir mensajes que indicaran la fase del anlisis en la que a nos encontramos, no necesitar amos mucho ms. En general, querremos que las acciones respondan a al contenido de los programas que escribimos. Para ello, vamos a aadir a los no terminales una serie n de atributos. Estos no son ms que una generalizacin de los atributos que tienen los terminales. a o Vamos a ver un ejemplo. Supongamos que en un lenguaje de programacin se exige que en las subrutinas aparezca un o identicador en la cabecera que debe coincidir con el que aparece al nal. Tenemos el siguiente fragmento de la gramtica del lenguaje: a Subrutina Cabecera Fin Cabecera Cuerpo Fin subrutina id( Parmetros ); a n id

En la regla correspondiente a Fin querremos comprobar que el identicador es el mismo que en la cabecera. Vamos a denir un atributo del no terminal Fin que ser el que nos diga el nombre a de la funcin: o Fin n id {si id.lexema = Fin .nombre entonces error n si}

El atributo nombre es lo que se conoce como un atributo heredado: su valor se calcular en las a reglas en las que Fin aparezca en la parte derecha y se podr utilizar en las reglas en las que Fin a aparezca en la izquierda; ser informacin que heredar de su entorno. F a o a jate en cmo hemos o empleado un atributo de id para hacer comprobaciones. El analizador semntico es el que emplea a la informacin contenida en los atributos de los componentes lxicos. Recuerda que el sintctico o e a slo ve las etiquetas de las categor En cuanto a error, suponemos que representa las acciones o as. necesarias para tratar el error. Ahora tenemos que completar las acciones de modo que Fin tenga algn valor para el nombre. u Podemos aadir una accin a la regla de Subrutina para calcular el valor de Fin .nombre: n o Subrutina Cabecera Cuerpo { Fin .nombre:= Cabecera .nombre} Fin

Lo que hacemos es copiar el atributo nombre que nos devolver Cabecera . No debes confundir a el atributo nombre de Fin con el de Cabecera , son completamente independientes. De hecho, el atributo nombre de Cabecera es un atributo sintetizado: su valor se calcular en las reglas en las a que Cabecera aparezca en la parte izquierda y se utilizar en las reglas donde Cabecera aparezca a en la parte derecha; es informacin que Cabecera sintetiza para su entorno. Debemos calcular o el valor de nombre en la regla de Cabecera : Cabecera subrutina id( Parmetros ); { Cabecera .nombre:= id.lexema} a

2.3.

Otros elementos de las acciones

Como habrs comprobado, no hemos denido ningn lenguaje formal para escribir las acciones. a u Asumiremos que se emplean construcciones corrientes de los algoritmos tales como condicionales
c Universitat Jaume I 2006-2007

II26 Procesadores de lenguaje

o bucles. Tambin haremos referencia en ellos a subrutinas y a variables. Las variables las intere pretaremos como variables locales a la regla que estamos analizando o como variables globales si lo indicamos expl citamente. As en: ,

Valor

opsum {si opsum.lexema=+ entonces signo:= 1 si no signo:= -1 n si} Valor 1 { Valor .v:= signo* Valor 1 .v}

no hay ninguna interferencia entre las distintas variables signo que se puedan utilizar en un momento dado. F jate tambin en cmo hemos distinguido mediante un sub e o ndice las dos apariciones del no terminal Valor . Un recurso muy util son los atributos que contienen listas. Por ejemplo, para una declaracin o de variables del tipo: DeclVariables ListaIds podemos hacer lo siguiente: DeclVariables ListaIds ListaIds ListaIds : Tipo {para v en ListaIds .l hacer: declara_var(v, Tipo .t) n para} ListaIds : Tipo

id, ListaIds |id

id, ListaIds 1 { ListaIds .l:= concat( [id.lexema], ListaIds 1 .l)} id { ListaIds .l:= [id.lexema]}

2.4.

Recopilacin o

Con lo que hemos visto, podemos decir que un esquema de traduccin consta de: o Una gramtica incontextual que le sirve de soporte. a Un conjunto de atributos asociados a los s mbolos terminales y no terminales. Un conjunto de acciones asociadas a las partes derechas de las reglas. Dividimos los atributos en dos grupos: Atributos heredados. Atributos sintetizados. Sea A X1 . . . Xn una produccin de nuestra gramtica. Exigiremos que: o a Las acciones de la regla calculen todos los atributos sintetizados de A . Las acciones situadas a la izquierda de Xi calculen todos los atributos heredados de Xi . Ninguna accin haga referencia a los atributos sintetizados de los no terminales situados a o su derecha en la produccin. o Las dos ultimas reglas nos permitirn integrar las acciones semnticas en los analizadores descen a a dentes recursivos. Cuando se emplean, se dice que el esquema de traduccin est basado en una o a gramtica L-atribuida. La L indica que el anlisis y evaluacin de los atributos se pueden hacer de a a o izquierda a derecha.

Anlisis semntico a a

Ejercicio 1

Las siguientes reglas representan parte de las sentencias estructuradas de un lenguaje de programacin: o Programa Sentencias Sentencia Sentencia Sentencia Sentencia Sentencias Sentencia Sentencias | Sentencia

mientras Expresin hacer Sentencias nmientras o si Expresin entonces Sentencias sino Sentencias nsi o interrumpir otros

Aade las reglas necesarias para comprobar que la instruccin interrumpir aparece unicamente n o dentro de un bucle.
Ejercicio 2

Sea G la siguiente gramtica: a A B C B C |a

A a B b| C a a C C |aba C |a

Aade a G las reglas semnticas necesarias para que el atributo ia de A contenga el nmero n a u de aes al inicio de la cadena generada. Por ejemplo, dadas las cadenas aaaaba, abaaaa y aaa, los valores de ia ser 4, 1 y 3, respectivamente. an Puedes utilizar los atributos adicionales que consideres necesarios, pero ninguna variable global. Adems, los atributos que aadas deben ser de tipo entero o lgico. a n o

2.5.

Algunas cuestiones formales

Una pregunta razonable es si puede el s mbolo inicial tener atributos heredados y, si esto es as de dnde proceden. La respuesta var segn los textos. Los que se oponen, deenden que el , o a u signicado del programa debe depender unicamente de l. Los que estn a favor de los atributos e a heredados, sostienen que permiten formalizar la entrada de informacin acerca de, por ejemplo, el o entorno donde se ejecutar el programa o las opciones de compilacin. a o Una situacin similar se da con los s o mbolos terminales: hay autores que piensan que no deber an tener atributos en absoluto; otros deenden que slo deben tener atributos sintetizados; y los hay o que opinan que pueden tener tanto atributos heredados como sintetizados. Otro aspecto sobre el que hay diferencias de interpretacin es el de los efectos laterales de las o reglas. En algunos textos, las reglas de evaluacin de los atributos no pueden tener efectos laterales. o Otros autores deenden que esta distincin no es ms que un problema de implementacin ya que o a o es posible aadir un nuevo atributo que represente el entorno e ir actualizndolo adecuadamente. n a Esto unicamente supone una incomodidad, pero no un problema formal. Esta es la posicin que o seguiremos nosotros, permitiendo que haya efectos laterales en el clculo de atributos. a Quiz los ejemplos ms claros de existencia de efectos laterales sean el manejo de la tabla de a a s mbolos y el control de errores. Cuando se encuentra una declaracin en un programa es necesario o actualizar la tabla de s mbolos de modo que sea posible reconocer ocurrencias posteriores del identicador. Podemos suponer que existe una tabla de s mbolos global o tener un atributo que lleve copias de la tabla de un sitio a otro. Por otro lado, en caso de que se encuentre un error, es necesario tomar medidas como detener la generacin de cdigo. Una manera sencilla de marcar esta circunstancia es tener una variable o o
c Universitat Jaume I 2006-2007

II26 Procesadores de lenguaje

global de tipo lgico que indique si se ha encontrado algn error. Nuevamente, ser posible, pero o u a no necesario, tener un atributo que transporte esa informacin. o

2.6.

Eliminacin de recursividad por la izquierda y atributos o

En el tema de anlisis sintctico vimos cmo se pueden modicar las producciones con recursivia a o dad por la izquierda para lograr que la gramtica sea LL(1). El problema con las transformaciones a es que dejan una gramtica que tiene muy poca relacin con la original, lo que hace que escribir los a o atributos y las reglas correspondientes sea dif sobre la gramtica transformada. Sin embargo, cil a se pueden escribir los atributos sobre la gramtica original y despus convertirlos de una manera a e bastante mecnica. a Como las GPDRs no suelen necesitar recursividad por la izquierda, es fcil que no tengas que a utilizar esta transformacin. Pese a todo, vamos a ver cmo se hace la transformacin sobre un o o o ejemplo. Partimos del siguiente esquema de traduccin: o E E T E 1 + T { E .v:= E 1 .v+ T .v}

T { E .v:= T .v} num { T .v:= num.v}

Comenzamos por transformar la gramtica: a E E E T T E

+ T E num

Como vemos, el problema es que cuando vemos el sumando derecho en E , no tenemos acceso al izquierdo. La idea ser crear un atributo heredado que nos diga el valor del sumando izquierdo. a En la primera regla lo podemos calcular directamente. En la segunda regla, realizamos la suma y la transmitimos: E E T { E .h:= T .v} E

+ T { E 1 .h:= E .h+ T .v} E 1

Con esto, el atributo h contendr siempre la suma, que es el valor que tenemos que devolver a nalmente. Por eso hay que transmitir el nuevo valor al atributo v: E E T { E .h:= T .v } E { E .v:= E .v}

+ T { E 1 .h:= E .h+ T .v} E 1 { E .v:= E 1 .v}

Qu hacemos cuando E se reescribe como la cadena vac En este caso, basta con devolver e a? como sintetizado el valor que se hereda. El esquema de traduccin completo queda: o E E E T
Ejercicio 3

T { E .h:= T .v} E { E .v:= E .v}

+ T { E 1 .h:= E .h+ T .v} E 1 { E .v:= E 1 .v} { E .v:= E .h} num { T .v:= num.v}

Escribe el rbol de anlisis de 3+4 con el esquema original y el transformado. Decralos. a a o

Anlisis semntico a a

Ejercicio 4

Transforma el siguiente esquema de traduccin para eliminar la recursividad por la izquierda: o E E T T F F E 1 + T { E .v:= E 1 .v+ T .v} T { E .v:= T .v} T 1 * F { T .v:= T 1 .v* F .v}

F { T .v:= F .v} ( E ) { F .v:= E .v} num { F .v:= num.v}

El siguiente ejercicio presenta la transformacin de una manera ms general. o a


Ejercicio* 5

Si tenemos las siguientes reglas en un esquema de traduccin o A A A 1 Y { A .a:= g( A 1 .a, Y .y)} X { A .a:= f( X .x)}

podemos transformarlas en A A A X { A .h:= f( X .x)} A { A .a:= A .s} Y { A 1 .h:= g( A .h, Y .y)} A 1 { A .s:= A 1 .s}

{ A .s:= A .h}

Comprueba que la transformacin es correcta analizando X Y 1 Y 2 mediante las dos versiones y o comparando el valor de a.

2.7.

Implementacin de los esquemas de traduccin o o

La interpretacin de las acciones como sentencias que se ejecutan al pasar el anlisis por ellas o a permite implementar los esquemas de traduccin de manera sencilla. Para ello se modica la o implementacin del analizador recursivo descendente correspondiente a la gramtica original de la o a siguiente manera: Los atributos heredados del no terminal A se interpretan como parmetros de entrada de a la funcin Analiza_A. o Los atributos sintetizados del no terminal A se interpretan como parmetros de salida de a la funcin Analiza_A. o Las acciones semnticas, una vez traducidas al lenguaje de programacin correspondiente, se a o insertan en la posicin correspondiente segn su orden en la parte derecha donde aparecen. o u En la prctica, es frecuente que, si el lenguaje de programacin (como C) no permite devolver a o ms de un valor, los atributos sintetizados del no terminal se pasen por referencia. a En Python existe una solucin bastante cmoda. Comenzamos por denir una clase vac o o a: class Atributos: pass Antes de llamar a una funcin o mtodo de anlisis, creamos un objeto de esta clase y le o e a aadimos los atributos heredados del no terminal. La correspondiente funcin de anlisis crear n o a a los atributos sintetizados. Este es el unico parmetro que se pasa. As la traduccin de la regla: a , o
c Universitat Jaume I 2006-2007

II26 Procesadores de lenguaje

E es la siguiente:

T { R .h:= T .v} R { E .v:= R .v}

def analiza_E(E): T= Atributos() # Atributos de T R= Atributos() # Atributos de R analiza_T(T) R.h= T.v # Creamos un atributo heredado de R analiza_R(R) E.v= R.v # Creamos un atributo sintetizado de E Lgicamente, tendr o amos que haber aadido el cdigo de control de errores. n o

2.8.

Atributos en GPDR

La interpretacin que hemos hecho de los esquemas de traduccin se traslada de forma natural o o a las GPDR. Por ejemplo, la traduccin de: o E es simplemente: def analiza_E(E): T1= Atributos() # Atributos de T1 T2= Atributos() # Atributos de T2 analiza_T(T1) E.v= T1.v while token.cat=="suma": token= alex.siguiente() analiza_T(T2) E.v= E.v+T2.v Como antes, habr que aadir el correspondiente cdigo para controlar errores. a n o T 1 { E .v:= T 1 .v}(+ T 2 { E .v:= E .v+ T 2 .v})

3.

El rbol de sintaxis abstracta a

Como hemos comentado en la introduccin, una de las posibles representaciones semnticas de o a la entrada es el rbol de sintaxis abstracta o AST. a Aunque es similar a los rboles de anlisis sintctico, tiene algunas diferencias importantes: a a a No aparecen todos los componentes lxicos del programa. Por ejemplo: e No es necesario incluir los parntesis de las expresiones. e No se necesitan los separadores o terminadores de las sentencias. ... Pueden aparecer otros componentes no estrictamente sintcticos, como acciones de coercin a o de tipos.

Anlisis semntico a a

3.1.

Construccin o

Para construir los rboles, debemos comenzar por denir qu elementos emplearemos para cada a e estructura: Estructura Representacin o si if E then LS end E LS mientras while C do LS end C LS repetir repeat LS until C ; LS ... ... C ... E 1+ E 2 E1 ... E2 id:= E ; id E suma begin S 1 . . . S n end S 1 ... Sn asignacin o Estructura Representacin o sentencias

F jate que el rbol resultante puede representar programas en diversos lenguajes de programaa cin del estilo C, Pascal, etc. o Ahora debemos utilizar los atributos para construir el rbol. Utilizando el atributo arb para a devolver el rbol que construye cada no terminal, podemos hacer algo parecido a: a Sentencia Sentencia Sentencia Sentencia if Expresin then Sentencias end o { Sentencia .arb:= NodoSi( Expresin .arb, Sentencias .arb)} o while Expresin do Sentencias end o { Sentencia .arb:= NodoMientras( Expresin .arb, Sentencias .arb)} o repeat Sentencias until Expresin ; o { Sentencia .arb:= NodoRepetir( Sentencias .arb, Expresin .arb)} o id:= Expresin ; o { Sentencia .arb:= NodoAsignacin(id.lexema, Expresin .arb)} o o

Para la implementacin hay dos opciones principales: o Utilizar funciones (NodoSi, NodoMientras, . . . ) que devuelvan una estructura de datos adecuada. Utilizar objetos (NodoSi, NodoMientras, . . . ) para cada uno de los nodos. Probablemente, la segunda opcin sea la ms cmoda para el trabajo posterior con el rbol. o a o a En cuanto a la lista de sentencias, podemos utilizar nodos que tengan un grado variable, para eso almacenamos una lista con los hijos: Sentencias {l:=} ( Sentencia {l:=l+ Sentencia .arb}) { Sentencias .arb:= NodoSentencias(l)}

El tratamiento de las expresiones es sencillo. Por ejemplo, para la suma podemos hacer: Expresin o Trmino 1 {arb:= Trmino 1 .arb} e e ( + Trmino 2 {arb:=NodoSuma(arb, Trmino 2 .arb)}) e e { Expresin .arb:= arb} o
c Universitat Jaume I 2006-2007

10

II26 Procesadores de lenguaje

Las restas, productos, etc, se tratar de forma similar. Puedes comprobar que de esta manera an el AST resultante respeta la asociatividad por la izquierda.
Ejercicio* 6

En el caso de la asociatividad por la derecha tenemos dos opciones: crear una lista de operandos y recorrerla en orden inverso para construir el rbol o utilizar recursividad por la derecha, que a no da problemas para el anlisis LL(1). Utiliza ambas posibilidades para escribir sendos esquea mas de traduccin que construyan los AST para expresiones formadas por sumas y potencias de o identicadores siguiendo las reglas habituales de prioridad y asociatividad.

3.2.

Evaluacin de atributos sobre el AST o

Un aspecto interesante del AST es que se puede utilizar para evaluar los atributos sobre l, en e lugar de sobre la gramtica inicial. Si reexionas sobre ello, es lgico. Todo el proceso de evaluacin a o o de los atributos se puede ver como el etiquetado de un rbol (el rbol de anlisis), pero no hay a a a nada que impida que el rbol sobre el que se realiza la evaluacin sea el AST. a o Un ejemplo ser el clculo de tipos. Si tenemos la expresin (2+3.5)*4, podemos calcular los a a o tipos sobre el rbol de anlisis: a a

Expresin o
t: real

Trmino e
t: real

Factor
t: real

Factor
t: entero

Expresin o
t: real

num
v: 4 t: entero

Trmino e
t: entero

Trmino e
t: real

Factor
t: entero

Factor
t: real

num
v: 2 t: entero

num
v: 3.5 t: real

Anlisis semntico a a

11

Para realizar esta evaluacin, podr o amos utilizar una gramtica similar a la siguiente1 : a Expresin o ... Trmino e ... Factor Factor ... num { Factor .t:= num.t} ( Expresin ) { Factor .t:= Expresin .t} o o Factor 1 {t:= Factor 1 .t}(* Factor 2 {t:= msgeneral(t, Factor 2 .t)}) a { Trmino .t:= t} e Trmino 1 {t:= Trmino 1 .t}(+ Trmino 2 {t:= msgeneral(t, Trmino 2 .t)}) e e e a e { Expresin .t:= t} o

Tambin podemos realizar el clculo sobre el AST: e a producto


t: real

suma
t: real

num
v: 4 t: entero

num

num

v: 2 v: 3.5 t: entero t: real

Esto se har junto con el resto de comprobaciones semnticas. a a La eleccin acerca de si evaluar atributos en el AST o durante el anlisis descendente es bsio a a camente una cuestin de simplicidad. Generalmente, podemos decir que los atributos sintetizados o tienen una dicultad similar en ambos casos. Sin embargo, cuando la gramtica ha sufrido transa formaciones o hay muchos atributos heredados, suele ser ms fcil evaluarlos sobre el AST. a a Otro aspecto interesante a la hora de decidir qu atributos evaluar sobre el rbol de anlisis y e a a cules sobre el AST est en los propios nodos del rbol. Supongamos que queremos tener nodos a a a diferentes para la suma entera y la suma real. En este caso, tendremos que comprobar los tipos directamente sobre el rbol de anlisis (o crear un AST y despus modicarlo, pero esto puede ser a a e forzarlo demasiado).

4.

Comprobaciones semnticas a

Los atributos nos permitirn llevar a cabo las comprobaciones semnticas que necesite el lena a guaje. En algunos casos, utilizaremos los atributos directamente (posiblemente evaluados sobre el AST), por ejemplo en la comprobacin que hac o amos de que el identicador al nal de la funcin o era el mismo que al principio. En otros casos, los atributos se utilizan indirectamente mediante estructuras globales, por ejemplo la tabla de s mbolos. Un ejemplo ser la comprobacin de que un identicador no se ha a o declarado dos veces. Si hemos utilizado un nodo similar a:
1 Utilizamos la funcin msgeneral que devuelve el tipo ms general de los que se le pasan como parmetros (el o a a a tipo real se considera ms general que el entero). a

c Universitat Jaume I 2006-2007

12

II26 Procesadores de lenguaje

declaracin o

Tipo el mtodo de comprobacin ser e o a:

Listaids

Objeto NodoDeclaracin: o ... Mtodo compsemnticas() e a ... para i listaids hacer si TablaS mbolos.existe(i) entonces error si no TablaS mbolos.inserta(i, tipo) n si n para ... n compsemnticas a ... n NodoDeclaracin o Utilizando directamente el esquema de traduccin habr que duplicar parte del cdigo: o a o Declaracin o Listaids Tipo { Listaids .t:= Tipo .t } Listaids id, { Listaids 1 .t:= Listaids .t} Listaids 1 {si TablaS mbolos.existe(id.lexema) entonces error si no TablaS mbolos.inserta(id.lexema, Listaids .t)} Listaids id {si TablaS mbolos.existe(id.lexema) entonces error si no TablaS mbolos.inserta(id.lexema, Listaids .t)}

4.1.

La tabla de s mbolos

Durante la construccin del AST, las comprobaciones semnticas y, probablemente, durante la o a interpretacin y la generacin de cdigo necesitaremos obtener informacin asociada a los distintos o o o o identicadores presentes en el programa. La estructura de datos que permite almacenar y recuperar esta informacin es la tabla de s o mbolos. En principio, la tabla debe ofrecer operaciones para: Insertar informacin relativa a un identicador. o Recuperar la informacin a partir del identicador. o La estructura que puede realizar estas operaciones de manera eciente es la tabla hash (que en Python est disponible mediante los diccionarios). La implementacin habitual es una tabla a o que asocia cada identicador a informacin tal como su naturaleza (constante, variable, nombre de o funcin, etc.), su tipo (entero, real, booleano, etc.), su valor (en el caso de constantes), su direccin o o (en el caso de variables y funciones), etc. Es importante tener unicamente una tabla; si tenemos varias, por ejemplo, una para constantes y otra para variables, el acceso se hace ms dif ya que para comprobar las propiedades de un a cil identicador hay que hacer varias consultas en lugar de una.

Anlisis semntico a a

13

Una cuestin importante es cmo se relacionan la tabla y el AST. Tenemos distintas posibilio o dades, que explicaremos sobre el siguiente rbol, correspondiente a la sentencia a= a+c: a asignacin o

variable
nombre: a

suma
tipo: entero

variable
nombre: a

variable
nombre: c

La primera posibilidad es dejar el rbol tal cual y cada vez que necesitemos alguna informacin, a o por ejemplo el valor de a, consultar la tabla. Esta opcin ser la ms adecuada para situaciones o a a en las que se vaya a recorrer el rbol pocas veces, por ejemplo en una calculadora donde se evale a u cada expresin una sola vez. o Otra posibilidad es ir decorando cada una de las hojas con toda la informacin que se vaya o recopilando. Por ejemplo, si durante el anlisis averiguamos el tipo y direccin de las variables, a o pasar amos a almacenar la nueva informacin: o Tabla de s mbolos asignacin o a: {tipo:entero, dir:1000} ... variable
nombre: a tipo: entero dir: 1000

suma
tipo: entero

c: {tipo:entero, dir:1008} ...

variable
nombre: a tipo: entero dir: 1000

variable
nombre: c tipo: entero dir: 1008

Esto tiene costes muy elevados en tiempo tendremos que actualizar repetidamente un nmero u de nodos que puede ser elevado y espacio hay mucha informacin que se almacena repetida. o Podemos reducir estos costes guardando en cada nodo un puntero a la entrada correspondiente en la tabla: Tabla de s mbolos a: {tipo:entero, dir:1000} asignacin o ... c: {tipo:entero, dir:1008} variable suma
tipo: entero

...

variable variable

Finalmente, la opcin ms adecuada cuando tenemos lenguajes que presenten mbitos anidados o a a (veremos en otro tema cmo representar los mbitos en la tabla) es guardar la informacin sobre o a o
c Universitat Jaume I 2006-2007

14

II26 Procesadores de lenguaje

los objetos en otra estructura de datos a la que apunta tanto la tabla como los nodos del rbol: a Informacin objetos o {tipo:entero, dir:1000} asignacin o ... {tipo:entero, dir:1008} variable suma
tipo: entero

... Tabla de s mbolos

variable variable

a c ...

4.2.

Comprobaciones de tipos

Un aspecto importante del anlisis semntico es la comprobacin de los tipos de las expresiones. a a o Esta comprobacin se hace con un doble objetivo: o Detectar posibles errores. Averiguar el operador o funcin correcto en casos de sobrecarga y polimorsmo. o Para representar los tipos en el compilador, se emplean lo que se denominan expresiones de tipo (ET). La denicin de estas expresiones es generalmente recursiva. Un ejemplo ser o a: Los tipos bsicos: real, entero,. . . , adems de los tipos especiales error de tipo y ausencia de a a tipo (void) son ETs. Si n1 y n2 son enteros, rango(n1 , n2 ) es una ET. Si T es una ET, tambin lo es puntero(T ). e Si T1 y T2 son ETs, tambin lo es T1 T2 . e Si T1 y T2 son ETs, tambin lo es vector(T1 , T2 ). e Si T1 y T2 son ETs, tambin lo es T1 T2 . e Si T1 ,. . . ,Tk son ETs y N1 ,. . . ,Nk son nombres de campos, registro((N1 , T1 ), . . . , (Nk , Tk )) es una ET. Ten en cuenta que segn los lenguajes, estas deniciones cambian. Es ms, los lenguajes pueden u a restringir qu expresiones realmente resultan en tipos vlidos para los programas (por ejemplo, en e a Pascal no se puede denir un vector de funciones). Algunos ejemplos de declaraciones y sus expresiones correspondientes ser an: Declaracin o int v[10]; struct { int a; float b; } st; int *p; int f(int, char); void f2(float); Expresin de tipo o vector(rango(0, 9),entero) registro((a,entero), (b,real))

puntero(entero) entero carcter entero a real void

Anlisis semntico a a

15

4.2.1.

Equivalencia de tipos

Comprobar si dos expresiones de tipo, T1 y T2 , son equivalentes es muy sencillo. Si ambas corresponden a tipos elementales, son equivalentes si son iguales. En caso de que correspondan a tipos estructurados, hay que comprobar si son el mismo tipo y si los componentes son equivalentes. En muchos lenguajes se permite dar nombre a los tipos. Esto introduce una sutileza a la hora de comprobar la equivalencia. La cuestin es, dada una declaracin como o o typedef int a; typedef int b; son equivalentes a y b? La respuesta depende del lenguaje (o, en casos como el Pascal, de la implementacin). Existen dos criterios para la equivalencia: o Equivalencia de nombre: dos expresiones de tipo con nombre son equivalentes si y slo si tienen o el mismo nombre. Equivalencia estructural: dos expresiones de tipo son equivalentes si y slo si tienen la misma o estructura. Hay argumentos a favor de uno y otro criterio. En cualquier caso, hay que tener en cuenta que para comprobar la equivalencia estructural ya no sirve el mtodo trivial presentado antes. En este e caso, puede haber ciclos, lo que obliga a utilizar algoritmos ms complicados. a 4.2.2. Comprobacin de tipos en expresiones o

Para comprobar los tipos en las expresiones podemos utilizar un atributo que indique el tipo de la expresin. Este tipo se inere a partir de los distintos componentes de la expresin y de las o o reglas de tipos del lenguaje. A la hora de calcular el tipo hay varias opciones: podemos calcularlo sobre la gramtica, sobre a el AST o al construir el AST. En cualquier caso, puede ser util tener una funcin similar a la de o la seccin 3.2 que reeje las peculiaridades de los tipos del lenguaje. Por ejemplo, si tenemos tipos o entero y real con las reglas habituales de promocin, esta funcin devolver los resultados segn o o a u esta tabla: Primero entero entero real real Segundo Resultado entero real real real error tipo

entero real entero real otros

El clculo de atributos de tipo deber ser trivial sobre la gramtica o sobre el AST. En cuanto a a a al clculo al construir el AST, se puede, por ejemplo, hacer que el constructor compruebe los tipos a de los componentes que recibe. En este caso, si es necesario, se puede aadir un nodo de promocin n o de tipos. Por ejemplo, si vamos a crear un nodo suma a partir de los rboles: a producto
t: entero

suma
t: real

y variable num variable num


t: entero t: entero nombre: a valor: 3 t: real t: real nombre: z valor: 3.14

podemos aadir un nodo intermedio para indicar la promocin: n o


c Universitat Jaume I 2006-2007

16

II26 Procesadores de lenguaje

suma
t: real

enteroAreal

suma
t: real

producto
t: entero

variable

num

t: real t: real nombre: z valor: 3.14

variable

num

t: entero t: entero nombre: a valor: 3

Respecto a la propagacin de los errores de tipo, hay que intentar evitar un exceso de mensajes o de error. Para ello se pueden utilizar distintas estrategias: Se puede hacer que error tipo sea compatible con cualquier otro tipo. Se puede intentar inferir cul ser el tipo en caso de no haber error. Por ejemplo: si el a a operador en que se ha detectado el error es el y-lgico, se puede devolver como tipo el tipo o lgico. o

5.

Interpretacin o

La utilizacin del AST hace que la interpretacin sea bastante sencilla. Una vez lo hemos o o construido, tenemos dos aproximaciones para la interpretacin: o Podemos representar la entrada mediante una serie de instrucciones similares a un lenguaje mquina con algunas caracter a sticas de alto nivel. Este es el caso, por ejemplo, de Java y el Java bytecode. Esta aproximacin representa prcticamente una compilacin. o a o La otra alternativa es representar la entrada de manera similar al AST y recorrerlo para ejecutar el programa. Utilizaremos la segunda opcin. Los nodos correspondientes a las expresiones tendrn un mo a e todo al que llamaremos evala y que devolver el resultado de evaluar el subrbol correspondiente. u a a Por ejemplo, en el nodo de la suma tendr amos: Objeto NodoSuma: ... Mtodo evala() e u devuelve i.evala() + d.evala(); // i y d son los hijos del nodo u u n evala u ... n NodoSuma Aquellos nodos que representen sentencias tendrn el mtodo interpreta. Por ejemplo, para un a e nodo que represente un mientras, el mtodo correspondiente ser e a: Objeto NodoMientras: ... Mtodo interpreta() e mientras condicin.evala()=cierto: o u sentencias.interpreta() n mientras n interpreta ... n NodoMientras

Anlisis semntico a a

17

Las variables se representar mediante entradas en una tabla (que, en casos sencillos, puede an ser la tabla de s mbolos) que contenga el valor correspondiente en cada momento. Una asignacin o consiste simplemente en evaluar la expresin correspondiente y cambiar la entrada de la tabla. o La ejecucin del programa consistir en una llamada al mtodo interpreta del nodo ra del o a e z programa. Alternativamente, si hemos guardado el programa como una lista de sentencias, la ejecucin consistir en un bucle que recorra la lista haciendo las llamadas correspondientes. o a Lgicamente, existen otras maneras de recorrer el rbol. En particular, si no lo hemos represeno a tado con objetos, podemos recorrerlo mediante funciones recursivas o de manera iterativa. En este ultimo caso, podemos emplear una pila auxiliar o ir enhebrando el rbol durante su construccin. a o Esta es probablemente la opcin ms rpida con una implementacin cuidadosa, pero es tambin o a a o e la que ms esfuerzo exige (especialmente si hay que recorrer el rbol en distintos rdenes). a a o

6.

Introduccin a metacomp o

Mediante metacomp se pueden traducir esquemas de traduccin a programas Python que imo plementan los correspondientes analizadores descendentes recursivos. Un programa metacomp se divide en varias secciones. Cada seccin se separa de la siguiente o por un carcter % que ocupa la primera posicin de una l a o nea. La primera seccin contiene la o especicacin del analizador lxico. Las secciones pares contienen cdigo de usuario. La tercera y o e o sucesivas secciones impares contienen el esquema de traduccin. Es decir, un programa metacomp o se escribe en un chero de texto siguiendo este formato: Especicacin lxica o e % Cdigo de usuario o % Esquema de traduccin o % Cdigo de usuario o % Esquema de traduccin o . . . Las secciones de cdigo de usuario se utilizan para declarar estructuras de datos y variables, o denir funciones e importar mdulos utiles para el procesador de lenguaje que estamos especicano do. Estos elementos debern codicarse en Python. La herramienta metacomp copia literalmente a estos fragmentos de cdigo en el programa que produce como salida. o

6.1.

Especicacin lxica o e

La especicacin lxica consiste en una serie de l o e neas con tres partes cada una: Un nombre de categor o la palabra None si la categor se omite. a a Un nombre de funcin de tratamiento o None si no es necesario ningn tratamiento. o u Una expresin regular. o Los analizadores lxicos generados por metacomp dividen la entrada siguiendo la estrategia e avariciosa y, en caso de conicto, preriendo las categor que aparecen en primer lugar. Por as cada lexema encontrado en la entrada se llama a la correspondiente funcin de tratamiento, que o normalmente se limita a calcular algn atributo adicional a los tres que tienen por defecto todos u los componentes: lexema, nlinea y cat, que representan, respectivamente, el lexema, el nmero de u l nea y la etiqueta de categor del componente. a
c Universitat Jaume I 2006-2007

18

II26 Procesadores de lenguaje

Un ejemplo de especicacin lxica para una calculadora sencilla ser o e a: categor a num sum abre cierra espacio nl expresin regular o [09] [-+] \( \) [ \t]+ \n
+

acciones calcular valor emitir copiar lexema emitir emitir emitir omitir emitir

atributos valor lexema

En metacomp, lo podemos escribir as : num sum abre cierra None nl valor [0-9]+ None [-+] None \( None \) None [ \t]+ None \n

Donde valor ser una funcin como la siguiente: a o def valor(componente): componente.v= int(componente.lexema) No necesitamos ningn tratamiento para sum ya que metacomp aade por defecto el atributo lexema u n (que es el que hemos usado para calcular el valor de los enteros).

6.2.

Esquema de traduccin o

Las secciones de esquema de traduccin contienen las reglas de la GPDR utilizando una sintaxis o muy similar a la de la asignatura. Cada regla tiene una parte izquierda, un s mbolo ->, una parte derecha y un punto y coma. Los no terminales se escriben marcados mediante < y >. Los terminales tienen que coincidir con los declarados en la especicacin lxica2 . Se pueden emplear los operao e dores regulares de concatenacin (impl o cita), disyuncin (mediante |), opcionalidad (mediante ?), o clausura (mediante *) y clausura positiva (mediante +). Por ejemplo, la regla E se escribir en metacomp as a : <E> -> <T> ( sum <T> )*; Las acciones de las reglas se escriben encerradas entre arrobas (@) y sin n de l nea entre ellas. Para poder referirse a los terminales y no terminales, estos estn numerados por su orden a de aparicin en la parte derecha de la regla. El no terminal de la izquierda no tiene nmero y, o u si no hay ambigedad, la primera aparicin de cada s u o mbolo no necesita nmero. Si tenemos las u acciones de la regla anterior: E T 1 {v:= T 1 .v} (sum T 2 {si sum.lexema= + entonces v:= v+ T 2 .v si no v:= v T 2 .v n si}) { E .v:= v} podemos escribirlas en metacomp as :
2 Existe tambin la posibilidad de utilizar una sintaxis especial para categor e as con un slo lexema, pero no lo o comentaremos.

T (sum T )

Anlisis semntico a a

19

<E> -> <T> @v= T.v@ ( sum <T> @if sum.lexema=="+":@ @ v+= T2.v@ @else:@ @ v-= T2.v@ )* @E.v= v@;

7.

Algunas aplicaciones

Vamos a aprovechar metacomp para reescribir los ejemplos que vimos en el tema de anlisis a lxico: la suma de las columnas de un chero con campos separados por tabuladores y la lectura e de cheros de conguracin. o

7.1.

Ficheros organizados por columnas

La representacin en metacomp de la especicacin lxica podr ser: o o e a


1 2 3 4

numero separacion nl None

valor None None None

[0-9]+ \t \n [ ]+

La funcin valor se dene en la zona de auxiliares junto con dos funciones para tratamiento o de errores:
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

% def valor(componente): componente.v= int(componente.lexema) def error(linea, mensaje): sys.stderr.write("Error en lnea %d: %s\n" % (linea, mensaje)) sys.exit(1) def error_lexico(linea, cadena): if len(cadena)> 1: error(linea, "no he podido analizar la cadena %s." % repr(cadena)) else: error(linea, "no he podido analizar el carcter %s." % repr(cadena)) a ncol= 0

La funcin error la utilizamos para escribir mensajes genricos. La funcin error_lexico es o e o llamada por metacomp cuando encuentra un error lxico. Los dos parmetros que recibe son la e a l nea donde se ha producido el error y el carcter o caracteres que no se han podido analizar. a Podemos emplear el siguiente esquema de traduccin: o Fichero L nea {suma:=0}( L nea nl {suma:= suma+ L nea .v}) { Fichero .suma:= suma}

numero1 {global ncol; col:=1; si col= ncol entonces L nea .v:= numero.v n si} (separacin nmero2 o u {col:= col+1; si col= ncol entonces L nea .v:= numero2 .v n si} )

c Universitat Jaume I 2006-2007

20

II26 Procesadores de lenguaje

Hemos supuesto que el s mbolo inicial tiene un atributo sintetizado que leer el entorno. Adea ms, la columna deseada est en la variable global ncol. Representamos esto en metacomp as a a :
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

% <Fichero>->

@suma= 0@ ( <Linea> @suma+= Linea.v@ nl )* @Fichero.suma= suma@ ;

<Fichero>-> error @error(mc_al.linea(), "lnea mal formada")@ ; <Linea>-> numero @col= 1@ @if col== ncol:@ @ Linea.v= numero.v@ ( separacion numero @col+= 1@ @if col== ncol:@ @ Linea.v= numero2.v@ )* @if col< ncol:@ @ error(numero.nlinea, "no hay suficientes columnas")@ ;

La regla <Fichero>-> error nos permite capturar los errores que se produzcan durante el anlisis. a Hay varias maneras de tratar los errores, pero hemos optado por abortar el programa. Hemos utilizado mc_al, que contiene el analizador lxico, para averiguar el nmero de l e u nea del error. Finalmente, hemos decidido que el programa tenga un parmetro que indique qu columna a e sumar. Si no se especica ningn parmetro adicional, se leer la entrada estndar. En caso de que u a a a haya varios parmetros, se sumar la columna deseada de cada uno de los cheros especicados, a a escribindose el total. Con esto, el programa principal es: e
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

% def main(): global ncol if len(sys.argv)== 1: sys.stderr.write("Error, necesito un nmero de columna.\n") u sys.exit(1) try: ncol= int(sys.argv[1]) except: sys.stderr.write("Error, nmero de columna mal formado.\n") u sys.exit(1) if len(sys.argv)== 2: A= AnalizadorSintactico(sys.stdin) suma= A.Fichero.suma else: suma= 0 for arg in sys.argv[2:]: try: f= open(arg) except:

Anlisis semntico a a

21

63 64 65 66 67

sys.stderr.write("Error, no he podido abrir el fichero %s.\n" % arg) sys.exit(1) A= AnalizadorSintactico(f) suma+= A.Fichero.suma print "La suma es %d." % suma

Por defecto, metacomp genera una funcin main que analiza sys.stdin. Si queremos modicar ese o comportamiento, debemos crear nuestra propia funcin. Como ves, la mayor parte del cdigo se o o dedica a analizar posibles errores en los parmetros. La construccin ms interesante est en las a o a a dos l neas que crean el analizador. La asignacin o A= AnalizadorSintactico(f) hace que se cree un analizador que inmediatamente analizar el chero f (que debe estar abierto). a Cuando termina el anlisis, el objeto A tiene un atributo con el nombre del s a mbolo inicial y que tiene sus atributos sintetizados. As es como transmitimos el resultado.

7.2.

Ficheros de conguracin o

Vamos ahora a programar el mdulo de lectura de los cheros de conguracin. Haremos que o o el resultado del anlisis sea un diccionario en el que las claves sean las variables y los valores a asociados sean los le dos del chero. El analizador lxico se escribir as e a :
1 2 3 4 5

variable igual valor nl None

None None None None None

[a-zA-Z][a-zA-Z0-9]* = [0-9]+|\"[^"\n]*\" (#[^\n]*)?\n [ \t]+

Los unicos auxiliares que necesitamos son las funciones de tratamiento de errores:
6 7 8 9 10 11 12 13 14 15

% def error(linea, mensaje): sys.stderr.write("Error en lnea %d: %s\n" % (linea, mensaje)) sys.exit(1) def error_lexico(linea, cadena): if len(cadena)> 1: error(linea, "no he podido analizar la cadena %s." % repr(cadena)) else: error(linea, "no he podido analizar el carcter %s." % repr(cadena)) a Nuestro esquema de traduccin ser: o a Fichero L nea {dic:= {}}(( L nea {dic[ L nea .izda]:= L nea .dcha}|) nl) { Fichero .dic:= dic}

variable { L nea .izda:= variable.lexema } igual valor { L nea .dcha:= valor.lexema}

Pasado a metacomp:
16 17 18 19 20 21

% <Fichero>->

@dic= {}@ ( (<Linea> @dic[Linea.izda]= Linea.dcha@)? nl )* @Fichero.dic= dic@ ;

<Fichero>-> error
c Universitat Jaume I 2006-2007

22

II26 Procesadores de lenguaje

22 23 24 25 26 27 28 29 30

@error(mc_al.linea(), "lnea mal formada")@ ; <Linea>-> variable @Linea.izda= variable.lexema@ igual valor @Linea.dcha= valor.lexema@ ; Como ves, hemos utilizado el operador de opcionalidad para representar la disyuncin ( Linea |). o

8.

Resumen del tema


El analizador semntico tiene dos objetivos: a Hacer comprobaciones que no se hagan durante el anlisis lxico o sintctico. a e a Crear una representacin adecuada para fases posteriores. o Implementaremos el anlisis semntico en dos partes: a a Mediante esquemas de traduccin dirigidos por la sintaxis. o Recorriendo el AST. Un esquema de traduccin dirigido por la sintaxis aade a las gramticas: o n a Acciones intercaladas en las partes derechas de las reglas. Atributos asociados a los no terminales. Dos tipos de atributos: heredados y sintetizados. Las acciones deben garantizar que se evalan correctamente los atributos. u Se pueden implementar los esquemas de traduccin sobre los analizadores sintcticos intero a pretando los atributos como parmetros y aadiendo el cdigo de las acciones al cdigo del a n o o analizador. El clculo de algunos atributos y algunas comprobaciones semnticas son ms fciles sobre a a a a el AST. La tabla de s mbolos se puede implementar ecientemente mediante una tabla hash. Las comprobaciones de tipos se complican si se introducen nombres. Dos criterios: Equivalencia de nombre. Equivalencia estructural. La interpretacin se puede realizar mediante recorridos del AST. o metacomp permite implementar cmodamente esquemas de traduccin dirigidos por la sino o taxis.

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