Está en la página 1de 151

Ingeniera Infor-

mtica


Anlisis
Semntico en
Procesadores de
Lenguaje







Cuaderno N 38




Francisco Ortn Soler
Juan Manuel Cueva Lovelle
Maria Cndida Luengo Dez
Aquilino Adolfo Juan Fuente
Jos Emilio Labra Gayo
Ral Izquierdo Castanedo

Lenguajes y Sistemas Informticos
Departamento de Informtica
Universidad de Oviedo

Oviedo, Marzo 2004



Cuaderno N 38

ANLISIS SEMNTICO EN PROCESADORES DE
LENGUAJE


Autorers:
Francisco Ortn Soler
Juan Manuel Cueva Lovelle
Maria Cndida Luengo Dez
Aquilino Adolfo Juan Fuente
Jos Emilio Labra Gayo
Ral Izquierdo Castanedo

Universidad de Oviedo - Espaa




Editorial:

SERVITEC

ISBN: 84-688-6208-8
Deposito Legal: AS-1358-04




P PR R L LO OG GO O
El objetivo de este libro es introducir los conceptos necesarios sobre la fase de an-
lisis semntico en procesadores de lenguaje, para un curso universitario de traductores,
compiladores e intrpretes: procesadores de lenguajes de programacin.
Est principalmente dirigido a alumnos de cuarto curso de Ingeniera Informtica,
aunque cualquier persona con conocimientos bsicos de teora de lenguajes y gramticas,
as como el conocimiento de algn lenguaje de programacin orientado a objetos como
Java o C++ est capacitado para seguir su contenido.
Para facilitar la labor docente del mismo, los conceptos introducidos han sido ilus-
trados con un conjunto importante de ejemplos. Asimismo, al final del libro se ha aadido
un captulo de cuestiones de revisin y otro de ejemplos propuestos. El objetivo principal
de estos dos puntos es fijar los conocimientos adquiridos y enfatizar los puntos ms impor-
tantes.
El libro se compone de los siguientes puntos:
Inicialmente se definen los conceptos bsicos a emplear a lo largo de todo el
texto.
El primer punto es una introduccin somera a la especificacin de la semntica
de lenguajes de programacin. Aunque la principal tarea de este texto es cen-
trarnos en el anlisis semntico de lenguajes y no en su semntica, introducire-
mos este concepto por la relacin que posee con las gramticas atribuidas.
El segundo captulo es el que muestra el contexto del anlisis semntico dentro
del marco de los procesadores de lenguaje. Detalla los objetivos principales de
ste, as como la interaccin de esta fase con el resto.
El captulo 3 introduce el mecanismo ms empleado a la hora de definir anali-
zadores semnticos de procesadores de lenguajes: las gramticas atribuidas (de-
finiciones dirigida por sintaxis).
El siguiente captulo profundiza en las caractersticas ms importantes de las
gramticas atribuidas, empleadas para la implementacin de un evaluador.
El punto cuarto de este libro muestra cmo pueden evaluarse las gramticas
atribuidas. Se basa en los conceptos y clasificaciones expuestas en el captulo
anterior, ahondando en cmo, en funcin del tipo de gramtica atribuida, po-
dremos implementar sta, empleando distintas tcnicas.
El captulo 6 detalla la parte principal de prcticamente la mayora de los anali-
zadores semnticos: la comprobacin de tipos. Define los conceptos necesarios
e indica los objetivos y problemas que deber solventar un procesador de len-
guaje.
Una vez concluidos los captulos, cuestiones y ejercicios propuestos, se presenta
un conjunto de apndices en los que se detalla el cdigo fuente empleado en los

ejemplos de implementacin, presentados a lo largo del texto. stos tambin
podrn ser descargados de la URL mostrada al final de este prlogo.
Finalmente se indica la lista de referencias bibliogrficas principales empleadas para
escribir este texto. Podrn servir al lector como un mecanismo para ampliar los contenidos
aqu introducidos.
Por la amplia bibliografa existente en el idioma ingls adems de lo presente en
Internet se ha considerado oportuno hacer empleo de notas al pie de pgina para indicar,
entre otras cosas, la traduccin de los trminos principales.
Para concluir este prlogo, el cdigo fuente empleado en el texto se encuentra en
mi pgina personal, as como mi direccin de correo electrnico para posibles mejoras o
comentarios:
http://www.di.uniovi.es/~ortin

CONTENIDO
Anlisis Semntico en Procesadores de Lenguaje.................................................................................. 1
1 Especificacin Semntica de Lenguajes de Programacin ..............................................................5
1.1. Especificacin Formal de Semntica............................................................................................5
2 Tareas y Objetivos del Anlisis Semntico........................................................................................9
2.1. Ejemplos de Comprobaciones Realizadas por el Analizador Semntico .................................. 10
Declaracin de identificadores y reglas de mbitos ......................................................................................... 10
Comprobaciones de unicidad.............................................................................................................................. 11
Comprobaciones de enlace.................................................................................................................................. 11
Comprobaciones pospuestas por el analizador sintctico............................................................................... 12
Comprobaciones dinmicas ................................................................................................................................ 12
Comprobaciones de tipo ..................................................................................................................................... 12
2.2. Anlisis Semntico como Decoracin del AST.......................................................................... 13
rbol de sintaxis abstracta................................................................................................................................... 13
Decoracin del AST............................................................................................................................................. 17
3 Gramticas Atribuidas...................................................................................................................... 21
3.1. Atributos....................................................................................................................................... 21
3.2. Reglas Semnticas....................................................................................................................... 22
3.3. Gramticas Atribuidas................................................................................................................. 23
3.4. Gramticas Atribuidas en Anlisis Semntico............................................................................ 28
4 Tipos de Gramticas Atribuidas ...................................................................................................... 33
4.1. Atributos Calculados en una Produccin.................................................................................... 33
4.2. Gramtica Atribuida Completa................................................................................................... 33
4.3. Gramtica Atribuida Bien Definida............................................................................................ 37
4.4. Gramticas S-Atribuidas ............................................................................................................. 38
4.5. Gramticas L-Atribuidas............................................................................................................. 38
4.6. Traduccin de Gramticas S-Atribuidas a L-Atribuidas............................................................ 40
5 Evaluacin de Gramticas Atribuidas ............................................................................................. 43
5.1. Grafo de Dependencias ............................................................................................................... 43
Ordenamiento topolgico del grafo de dependencias..................................................................................... 46
Evaluacin de una gramtica atribuida .............................................................................................................. 49
5.2. Evaluacin de Atributos en una Pasada ..................................................................................... 50
Evaluacin ascendente de gramticas S-atribuidas .......................................................................................... 51
Evaluacin descendente de gramticas L-atribuidas........................................................................................ 51
Evaluacin ascendente de atributos heredados................................................................................................ 54
5.3. Evaluacin de Atributos en Varias Pasadas ............................................................................... 56
Recorrido del AST................................................................................................................................................ 57
Evaluacin de gramticas S-atribuidas............................................................................................................... 63
Evaluacin de gramticas L-atribuidas .............................................................................................................. 64
Otras evaluaciones con una nica visita ............................................................................................................ 65
Evaluacin de gramticas atribuidas bien definidas......................................................................................... 66
5.4. Rutinas Semnticas y Esquemas de Traduccin ....................................................................... 68
6 Comprobacin de Tipos................................................................................................................... 73
6.1. Beneficios del Empleo de Tipos ................................................................................................. 74

6.2. Definicin de Tipo ...................................................................................................................... 75
6.3. Expresin de Tipo....................................................................................................................... 76
Implementacin.....................................................................................................................................................81
6.4. Sistema de Tipos ......................................................................................................................... 85
6.5. Comprobacin Esttica y Dinmica de Tipos ........................................................................... 93
6.6. Equivalencia de Expresiones de Tipo........................................................................................ 97
6.7. Conversin y Coercin de Tipos............................................................................................... 102
6.8. Sobrecarga y Polimorfismo ....................................................................................................... 108
6.9. Inferencia de Tipos ....................................................................................................................112
Cuestiones de Revisin.........................................................................................................................115
Ejercicios Propuestos ...........................................................................................................................117
A Evaluacin de un AST.................................................................................................................... 123
A.1 Implementacin del AST ..................................................................................................... 123
ast.h ...................................................................................................................................................................... 123
A.2 Visitas del AST ..................................................................................................................... 124
visitor.h ................................................................................................................................................................ 124
visitorsemantico.h............................................................................................................................................... 124
visitorsemantico.cpp .......................................................................................................................................... 124
visitorgc.h ............................................................................................................................................................ 125
visitorgc.cpp ........................................................................................................................................................ 125
visitorcalculo.h.................................................................................................................................................... 126
visitorcalculo.cpp................................................................................................................................................ 126
visitormostrar.h................................................................................................................................................... 126
visitormostrar.cpp............................................................................................................................................... 126
A.3 Especificacin Lxica y Sintctica del Lenguaje ................................................................ 127
sintac.y.................................................................................................................................................................. 127
lexico.l .................................................................................................................................................................. 128
B Evaluacin de una Gramtica L-Atribuida mediante un Analizador Descendente Recursivo.... 129
B.1 Mdulo Sintctico ................................................................................................................ 129
Sintactico.java...................................................................................................................................................... 129
B.2 Mdulo Lxico ......................................................................................................................131
Atributo.java........................................................................................................................................................ 131
Lexico.java........................................................................................................................................................... 131
B.3 Mdulo Errores .................................................................................................................... 132
Error.java............................................................................................................................................................. 132
C Comprobador de Tipos .................................................................................................................. 135
C.1 Expresiones de Tipo ............................................................................................................ 135
tipos.h................................................................................................................................................................... 135
tipos.cpp .............................................................................................................................................................. 137
ts.h ........................................................................................................................................................................ 139
C.2 Sistema y Comprobador de Tipos........................................................................................ 139
sintac.y.................................................................................................................................................................. 139
lexico.l .................................................................................................................................................................. 141
Referencias Bibliogrficas ................................................................................................................... 143

1
A AN N L LI IS SI IS S S SE EM M N NT TI IC CO O E EN N P PR RO OC CE ES SA AD DO OR RE ES S D DE E L LE EN NG GU UA AJ JE E
La fase de anlisis semntico de un procesador de lenguaje es aqulla que computa
la informacin adicional necesaria para el procesamiento de un lenguaje, una vez que la
estructura sintctica de un programa haya sido obtenida. Es por tanto la fase posterior a la
de anlisis sintctico y la ltima dentro del proceso de sntesis de un lenguaje de programa-
cin [Aho90].
Sintaxis de un lenguaje de programacin es el conjunto de reglas formales que espe-
cifican la estructura de los programas pertenecientes a dicho lenguaje. Semntica de un len-
guaje de programacin es el conjunto de reglas que especifican el significado de cualquier
sentencia sintcticamente vlida. Finalmente, el anlisis semntico
1
de un procesador de
lenguaje es la fase encargada de detectar la validez semntica de las sentencias aceptadas por
el analizador sintctico.
Ejemplo 1:Dado el siguiente ejemplo de cdigo en C:

superficie = base * altura / 2;
La sintaxis del lenguaje C indica que las expresiones se pueden formar con un conjunto de
operadores y un conjunto de elementos bsicos. Entre los operadores, con sintaxis binaria
infija, se encuentran la asignacin, el producto y la divisin. Entre los elementos bsicos de
una expresin existen los identificadores y las constantes enteras sin signo (entre otros).
Su semntica identifica que en el registro asociado al identificador superficie se le va a aso-
ciar el valor resultante del producto de los valores asociados a base y altura, divididos
por dos (la superficie de un tringulo).
Finalmente, el anlisis semntico del procesador de lenguaje, tras haber analizado correcta-
mente que la sintaxis es vlida, deber comprobar que se satisfacen las siguientes condicio-
nes:
Que todos los identificadores que aparecen en la expresin hayan sido declara-
dos en el mbito actual, o en alguno de sus mbitos (bloques
2
) previos.
Que la subexpresin de la izquierda sea semnticamente vlida, es decir, que sea
un lvalue
3
.
Que a los tipos de los identificadores base y altura se les pueda aplicar el
operador de multiplicacin. Un registro en C, por ejemplo, no sera vlido.

1
Semantic Analysis o Contextual Analysis.
2
El lenguaje C es un lenguaje orientado a bloques. Los bloques se especifican mediante la pareja de
caracteres { y }. Dentro de un bloque, es posible declarar variables que ocultan a las variables declaradas
en bloques de un nivel menor de anidamiento.
3
Una expresin es un lvalue (left value, valor a la izquierda) si puede estar a la izquierda en una expre-
sin de asignacin, es decir, si se puede obtener su direccin de memoria y modifica el contenido de sta.
Otros ejemplos de expresiones lvalue en C, son: *puntero, array[n], registro.campo...
Anlisis Semntico en Procesadores de Lenguaje
2
Deber inferirse el tipo resultante de la multiplicacin anterior. Al tipo inferido
se le deber poder aplicar el operador de dividir, con el tipo entero como multi-
plicando.
Deber inferirse el tipo resultante de la divisin y comprobarse si ste es com-
patible con el tipo de superficie para llevar a cabo la asignacin. Como
ejemplo, si superficie fuese entera y division real, no podra llevarse a
cabo la asignacin.

La fase de anlisis semntico obtiene su nombre por requerir informacin relativa al
significado del lenguaje, que est fuera del alcance de la representatividad de las gramticas
libres de contexto y los principales algoritmos existentes de anlisis; es por ello por lo que
se dice que captura la parte de la fase de anlisis considerada fuera del mbito de la sintaxis.
Dentro del la clasificacin jerrquica que Chomsky dio de los lenguajes [Hopcroft02, Cue-
va03], la utilizacin de gramticas sensibles al contexto (o de tipo 1) permitiran identificar
sintcticamente caractersticas como que la utilizacin de una variable en el lenguaje Pascal
ha de estar previamente declarada. Sin embargo, la implementacin de un analizador sintc-
tico basado en una gramtica de estas caractersticas sera computacionalmente ms com-
pleja que un autmata de pila [Louden97].
As, la mayora de los compiladores utilizan una gramtica libre de contexto para
describir la sintaxis del lenguaje y una fase de anlisis semntico posterior para restringir las
sentencias que semnticamente no pertenecen al lenguaje. En el caso que mencionba-
mos del empleo de una variable en Pascal que necesariamente haya tenido que ser declara-
da, el analizador sintctico se limita a comprobar, mediante una gramtica libre de contexto,
que un identificador forma parte de una expresin. Una vez comprobado que la sentencia
es sintcticamente correcta, el analizador semntico deber verificar que el identificador
empleado como parte de una expresin haya sido declarado previamente. Para llevar a cabo
esta tarea, es tpica la utilizacin de una estructura de datos adicional denominada tabla de
smbolos. sta poseer una entrada por cada identificador declarado en el contexto que se
est analizando. Con este tipo de estructuras de datos adicionales, los desarrolladores de
compiladores acostumbran a suplir las carencias de las gramticas libres de contexto.
Otro caso que se da en la implementacin real de compiladores es ubicar determi-
nadas comprobaciones en el analizador semntico, aun cuando puedan ser llevadas a cabo
por el analizador sintctico. Es factible describir una gramtica libre de contexto capaz de
representar que toda implementacin de una funcin tenga al menos una sentencia
return. Sin embargo, la gramtica sera realmente compleja y su tratamiento en la fase de
anlisis sintctico sera demasiado complicada. As, es ms sencillo transferir dicha respon-
sabilidad al analizador semntico que slo deber contabilizar el nmero de sentencias
return aparecidas en la implementacin de una funcin.
El objetivo principal del analizador semntico de un procesador de lenguaje es ase-
gurarse de que el programa analizado satisfaga las reglas requeridas por la especificacin del
lenguaje, para garantizar su correcta ejecucin. El tipo y dimensin de anlisis semntico
requerido vara enormemente de un lenguaje a otro. En lenguajes interpretados como Lisp
o Smalltalk casi no se lleva a cabo anlisis semntico previo a su ejecucin, mientras que en
lenguajes como Ada, el analizador semntico deber comprobar numerosas reglas que un
programa fuente est obligado a satisfacer.
Vemos, pues, cmo el anlisis semntico de un procesador de lenguaje no modela la
semntica o comportamiento de los distintos programas construidos en el lenguaje de pro-
gramacin, sino que, haciendo uso de informacin parcial de su comportamiento, realiza
Anlisis Semntico en Procesadores de Lenguaje
3
todas las comprobaciones necesarias no llevadas a cabo por el analizador sintctico para
asegurarse de que el programa pertenece al lenguaje. Otra fase del compilador donde se
hace uso parcial de la semntica del lenguaje es en la optimizacin de cdigo, en la que ana-
lizando el significado de los programas previamente a su ejecucin, se pueden llevar a cabo
transformaciones en los mismos para ganar en eficiencia.

5
1 Especificacin Semntica de Lenguajes de
Programacin
Existen dos formas de describir la semntica de un lenguaje de programacin: me-
diante especificacin informal o natural y formal.
La descripcin informal de un lenguaje de programacin es llevada a cabo mediante
el lenguaje natural. Esto hace que la especificacin sea inteligible (en principio) para cual-
quier persona. La experiencia nos dice que es una tarea muy compleja, si no imposible, el
describir todas las caractersticas de un lenguaje de programacin de un modo preciso.
Como caso particular, vase la especificacin del lenguaje ISO/ANSI C++ [ANSIC++].
La descripcin formal de la semntica de lenguajes de programacin es la descrip-
cin rigurosa del significado o comportamiento de programas, lenguajes de programacin,
mquinas abstractas o incluso cualquier dispositivo hardware. La necesidad de hacer especi-
ficaciones formales de semntica surge para [Nielson92, Watt96, Labra03]:
Revelar posibles ambigedades existentes implementaciones de procesadores de
lenguajes o en documentos descriptivos de lenguajes de programacin.
Ser utilizados como base para la implementacin de procesadores de lenguaje.
Verificar propiedades de programas en relacin con pruebas de correccin o in-
formacin relacionada con su ejecucin.
Disear nuevos lenguajes de programacin, permitiendo registrar decisiones
sobre construcciones particulares del lenguaje, as como permitir descubrir po-
sibles irregularidades u omisiones.
Facilitar la comprensin de los lenguajes por parte del programador y como
mecanismo de comunicacin entre diseador del lenguaje, implementador y
programador. La especificacin semntica de un lenguaje, como documento de
referencia, aclara el comportamiento del lenguaje y sus diversas construcciones.
Estandarizar lenguajes mediante la publicacin de su semntica de un modo no
ambiguo. Los programas deben poder procesarse en otra implementacin de
procesador del mismo lenguaje exhibiendo el mismo comportamiento.
1.1. Especificacin Formal de Semntica
Si bien la especificacin formal de la sintaxis de un lenguaje se suele llevar a cabo
mediante la descripcin estndar de su gramtica en notacin BNF (Backus-Naur Form), en
el caso de la especificacin semntica la situacin no est tan clara; no hay ningn mtodo
estndar globalmente extendido.
El comportamiento de las distintas construcciones de un lenguaje de programacin,
puede ser descrito desde distintos puntos de vista. Una clasificacin de los principales m-
Anlisis Semntico en Procesadores de Lenguaje
6
todos formales de descripcin semntica, as como una descripcin muy breve de las ideas
en las que se fundamentan, es [Nielson92, Labra01]:
Semntica operacional
4
: El significado de cada construccin sintctica es es-
pecificado mediante la computacin que se lleva a cabo en su ejecucin sobre
una mquina abstracta. Lo que realmente se especifica es cmo se lleva a cabo di-
cha ejecucin. Los significados del programa son descritos en trminos de ope-
raciones, utilizando un lenguaje basado en reglas de inferencia lgicas en las que
se describen formalmente las secuencias de ejecucin de las diferentes instruc-
ciones sobre una mquina abstracta [Nielson92]. Es muy cercano a la imple-
mentacin y se puede emplear para construir prototipos de procesadores de
lenguajes como la descripcin de PL/I en VDL [Lucas69].
Ejemplo 2. Lo siguiente es la especificacin formal de la semntica de una asignacin en un
lenguaje de programacin:

{ } v x e x
v e
a =



) : (
) (

Lo que se encuentra en la parte superior es una premisa y en la parte inferior una con-
clusin. La premisa indica que el resultado de evaluar una expresin e en un determinado
almacn (estado de una mquina abstracta) produce un valor v. La conclusin indica que,
dado un estado , la asignacin de una expresin e a un identificador x produce un nuevo
estado resultado de aadir a la asociacin del valor de v al identificador x.

Semntica denotacional
5
. La representacin del comportamiento de cada sen-
tencia o frase del lenguaje se lleva a cabo mediante entidades matemticas (de-
notacin) que representan el efecto de haber ejecutado las sentencia o frase aso-
ciada [Watt96]. Por tanto, se hace ms hincapi en el efecto de la computacin
que en cmo se lleva a cabo. Se utiliza mayoritariamente en diseo de lenguajes
de programacin y se ha empleado para especificar la semntica completa de
lenguajes como Ada, Algol-60 y Pascal [Bjorner82]
Ejemplo 3. La especificacin de una asignacin en semntica denotacional es:

[ ] [ ] [ ] [ ] { } ) ( ) ( : s e v s s e v
E S
a = =
Los doble corchetes con un subndice indican una funcin semntica. En nuestro ejemplo,
se est definiendo parcialmente la que posee el subndice S (sentencia
6
); la que posee el
subndice E (evaluacin de una expresin) est definida en otra parte. As, la especificacin
que tenemos arriba denota la semntica de la sentencia de asignacin de cualquier identifi-
cador v a una expresin e. sta es aplicada a un estado s y devuelve el mismo estado am-
pliado con la asociacin a v del resultado de evaluar la expresin e.

Semntica axiomtica
7
. Especifica las propiedades del efecto de ejecutar las
sentencias sintcticamente correctas, expresadas mediante asertos, desoyendo
as los aspectos de su ejecucin. El sistema permite estudiar formalmente las
propiedades del lenguaje y se requiere la utilizacin de sistemas consistentes y

4
Operational semantics.
5
Denotational semantics, inicialmente denominada mathematical semantics.
6
La S viene de statement (sentencia).
7
Axiomatic semantics.
Especificacin Semntica de Lenguajes de Programacin
7
completos [Hoare73]. Se utiliza mayoritariamente en verificacin formal de co-
rreccin de programas.
Ejemplo 4. La siguiente especificacin de la semntica de una sentencia de asignacin ha sido
descrita utilizando la semntica axiomtica:

[ ] { } { } P e x e x P = :
En la semntica axiomtica, se antepone al fragmento sintctico (asignacin) la precondi-
cin que indica la estado que se satisface previamente a la asignacin. Este estado, llamado
genricamente P, indica que todas las ocurrencias de x estn textualmente sustituidas por la
expresin e. Una vez llevada a cabo la asignacin, la postcondicin nos indica el estado que
se satisface tras su evaluacin. Ejemplos de satisfaccin de predicados son:

{ } { }
{ } { }
{ } { } 10 1 2 * 10 1 2
2 1 : 2 1
2 2 : 2 2
> + = > +
= + = = +
= = =
x y x y
x n x n
x x

Las especificaciones axiomticas tambin se denominan tripletas de Hoare en honor a su
creador. Como se muestra en el ejemplo, la derivacin de estas tripletas es llevada a cabo de
la postcondicin hacia la precondicin siguiendo un razonamiento hacia atrs.

Semntica algebraica
8
. Se basa en la especificacin de tipos de datos abstrac-
tos mediante una coleccin de operaciones (incluyendo alguna constante). Pues-
to que un conjunto de valores al que se le aaden una coleccin de operaciones
constituye un lgebra, este mtodo de descripcin formal de semntica se de-
nomina semntica algebraica [Meinke92]. Este mtodo est pues enfocado a es-
pecificar la semntica de los tipos y sus operaciones. La semntica algebraica
constituye tambin la base de la semntica de acciones, empleada para especifi-
car la semntica de lenguajes de programacin al completo.
Ejemplo 5. La especificacin del tipo lgico (booleano) en un lenguaje de programacin puede
llevarse a cabo del siguiente modo, siguiendo la semntica algebraica:

specification Truth-Values
sort Truth-Value
operations
true : Truth-Value
false : Truth-Value
not_ : Truth-Value Truth-Value
__ : Truth-Value, Truth-Value Truth-Value
__ : Truth-Value, Truth-Value Truth-Value
variables t, u: Truth-Value
equtations
not true = false
not false = true
t true = t
t false = false
t u = u t
t true = true
t false = t
t u = u t
end specification


8
Algegraic semantics.
Anlisis Semntico en Procesadores de Lenguaje
8
Semntica de acciones
9
. Fue elaborado por Peter Mosses [Mosses91] para
describir la semntica de lenguajes de un modo ms inteligible. Las especifica-
ciones semnticas de lenguajes siempre han sido consideradas como oscuras,
complicadas y nicamente legibles por expertos, adquiriendo as una mala repu-
tacin por su uso intensivo de smbolos matemticos [Watt96]. De este modo,
esta semntica est basada en el concepto de acciones que reflejan las operaciones
comunes en los lenguajes de programacin, ofreciendo primitivas para la asig-
nacin y declaracin de identificadores, as como la combinacin de instruccio-
nes mediante control de flujo secuencial, condicional e iterativo.
Otro modo de especificar formalmente lenguajes de programacin es mediante el
uso de gramticas atribuidas
10
. Las gramticas atribuidas asignan propiedades (atributos)
a las distintas construcciones sintcticas del lenguaje. Estos atributos pueden describir in-
formacin semntica para implementar un analizador semntico (como por ejemplo el tipo
de una expresin), pero pueden emplearse tambin para representar cualquier otra propie-
dad como la evaluacin de una expresin o incluso su traduccin a una determinada plata-
forma. Al no estar directamente ligadas al comportamiento dinmico (en ejecucin) de los
programas, no suelen clasificarse como otro tipo de especificacin formal de semntica de
lenguajes. Sin embargo, su uso tan verstil hace que estn, de un modo directo o indirecto,
en cualquier implementacin de un procesador de lenguaje.


9
Action semantics.
10
Attribute grammar. Posiblemente, una mejor traduccin podra ser gramticas con atributos, pero la
amplia extensin del trmino gramtica atribuida hace que utilicemos la segunda traduccin.
9
2 Tareas y Objetivos del Anlisis Semntico
Como comentbamos al comienzo de este libro, el anlisis semntico
11
de un pro-
cesador de lenguaje es la fase encargada de detectar la validez semntica de las sentencias
aceptadas por el analizador sintctico. Tambin comentbamos cmo, de un modo con-
vencional y menos formal, se suele afirmar que la sintaxis de un lenguaje de programacin
es aquella parte del lenguaje que puede ser descrita mediante una gramtica libre de contex-
to, mientras que el anlisis semntico es la parte de su especificacin que no puede ser des-
crita por la sintaxis [Scott00].
En el diagrama de fases de un compilador [Aho90] podemos destacar, a raz de las
dos definiciones previas, una mayor interconexin entre la fase de anlisis semntico y las
siguientes fases de un compilador:
Analizador
Lxico
Analizador
Sintctico
Analizador
Semntico
Generador
de Cdigo
Intermedio
Optimizador
de Cdigo
Generador
de Cdigo
Manejador
de Errores
Tabla de
Smbolos
Cdigo objeto
Cdigo Optimizado
Cdigo fuente
AST
Tokens
AST decorado
Cdigo Intermedio
Errores
Semnticos
Insercin y
Bsqueda de
Smbolos

Figura 1: Fases de un compilador, destacando la interaccin con el anlisis semntico.

11
Semantic Analysis o Contextual Analysis.
Anlisis Semntico en Procesadores de Lenguaje
10
Anlisis sintctico. Como se muestra en la Figura 1, la entrada del analizador
semntico es la salida generada por el analizador sintctico. La estructura em-
pleada para intercambiar la informacin entre estas dos fases es lo que se cono-
ce como rbol sintctico o una simplificacin del mismo, denominada rbol
sintctico abstracto ( 2.2). Una vez validada la sintaxis de un programa, el an-
lisis semntico aplicar reglas semnticas para validar dicho rbol.
Manejador de errores. Si la validacin del rbol sintctico descrita en el prrafo
anterior no fuese satisfactoria, es decir, existiese un error semntico, la fase de
anlisis semntico debera notificar dicho error al manejador de errores para que
ste se encargase de su gestin. El proceso de anlisis podra seguir ejecutndo-
se o no, en funcin de si el procesador de lenguaje implementa algn mecanis-
mo de recuperacin de errores [Louden97].
Generacin de cdigo (intermedio). La salida del anlisis semntico se suele
emplear como entrada para la generacin de cdigo
12
. La estructura de datos
empleada para intercambiar informacin entre las dos fases mencionadas es un
rbol sintctico decorado ( 2.2). Este rbol posee informacin adicional al rbol
generado por el analizador sintctico, como por ejemplo la informacin relativa
al tipo de cada una de las expresiones del programa. El empleo de dicha infor-
macin es til para llevar a cabo el proceso de generacin de cdigo (a bajo ni-
vel, el tipo de una expresin es necesario, por ejemplo, para saber el nmero de
bytes que ocupa su valor).
Tabla de smbolos. Como hemos mencionado previamente, la utilizacin de
gramticas libres de contexto (de tipo 2) no permite expresar caractersticas re-
presentables con gramticas sensibles al contexto como la necesidad de que la
utilizacin de una variable en el lenguaje Pascal requiera la declaracin previa de
la variable utilizada. Para poder implementar un procesador del lenguaje Pascal
empleando gramticas de tipo 2 e implementaciones de autmatas de pila, es
necesario emplear una estructura de datos auxiliar denominada tabla de smbo-
los. Esta estructura de datos, a su nivel ms bsico, es un diccionario (memoria
asociativa) que asocia identificadores a la informacin requerida por el compila-
dor. Sus dos operaciones bsicas son insertar y buscar. En nuestro ejem-
plo, la declaracin de un identificador en Pascal requerir una insercin del
mismo en la tabla de smbolos; cada vez que se utilice un identificador en una
sentencia, el analizador semntico buscar ste en la tabla de smbolos (llaman-
do al manejador de errores si no existiere).
2.1. Ejemplos de Comprobaciones Realizadas por el Analizador
Semntico
Existen multitud de ejemplos reales de comprobaciones llevadas a cabo por el ana-
lizador semntico de un procesador de lenguaje. Describiremos algunos de ellos a modo de
ejemplo.
Declaracin de identificadores y reglas de mbitos
En el primer ejemplo de este libro, analizbamos la siguiente sentencia en C:


12
Un compilador no suele generar el cdigo destino directamente a una plataforma especfica, sino que
utiliza un mecanismo intermedio de representacin de cdigo [Cueva98].
Tareas y Objetivos del Anlisis Semntico
11
superficie = base * altura / 2;
Para que la asignacin previa fuese correcta, los tres identificadores deberan de es-
tar declarados. Puede que estn declarados en el mbito (bloque) actual o en uno menos
anidado que el actual, en cuyo caso el analizador sintctico tendra que aplicar reglas de
mbito como la ocultacin de identificadores.
Ejemplo 6. Si enmarcamos la sentencia anterior en el siguiente programa C:

#include <stdio.h>
int main() {
double base=2.5, altura=10;
{
double superficie, altura = 1;
superficie = base * altura / 2;
printf("%lf", superficie);
}
printf("%lf", superficie);
return 0;
}
El primer printf es correcto, pero no el segundo. En el primer caso, todas los identifica-
dores de la asignacin estn declarados: superficie y altura (con valor 1) estn decla-
rados en el mbito actual (altura oculta al identificador con valor 10, puesto que est ms
anidado) y el valor mostrado es 1.25. Sin embargo, el segundo printf no es correcto
puesto que la superficie del tringulo no ha sido declarada.

Comprobaciones de unicidad
Existen multitud de elementos en lenguajes de programacin cuyas entidades han
de existir de un modo nico, es decir, no se permite que estn duplicadas. Ejemplos tpicos
son:
Constantes de cada case en Pascal, C o Java. Cada uno de los elementos exis-
tentes en los condicionales mltiples de los lenguajes de programacin mencio-
nados, ha de ser nico. En otro caso, el analizador semntico deber generar un
error de compilacin.
Los valores de un tipo enumerado de Pascal o C han de ser nicos.
Las etiquetas de un lenguaje de programacin, como un ensamblador, no pue-
den estar repetidas, puesto que los saltos a las mismas seran ambiguos.
La declaracin de un identificador en un mbito ha de ser nica en multitud de
lenguajes de programacin.
Comprobaciones de enlace
13

En ocasiones, el empleo de un elemento de un lenguaje ha de estar ligado a una uti-
lizacin previa del mismo:
En un ensamblador, un salto a una etiqueta requiere que sta haya sido referida
como una posicin de memoria.
En el lenguaje de programacin ANSI C [Kernighan91] y en ISO/ANSI C++
[ANSIC++] la invocacin a una funcin o mtodo requiere que stos hayan si-
do declarados previamente.

13
Binding.
Anlisis Semntico en Procesadores de Lenguaje
12
La especificacin de mbitos para determinadas estructuras de control, en de-
terminados lenguajes como BASIC, requiere la utilizacin pareja de palabras re-
servadas como IF / END IF, FOR ID / NEXT ID o SUB / END SUB.
Comprobaciones pospuestas por el analizador sintctico
A la hora de implementar un procesador de un lenguaje de programacin, es comn
encontrarse con situaciones en las que una gramtica libre de contexto puede representar
sintcticamente propiedades del lenguaje; sin embargo, la gramtica resultante es compleja y
difcil de procesar en la fase de anlisis sintctico. En estos casos es comn ver cmo el
desarrollador del compilador escribe una gramtica ms sencilla que no representa detalles
del lenguaje, aceptndolos como vlidos cuando realmente no pertenecen al lenguaje. En la
posterior fase de anlisis semntico ser donde se comprueben aquellas propiedades del
lenguaje que, por sencillez, no fueron verificadas por el analizador sintctico.
Hay multitud de escenarios de ejemplo y estn en funcin de la implementacin de
cada procesador. Sin embargo, los siguientes suelen ser comunes:
Es posible escribir una gramtica libre de contexto capaz de representar que to-
da implementacin de una funcin en C tenga al menos una sentencia return.
No obstante, si escribimos la gramtica de cualquier funcin como una repeti-
cin de sentencias, siendo return es un tipo de sentencia, la gramtica es ms
sencilla y fcil de procesar. El analizador semntico deber comprobar, pues,
dicha restriccin.
Cuando un lenguaje posee el operador de asignacin como una expresin y no
como una sentencia (C y Java frente a Pascal), hay que comprobar que la expre-
sin de la parte izquierda de la asignacin posee una direccin de memoria en la
que se pueda escribir (lvalue). Esta restriccin puede ser comprobada por el ana-
lizador semntico, permitiendo sintcticamente que cualquier expresin se en-
cuentre en la parte izquierda del operador de asignacin.
Las sentencias break y continue de Java y C slo pueden utilizarse en de-
terminadas estructuras de control del lenguaje. ste es otro escenario para que
el analizador sintctico posponga la comprobacin hasta la fase anlisis semn-
tico.
Comprobaciones dinmicas
Todas las comprobaciones semnticas descritas en este punto suelen llevarse a cabo
en fase de compilacin y por ello reciben el nombre de estticas. Existen comprobacio-
nes que, en su caso ms general, slo pueden ser llevadas a cabo en tiempo de ejecucin y
por ello se llaman dinmicas. stas suelen ser comprobadas por un intrprete o por c-
digo de comprobacin generado por el compilador tambin puede darse el caso de que no
se comprueben. Diversos ejemplos pueden ser acceso a un vector fuera de rango, utiliza-
cin de un puntero nulo o divisin por cero.
Analizaremos ms en detalle este tipo de comprobaciones en 6.5.
Comprobaciones de tipo
Sin duda, este tipo de comprobaciones es el ms exhaustivo y amplio en fase de
anlisis semntico. Ya bien sea de un modo esttico (en tiempo de compilacin), dinmico
(en tiempo de ejecucin) o en ambos, las comprobaciones de tipo son necesarias en todo
lenguaje de alto nivel. De un modo somero, el analizador semntico deber llevar a cabo las
dos siguientes tareas relacionadas con los tipos:
Tareas y Objetivos del Anlisis Semntico
13
1. Comprobar las operaciones que se pueden aplicar a cada construccin del len-
guaje. Dado un elemento del lenguaje, su tipo identifica las operaciones que so-
bre l se pueden aplicar. Por ejemplo, en el lenguaje Java el operador de produc-
to no es aplicable a una referencia a un objeto. De un modo contrario, el opera-
dor punto s es vlido.
2. Inferir el tipo de cada construccin del lenguaje. Para poder implementar la
comprobacin anterior, es necesario conocer el tipo de toda construccin sin-
tcticamente vlida del lenguaje. As, el analizador semntico deber aplicar las
distintas reglas de inferencia de tipos descritas en la especificacin del lenguaje
de programacin, para conocer el tipo de cada construccin del lenguaje.
La tarea de comprobar todas las restricciones de cada tipo y la inferencia de stos
ser ampliamente descrita en 0.
2.2. Anlisis Semntico como Decoracin del AST
Un procesador de lenguaje en el que todas las fases ocurren en un nico recorrido
del cdigo fuente se denomina de una pasada
14
. En este tipo de procesadores, el anlisis
semntico y la generacin de cdigo estn intercaladas con el anlisis sintctico y por tanto
con el anlisis lxico. Este tipo de compiladores es ms eficiente y emplea menos memoria
que los que requieren ms de una pasada. Sin embargo, el cdigo que genera acostumbra a
ser menos eficiente que los compiladores que emplean ms de una pasada. Pascal y C son
ejemplos de lenguajes que pueden ser compilados con una sola pasada por ello, es siempre
necesario declarar una funcin antes de utilizarla (forward en Pascal).
Existen lenguajes como Modula-2 o Java cuyas estructuras requieren ms de una
pasada para ser procesados se puede invocar a mtodos o funciones definidas posterior-
mente en el archivo. Asimismo, la mayora de los compiladores que optimizan el cdigo
generado (en su representacin intermedia o final) son de ms de una pasada, para poder
analizar y optimizar el cdigo
15
. Es importante resaltar que una pasada de un compilador es
un concepto distinto al de una fase. En una pasada se pueden llevar a cabo todas las fases
(si ste es de una pasada), o para una fase se pueden dar varias pasadas (como la optimiza-
cin intensiva de cdigo). Las configuraciones intermedias son tambin comunes.
El anlisis semntico de un programa es ms sencillo de implementar si se emplean
para las fases de anlisis sintctico y semntico dos o ms pasadas
16
. En este caso, la fase de
anlisis sintctico crear un rbol sintctico abstracto para que sea procesado por el anali-
zador semntico.
Si el procesador es de una pasada, el analizador sintctico ir llamando al analizador
semntico de un modo recursivo y, si bien ningn rbol es creado de forma explcita, los
mbitos de las invocaciones recursivas (o los niveles de la pila del reconocedor) formarn
implcitamente el rbol sintctico.
rbol de sintaxis abstracta
Como sabemos, un rbol sintctico
17
es una representacin de la estructura de una
consecucin de componentes lxicos (tokens), en la que stos aparecen como nodos hoja y

14
One-pass compier.
15
Existen compiladores que optimizan el cdigo de un modo intensivo, llegando a dar hasta 7 pasadas a
la representacin del programa [Louden97].
16
En ocasiones, como en el caso de Java o Modula2, es de hecho en nico modo de hacerlo.
17
Parse tree.
Anlisis Semntico en Procesadores de Lenguaje
14
los nodos internos representan los pasos en las derivaciones de la gramtica asociada. Los
rboles sintcticos poseen mucha ms informacin de la necesaria para el resto de las fases
de un compilador, una vez finalizada la fase de anlisis sintctico.
Una simplificacin de rbol sintctico que represente toda la informacin necesaria
para el resto del procesamiento del programa de un modo ms eficiente que el rbol sintc-
tico original, recibe el nombre de rbol de sintaxis abstracta (AST, Abstract Syntax Tree).
As, la salida generada por un analizador sintctico de varias pasadas, ser el AST represen-
tativo del programa de entrada.
Un AST puede ser visto como el rbol sintctico de una gramtica denominada abs-
tracta, al igual que un rbol sintctico es la representacin de una gramtica (en ocasiones
denominada concreta). Por tanto, es comn ver una gramtica que representa una simplifi-
cacin de un lenguaje de programacin denominada gramtica abstracta del lenguaje.
Ejemplo 7. Lo siguiente es una gramtica (concreta) de una expresin, descrita en la notacin
propia de yacc/bison [Mason92]:

expresion: expresion '+' termino
| expresion '-' termino
| termino
;
termino: termino '*' factor
| termino '/' factor
| factor
;
factor: '-' factor
| '(' expresion ')'
| CTE_ENTERA
;
La sentencia 3*(21+-32)pertenece sintcticamente al lenguaje y su rbol sintctico es:
expresin
trmino
trmino
factor
*
factor
expresin ( )
expresin trmino +
factor
factor -
CTE_ENTERA
(32)
factor
CTE_ENTERA
(21)
CTE_ENTERA
(3)
trmino

Figura 2: rbol sintctico generado para la sentencia 3*(21+-32).
En el rbol anterior hay informacin (como los parntesis o los nodos intermedios factor y
trmino) que no es necesaria para el resto de fases del compilador. La utilizacin de un
AST simplificara el rbol anterior. Una posible implementacin sera siguiendo el siguiente
diagrama de clases:
Tareas y Objetivos del Anlisis Semntico
15
ConstanteEntera
valor : int
ConstanteEntera()
ExpresionUnaria
operador : char
operando : Expresion*
ExpresionUnaria()
ExpresionBinaria
operador : char
operando1 : Expresion*
operando2 : Expresion*
ExpresionBinaria()
Expresion
11
2

Figura 3: Diagrama de clases de los nodos de un AST.
Su implementacin en C++ puede consultarse en el apndice A.1. Todos los nodos son
instancias derivadas de la clase abstracta expresin. De este modo, se podr trabajar con
cualquier expresin, independientemente de su aridad, mediante el uso de esta clase. Todas
las expresiones binarias sern instancias de la clase ExpresionBinaria, teniendo un
atributo que denote su operador. Del mismo modo, las expresiones con un operador una-
rio (en nuestro ejemplo slo existe el menos unario) son instancias de ExpresionUna-
ria. Finalmente, las constantes enteras son modeladas con la clase ConstanteEntera.
Una implementacin yacc/bison que cree el AST a partir de un programa de entrada es:

%{
#include "ast.h"
int yyparse();
int yylex();
%}
%union {
int entero;
Expresion *expresion;
}
%token <entero> CTE_ENTERA
%type <expresion> expresion termino factor
%%
expresion: expresion '+' termino {$$=new ExpresionBinaria('+',$1,$3); }
| expresion '-' termino {$$=new ExpresionBinaria('-',$1,$3); }
| termino {$$=$1;}
;
termino: termino '*' factor {$$=new ExpresionBinaria('*',$1,$3); }
| termino '/' factor {$$=new ExpresionBinaria('/',$1,$3); }
| factor { $$=$1; }
;
factor: '-' factor { $$=new ExpresionUnaria('-',$2); }
| '(' expresion ')' { $$=$2; }
| CTE_ENTERA { $$=new ConstanteEntera($1); }
;
%%
As, ante la misma sentencia de entrada 3*(21+-32), el AST generado tendr la siguiente
estructura:
Anlisis Semntico en Procesadores de Lenguaje
16
Expresin Binaria
(*)
Expresin Binaria
(+)
Constante Entera
(21)
Expresin Unaria
(-)
Constante Entera
(32)
Constante Entera
(3)

Figura 4: AST generado para la sentencia 3*(21+-32).
Ntese cmo ya no son necesarios los nodos de factor y trmino, puesto que el propio
rbol ya se crear con una estructura que refleje la informacin relativa a la precedencia de
operadores. De la misma forma, el procesamiento del AST en las siguientes fases es nota-
blemente ms sencillo que el del rbol sintctico original.
La gramtica abstracta de nuestro ejemplo es:

expresion: expresionBinaria
| expresionUnaria
| constanteEntera
;
expresioBinaria: expresion ('*'|'/'|'+'|'-') expresion
;
expresionUnaria: '-' expresion
;
constanteEntera: CTE_ENTERA
;

Ejemplo 8. Considrese la siguiente gramtica libre de contexto, que representa una simplifica-
cin de la sintaxis de una sentencia condicional en un lenguaje de programacin imperativo
los smbolos terminales se diferencian de los no terminales porque los primeros han sido
escritos en negrita:

sentencia

sentenciaIf
| lectura
| escritura
| expresion
sentenciaIf

if ( expresion ) sentencia else
else

else sentencia
|

lectura

read expresion
escritura

write expresion
expresion

true
| false
| cte_entera
| id
| expresion + expresion
| expresion expresion
| expresion * expresion
| expresion / expresion
| expresion = expresion
Tareas y Objetivos del Anlisis Semntico
17
La gramtica anterior acepta ms sentencias que las pertenecientes al lenguaje, como suele
suceder en la mayor parte de los casos. El analizador semntico debera restringir la validez
semntica de las sentencias teniendo en cuenta que la expresin de la estructura condicional
debe ser lgica (o entera en el caso de C), y que tanto la expresin a la izquierda del opera-
dor de asignacin como la expresin de la sentencia de lectura sean lvalues.
La siguiente sentencia es sintcticamente vlida:

if (true)
read a
else
write a=2+a*b
La implementacin de un procesador del lenguaje
presentado en la que se emplee ms de una pasada,
un AST apropiado generado por el analizador sin-
tctico podra ser el mostrado en la Figura 5.
Ntese cmo el AST representa, de un modo ms
sencillo que el rbol sintctico, la estructura de la
sentencia condicional con tres nodos hijos: la ex-
presin de la condicin, la sentencia a ejecutar si la
condicin es vlida, y la asociada al valor falso de la
condicin. Del mismo modo, las expresiones crea-
das poseen la estructura adecuada para ser evalua-
das con un recorrido en profundidad infijo, de un
modo acorde a las precedencias.

Decoracin del AST
Una vez que el analizador semntico obten-
ga el AST del analizador sintctico, ste deber utilizar el AST para llevar a cabo todas las
comprobaciones necesarias para verificar la validez semntica del programa de entrada.
Este proceso es realizado mediante lo que se conoce como decoracin o anotacin del
AST: asignacin de informacin adicional a los nodos del AST representando propiedades
de las construcciones sintcticas del lenguaje, tales como el tipo de una expresin. Un AST
decorado o anotado
18
es una ampliacin del AST, en el que a cada nodo del mismo se le
aaden atributos indicando las propiedades necesarias de la construccin sintctica que
representan.
Ejemplo 9. Dada la gramtica libre de contexto:

S

S declaracion ;
| S expresion ;
|

declaracion

int id
| float id
expresion

id
| cte_entera
| cte_real
| ( expresion )
| expresion + expresion
| expresion expresion

18
Annotated AST o decorated AST.
if
true
=
read
+ id
(a)
cte_entera
(2)
*
id
(b)
id
(a)
id
(a)
write

Figura 5: AST generado para la gram-
tica y lenguaje de entrada presenta-
dos.
Anlisis Semntico en Procesadores de Lenguaje
18
| expresion * expresion
| expresion / expresion
| expresion = expresion
Para implementar un analizador semntico deberemos decorar el AST con la siguiente in-
formacin:
Las expresiones tendrn asociadas un atributo tipo que indique si son reales o enteras.
Esto es necesario porque se podr asignar un valor entero a una expresin real, pero no
al revs.
Las expresiones debern tener un atributo lgico que indique si son o no lvalues. De
este modo, se podr comprobar si lo que est a la izquierda de la asignacin es o no
semnticamente correcto.
En una declaracin se deber insertar el identificador en una tabla de smbolos con su
tipo declarado, para poder conocer posteriormente el tipo de cualquier identificador en
una expresin. Es, por tanto, necesario asignar un atributo nombre (cadena de caracte-
res) a un identificador.
Finalmente aunque ms enfocado a la fase de generacin de cdigo o interpretacin
que al anlisis semntico se le asigna un valor entero o real a las constantes del lengua-
je.
Para el siguiente programa, un posible AST decorado es el mostrado en la Figura 6.

int a;
float b;
b=(a+1)*(b-8.3);
=
(tipo=real,
lvalue=true)
*
(tipo=real,
lvalue=false)
id
(nombre=b,
tipo=real,
lvalue=true)
-
(tipo=real,
lvalue=false)
cte_real
(valor=8.3,
tipo=real,
lvalue=false)
id
(nombre=b,
tipo=real,
lvalue=true)
id
(nombre=a,
tipo=entero)
id
(nombre=b,
tipo=real)
+
(tipo=entero,
lvalue=false)
cte_entera
(valor=1,
tipo=entero,
lvalue=false)
id
(nombre=a,
tipo=entero,
lvalue=true)
S

Figura 6: AST decorado para llevar a cabo el anlisis semntico del programa analizado.
Ntese cmo el analizador semntico ser el encargado de decorar el AST como se muestra
en la Figura 6, adems de comprobar las restricciones mencionadas con anterioridad por
las que, precisamente, se ha decorado el rbol.

Tareas y Objetivos del Anlisis Semntico
19
De este modo es ms sencillo llevar a cabo la tarea de anlisis semntico puesto que
toda la informacin sintctica est explcita en el AST, y el anlisis semntico tiene que limi-
tarse a decorar el rbol y comprobar las reglas semnticas del lenguaje de programacin. En
el ejemplo anterior, utilizando la estructura del rbol se va infiriendo el tipo de cada una de
las expresiones. La informacin anotada a cada nodo del rbol ser empleada tambin por
las siguientes fases del procesador de lenguajes como la generacin de cdigo. Siguiendo
con el ejemplo, el conocer los tipos de cada una de las construcciones del AST facilita al
generador de cdigo saber el nmero de bytes que tiene que reservar, la instruccin de bajo
nivel que tiene que emplear, o incluso si es necesario o no convertir los operandos antes de
realizar la operacin.
El principal formalismo que existe para decorar rboles sintcticos es el concepto
de gramtica atribuida. Mediante gramticas atribuidas se implementan analizadores semn-
ticos a partir del AST o del rbol sintctico. Tambin, partiendo de una gramtica atribuida,
existen mtodos de traduccin de stas a cdigo. Estos conceptos y tcnicas sern lo que
estudiaremos en los siguientes puntos.
21
3 Gramticas Atribuidas
Las gramticas libres de contexto son las elegidas comnmente para representar la
sintaxis de los lenguajes de programacin. stas representan cmo debe ser la estructura de
cualquier programa perteneciente al lenguaje que describen. Sin embargo, un procesador de
lenguaje necesita conocimiento adicional del significado de las construcciones para llevar a
cabo acciones en funcin de la fase en la que se encuentre.
A modo de ejemplo, consideremos la gramtica inicial de expresiones mostrada en
el Ejemplo 7. La gramtica representa la estructura de un tipo de expresiones aritmticas de
constantes enteras, teniendo en cuenta la precedencia comn de los operadores empleados.
En la fase de anlisis sintctico el reconocimiento de dicha estructura es suficiente, pero en
las fases posteriores hay que llevar a cabo el clculo de distintas parte de su semntica:
En la fase de anlisis semntico, para una ampliacin del ejemplo con diversos
tipos, habr que comprobar que la expresin satisface las reglas propias de los
tipos del lenguaje, calculando los tipos de cada subexpresin y analizando si las
operaciones aplicadas son vlidas para los tipos inferidos.
En la generacin de cdigo habr que ir traduciendo el programa de entrada a
otro programa con igual semntica, pero expresado en otro lenguaje de salida.
Si nos encontramos desarrollando un intrprete, en la fase de ejecucin necesi-
taremos conocer el valor de cada una de las expresiones. Esto mismo puede
darse si se est implementando un compilador y nos encontramos en la fase de
optimizacin de cdigo, en la que podemos reemplazar el clculo de una expre-
sin con todos sus operandos constantes por su valor esta optimizacin recibe
el nombre de calculo previo de constantes
19
.
Para llevar a cabo las tareas previas, es comn emplear en el diseo e implementa-
cin de un procesador de lenguaje gramticas atribuidas. Las gramticas atribuidas (o con
atributos) son ampliaciones de las gramticas libres de contexto que permiten especificar
semntica dirigida por sintaxis: la semntica de una sentencia de un lenguaje de programa-
cin est directamente relacionada con su estructura sintctica y, por tanto, se suele repre-
sentar mediante anotaciones de su rbol sintctico [Louden97]. Puesto que el uso de las
gramticas atribuidas es posterior a la fase de anlisis sintctico, es comn ver stas como
ampliacin de gramticas que especifiquen sintaxis abstracta y no concreta [Saraiva99] (
2.2).
3.1. Atributos
Un atributo es una propiedad de una construccin sintctica de un lenguaje. Los
atributos varan considerablemente en funcin de la informacin que contienen, de la fase
de procesamiento en la que se hallen, e incluso en si estn siendo calculados en fase de tra-
duccin (empleados por los traductores y compiladores) o de ejecucin (empleados por los

19
Constant folding.
Anlisis Semntico en Procesadores de Lenguaje
22
intrpretes). Tpicos ejemplos de atributos de una gramtica son: el tipo de una variable, el
valor de una expresin, la direccin de memoria de una variable o el cdigo objeto (desti-
no) de una funcin.
El tiempo en el que los atributos mencionados son calculados es variable en fun-
cin del lenguaje y procesador empleado. Pueden estticos si son calculados de un modo
previo a la ejecucin de la aplicacin, o dinmicos si su valor se obtiene en tiempo de eje-
cucin del programa. En el primer ejemplo mencionado el clculo del tipo de una varia-
ble, para lenguajes como C y Pascal, la inferencia de tipos es resuelta de un modo esttico
por el analizador semntico; en el caso de Lisp, la comprobacin relativa a los tipos es
siempre calculada en tiempo de ejecucin.
Si a es un atributo de un smbolo gramatical X, escribiremos X.a para referirnos al
valor del atributo a asociado al smbolo gramatical X. Para cada atributo, se debe especificar
su dominio: el conjunto de posibles valores que puede tomar
20
. Si en una produccin de la
gramtica libre de contexto aparece ms de una vez un smbolo gramatical X, entonces se
debe aadir un subndice a cada una de las apariciones para podernos referir a los valores
de los atributos de un modo no ambiguo: ... . , . , .
3 2 1
a X a X a X
Ejemplo 10. Dado el smbolo no terminal expresion, podremos tener en un compilador los
siguientes atributos definidos sobre l:

Atributo Dominio (posibles valores) Descripcin
expresion.tipo
Entero, carcter, real, booleano,
array, registro, puntero o fun-
cin.
El tipo inferido para la expre-
sin (anlisis semntico).
expresion.lvalue
Un valor lgico Si la expresin puede estar o no
a la izquierda de la asignacin
(anlisis semntico).
expresion.codigo
Cadena de caracteres El cdigo objeto (generacin de
cdigo).
expresion.valor
Una unin de valores de tipo
entero, real o lgico
Cuando sea posible, calcular el
valor de una expresin de tipo
entera, real o booleana (optimi-
zacin de cdigo).

3.2. Reglas Semnticas
Las relaciones entre los valores de los atributos definidos sobre una gramtica atri-
buida se especifican mediante reglas semnticas o ecuaciones de atributos
21
[Lou-
den97]. Dada una gramtica libre de contexto G={S,P,VN,VT}, donde VN es el vocabula-
rio no terminal, VT el vocabulario terminal (tokens), S el smbolo no terminal inicial y P el
conjunto de producciones de la gramtica, donde cada p P es de la forma:
n i VN VT X VN X X X X X
i n
1 , , , ...
0 2 1 0


20
El concepto de dominio viene de la especificacin semntica de lenguajes, en especial de la denotacio-
nal. En programacin, el concepto de dominio es traducido al concepto de tipo.
21
Attribute equation o semantic rule.
Gramticas Atribuidas
23
Una regla semntica asociada a la produccin p es una funcin matemtica que es-
pecifica las relaciones entre los valores de los atributos del siguiente modo [Aho90]:
) ... , , ( :
3 2 1 k
a a a a f a =
Donde k i a
i
1 , son atributos de los smbolos gramaticales de la produccin
( n j VN VT X
j
0 , ) y a es:
O bien un atributo sintetizado del smbolo gramatical no terminal
0
X situado
a en la parte izquierda de la produccin p.
O bien un atributo heredado de uno de los smbolos gramaticales
n i VN VT X
i
1 , situados en la parte derecha de la produccin p.
En cualquier caso, se dice que el atributo a depende de los atributos k i a
i
1 , .
El conjunto de atributos sintetizados de un smbolo gramatical X se suele representar con el
conjunto AS(X). Del mismo modo, los atributos heredados de X se representan con el
conjunto AH(X)
22
.
Los atributos de los smbolos terminales de la gramtica se consideran atributos sin-
tetizados puesto que su valor ha sido asignado por el analizador lxico.
3.3. Gramticas Atribuidas
Las gramticas atribuidas fueron definidas originalmente por Knuth [Knuth68] co-
mo un mtodo para describir la semntica de un lenguaje de programacin. Una gramtica
atribuida (GA) es una tripleta GA={G,A,R}, donde:
G es una gramtica libre de contexto definida por la cudrupla
G={S,P,VN,VT}

=

U
VT VN X
X A A ) ( es un conjunto finito de atributos, siendo X un smbolo
gramatical (X VN VT) y A(X) el conjunto de atributos definidos para X.

U
P p
p R R ) ( es un conjunto finito de reglas semnticas o ecuaciones de
atributos, siendo R(p) el conjunto de reglas semnticas asociadas a p P y P el
conjunto de producciones de la gramtica libre de contexto G.
Como hemos definido en el punto 3.2, las reglas semnticas establecen relaciones
entre los valores de los atributos de una gramtica atribuida, expresadas mediante una fun-
cin matemtica. Desde el punto de vista ms estricto de definicin de gramtica atribuida,
las reglas semnticas nicamente pueden recibir como parmetros otros atributos de la
gramtica (no estn permitidas las constantes, variables o llamadas a funciones que generen
efectos colaterales
23
) y devolver, sin generar un efecto colateral, un nico valor que ser
asignado a otro atributo de la produccin actual.

22
En los textos escritos en ingls, este conjunto es AI(X) ya que el atributo es inherited.
23
Una funcin que no posea efectos colaterales es aqulla que devuelve y genera siembre el mismo resul-
tado al ser llamada con el mismo conjunto de argumentos. Una funcin que incremente una variable
global, por ejemplo, genera efectos laterales. Las funciones que no tienen efectos colaterales son meras
expresiones de clculo computacional sobre los argumentos pasados.
Anlisis Semntico en Procesadores de Lenguaje
24
A efectos prcticos, la reglas semnticas son fragmentos de cdigo expresadas en
una notacin bien definida, como pueda ser un lenguaje de programacin, una notacin
matemtica o un lenguaje de especificacin semntica como la denotacional o axiomtica (
1.1). El lenguaje empleado para especificar las acciones semnticas recibe el nombre de
metalenguaje [Louden97]. Realmente, ni la notacin de especificacin de las reglas semn-
ticas, ni la especificacin de los tipos (dominios) de los atributos, son intrnsecos al concep-
to de gramtica atribuida
24
[Scott00].
En ocasiones, las gramticas atribuidas que emplean un metalenguaje que permite la
utilizacin de funciones que tengan efectos colaterales son denominadas definiciones di-
rigidas por sintaxis
25
o definiciones atribuidas [Aho90].
Ejemplo 11. Dada la gramtica libre de contexto G={S,P,VN,VT}, donde S es expresion,
VN={expresin, termino y factor}, VT={+, -, *, /, (, ), CTE_ENTERA} y P es
26
:

(1) expresion
1


expresion
2
+ termino
(2) | expresion
2
- termino
(3) | termino
(4) termino
1


termino
2
* factor
(5) | termino
2
/ factor
(6) | factor
(7) factor
1


- factor
2

(8) | ( expresion )
(9) | CTE_ENTERA
Ntese como, en caso de existir una repeticin de un smbolo gramatical en una misma
produccin, se ha roto la posible ambigedad con la adicin de subndices. Una gramtica
atribuida que sea capaz de evaluar el valor de las expresiones del lenguaje, necesaria en un
intrprete o para optimizar cdigo (vase Ejemplo 10), estar formada por GA={G, A, R},
donde A ser:

Atributo Dominio
expresion.valor
Nmeros enteros
termino.valor
Nmeros enteros
factor.valor
Nmeros enteros
CTE_ENTERA.valor
Nmeros enteros
Finalmente, R ser:

P R
(1) expresion
1
.valor = expresion
2
.valor + termino.valor
(2) expresion
1
.valor = expresion
2
.valor - termino.valor
(3) expresion.valor = termino.valor
(4) termino
1
.valor = termino
2
.valor * factor.valor
(5) termino
1
.valor = termino
2
.valor / factor.valor
(6) termino.valor = factor.valor
(7) factor
1
.valor = factor
2
.valor

24
De hecho, desde la primera definicin de gramtica atribuida dada por Knuth [Knuth68] existe mltiple
bibliografa que define y emplea gramticas atribuidas variando estos dos parmetros en funcin del
objetivo buscado.
25
Syntax-directed definition.
26
Las producciones de las gramticas han sido numeradas para poder hacer referencia a ellas de un modo
ms sencillo.
Gramticas Atribuidas
25
P R
(8) factor.valor = expresion.valor
(9) factor.valor = CTE_ENTERA.valor
Hemos utilizado como metalenguaje el lenguaje de programacin C. En todos los casos los
atributos son sintetizados, puesto que el atributo que se calcula en toda regla semntica est
en la parte izquierda de su produccin asociada el atributo CTE_ENTERA.valor es sinte-
tizado por el analizador lxico.
La gramtica atribuida se ha construido para que la evaluacin de los cuatro operadores sea
asociativa a izquierdas, de modo que la sentencia 1-1-2 poseer el valor -2 (y no 2).

Del mismo modo que las gramticas libres de contexto no especifican cmo deben
ser procesadas por el analizador sintctico (ascendente o descendentemente, en sus mlti-
ples alternativas), una gramtica atribuida no especifica el orden en el que los atributos tie-
nen que ser calculados. Las gramticas atribuidas son, pues, declarativas y no imperativas
[Wilhelm95]. Veremos cmo, al igual que existen clasificaciones de gramticas libres de
contexto en funcin de los algoritmos que las procesan (LL, SLL, LL(K), LR, SLR o
LALR) tambin existen gramticas atribuidas en funcin del orden de evaluacin de sus
atributos (L-atribuidas, S-atribuidas y bien definidas).
Las principales clasificaciones de gramticas atribuidas tienen en cuenta si los atri-
butos calculados en las producciones son heredados o sintetizados. Es importante com-
prender la nocin de cada uno de ellos y cundo y cmo es necesario emplear uno u otro.
Supongamos una produccin p P de una gramtica libre de contexto:
n i VN VT X VN X X X X X
i n
1 , , , ...
0 2 1 0

Los atributos sintetizados se calculan ascendentemente en el rbol sintctico: en
funcin de los atributos de los nodos hijos y asignndole un valor a un atributo sintetizado
del nodo padre (Figura 7). Por este motivo, se dice que en esa produccin el atributo se
sintetiza (podr ser empleado en producciones en las que el smbolo gramatical
0
X se en-
cuentre en la parte derecha de la produccin).
X
0
X
1
X
2
X
n
Atributo
Sintetizado
Clculo del
atributo
A
B C
Produccin
donde se
utilizar el
valor del
atributo
sintetizado
Produccin
donde se
calcula el
atributo
sintetizado
X
0
X
1
X
2
X
n
Atributo
Heredado
Clculo del
atributo
Produccin
donde se
utilizar el
valor del
atributo
heredado
Produccin
donde se
calcula el
atributo
heredado
A B C

Figura 7: Evaluacin de atributos heredados y sintetizados.
De un modo contrario, los atributos heredados se calculan descendentemente en
el rbol sintctico. Como se muestra en la Figura 7, se asigna un valor a un atributo del
nodo hijo
2
X para que, en aquellas reglas en las que ste aparezca en la parte izquierda de
la produccin, herede el valor asignado.
Siguiendo con este mismo criterio, en cualquier produccin se podr utilizar los
atributos de los smbolos terminales de la gramtica que, por definicin, aparecern en la
Anlisis Semntico en Procesadores de Lenguaje
26
parte derecha de la produccin. Es por ello por lo que stos se consideran sintetizados por
el analizador lxico.
Ejemplo 12: Dada la siguiente gramtica libre de contexto:

(1) declaracion

tipo variables ;
(2) tipo

int
(3) | float
(4) variables
1


id , variables
2

(5) | id
Supngase que tenemos un objeto ts (tabla de smbolos) que nos ofrece el mtodo
insertar para aadir a una estructura de datos el valor de un tipo asociado a una cade-
na. El objetivo es, mediante una definicin dirigida por sintaxis, insertar los identificadores
de la gramtica en la tabla de smbolos junto a su tipo apropiado. En un procesador de len-
guaje real, esta informacin se necesitar para conocer el tipo de un identificador cuando
forme parte de una expresin.
Ntese cmo las producciones en las que realmente conocemos el identificador a insertar
en la tabla de smbolos son la cuarta y quinta. Sin embargo, el tipo que puedan tener aso-
ciado es conocido en las reglas 2 y 3. De este modo, una solucin es transmitir la informa-
cin relativa al tipo hasta las reglas 4 y 5 para que stas inserten el identificador en la tabla
de smbolos:

P R
(1) variables.tipo = tipo.tipo
(2) tipo.tipo = I
(3) tipo.tipo = F
(4) ts.insertar(id.valor,variables
1
.tipo)
variables
2
.tipo=variables
1
.tipo
(5) ts.insertar(id.valor,variables.tipo)
El valor del tipo declarado se sintetiza en las reglas 2 o 3 y es pasado como heredado, por
medio de la regla 1, al no terminal variables. ste lo emplear para, junto al valor del
identificador (su nombre), insertarlo en la tabla de smbolos.
Es importante darse cuenta de que el valor que variables ha heredado en la regla 4,
deber asignrselo a la segunda aparicin del mismo no terminal, para que este proceso sea
llevado a cabo de un modo recursivo heredando siempre su valor. En la Figura 8 se mues-
tra el rbol propio del siguiente programa de entrada: int a,b;
tipo
(tipo=I)
float int variables
(tipo=I)
id
(valor=b,
ts.insertar(b, I) )
variables
(tipo=I)
id
(valor=a,
ts.insertar(a, I) )
declaracion
;
,
Sintetiza el
valor de tipo
(regla 2)
Hereda el
valor de tipo
(regla 1)
Hereda el
valor de tipo
(regla 4)

Gramticas Atribuidas
27
Figura 8: rbol sintctico generado para el programa de entrada int a,b; .
Si el valor del atributo tipo no es asignado al nodo variables situado en la parte infe-
rior derecha por medio de la asignacin de la produccin cuarta, este atributo no tendra el
valor I. El resultado sera que no se insertara correctamente el identificador b en la tabla
de smbolos mediante la ltima regla semntica.
Si se trata de resolver el problema propuesto nicamente con atributos sintetizados, la solu-
cin sera mucho ms compleja. Tendramos que ir guardando en un atributo sintetizado de
variables la lista de todos los identificadores declarados. En la primera produccin se
tendra que poner un bucle en el que cada uno de los identificadores de la lista de variables
sera insertado en la tabla de smbolos con el tipo sintetizado.
Si en lugar de una definicin dirigida por sintaxis tuvisemos que escribir una gramtica
atribuida, no se podra utilizar una estructura de datos auxiliar por los efectos colaterales
que se producen al insertar elementos. Debera definirse un atributo que fuese una lista de
los smbolos e ir pasando ste a lo largo de todos los no terminales del programa. En el
desarrollo real de un procesador de lenguaje esto producira un elevado acoplamiento y, por
lo tanto, se suele abordar con una definicin dirigida por sintaxis.

Ejemplo 13: Dada la siguiente gramtica libre de contexto:

(1) expresion

termino masTerminos
(2) masTerminos
1


+ termino masTerminos
2

(3) | - termino masTerminos
2

(4) |

(5) termino

factor masFactores
(6) masFactores
1


* factor masFactores
2

(7) | / factor masFactores
2

(8) |

(9) factor

CTE_ENTERA
Se plantea, en la fase de interpretacin u optimizacin de cdigo, la cuestin de calcular el
valor de la expresin en un atributo expresion.valor. Hay que tener en cuenta que
todos los operadores poseen una asociatividad a izquierdas y los factores poseen mayor
precedencia que los trminos. La siguiente tabla muestra ejemplos de las evaluaciones que
se han de conseguir:
Sentencia Valor de expresion.valor
1-3-5 -7
1+2*3 7
16/4/4 1
La complejidad del problema radica en que los dos operandos de todas las expresiones
binarias se encuentran en producciones distintas. En el caso de la suma, el primer operando
aparece en la primera produccin, pero el segundo se encuentra en la produccin 2. El cl-
culo del valor de la subexpresin ha de llevarse a cabo en la segunda produccin, como
suma del trmino de la primera produccin y el trmino de sta. Pero, cmo podemos
acceder en la segunda produccin al valor del primer operando? Mediante el empleo de un
atributo heredado:

(1) masTerminos.operando1 = termino.valor
El atributo heredado operando1 hace referencia al valor ya calculado, sintetizado, del
trmino empleando como primer operando. El no terminal masTerminos ya conoce el
Anlisis Semntico en Procesadores de Lenguaje
28
valor del primer operando y, en la segunda produccin, estar en condiciones de poder
calcular el valor de la subexpresin. Este valor lo devolver con un atributo sintetizado
(masTerminos.valor). Por tanto, la segunda regla semntica a ubicar en la primera pro-
duccin ser asignar este atributo sintetizado a la expresin, teniendo:

(1) masTerminos.operando1 = termino.valor
expresion.valor = masTerminos.valor
Este clculo de subexpresiones, mediante la utilizacin de un atributo heredado que posee
el valor del primer operando y un atributo sintetizado con el valor de la subexpresin, ser
aplicado de un modo recursivo a las producciones 5 y 9:

(5) masFactores.operando1 = factor.valor
termino.valor = masFactores.valor
(9) factor.valor = CTE_ENTERA.valor
Al ser todos los operadores asociativos a izquierdas, la evaluacin de toda subexpresin
ser el clculo del primer operando y el segundo. Recursivamente, el resultado de la subex-
presin calculada podr ser el primer operando de otra subexpresin de la misma prece-
dencia (por ejemplo en las sentencias 1+2+3 y 23+14-8).

(2) masTerminos
2
.operando1=masTerminos
1
.operando1+termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
La primera regla semntica calcula la subexpresin con sus dos trminos y la convierte en el
primer operando de la siguiente subexpresin. Una vez que la siguiente subexpresin haya
evaluado su valor, el valor que retornamos (atributo masTerminos
1
.valor) es el valor
de la siguiente subexpresin (masTerminos
2
.valor). En el caso de que una subexpre-
sin no posea segundo operando (y por tanto produzca el vaco), su valor es el valor de su
primer operando:

(4) masTerminos
1
.valor = masTerminos
1
.operando1
La totalidad de las reglas semnticas de la gramtica atribuida es:

(1) masTerminos.operando1 = termino.valor
expresion.valor = masTerminos.valor
(2) masTerminos
2
.operando1=masTerminos
1
.operando1+termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(3) masTerminos
2
.operando1=masTerminos
1
.operando1-termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(4) masTerminos
1
.valor = masTerminos
1
.operando1
(5) masFactores.operando1 = factor.valor
termino.valor = masFactores.valor
(6) masFactores
2
.operando1=masFactores
1
.operando1*factor.valor
masFactores
1
.valor = masFactores
2
.valor
(7) masFactores
2
.operando1=masFactores
1
.operando1/factor.valor
masFactores
1
.valor = masFactores
2
.valor
(8) masFactores
1
.valor = masFactores
1
.operando1
(9) factor.valor = CTE_ENTERA.valor

3.4. Gramticas Atribuidas en Anlisis Semntico
Existe una ampliacin de la definicin de gramtica atribuida presentada en 3.3
enfocada a especificar el anlisis semntico de un lenguaje de programacin, es decir, a veri-
ficar la validez semntica de las sentencias aceptadas por el analizador sintctico [Waite84].
Gramticas Atribuidas
29
As, la tripleta definida en 3.3 es aumentada a la siguiente cudrupla: GA={G,A,R,B}
donde G, A y R poseen el mismo valor que en el caso anterior y

U
P p
p B B ) ( es un conjunto finito de condiciones (predicados), siendo
B(p) la conjuncin de predicados que debern satisfacer los atributos de p
P, y P el conjunto de producciones de la gramtica libre de contexto G.
As, para que una sentencia pertenezca al lenguaje definido por la gramtica atribui-
da deber ser sintcticamente correcta (reconocida por la gramtica libre de contexto), y los
valores de los atributos debern, tras crearse el rbol y evaluarse (decorarse) ste, satisfacer
todas las restricciones especificadas en B.
Ejemplo 14: Considrese la siguiente gramtica que reconoce nmeros en base octal y decimal,
posponiendo para ello el carcter o o d a cada nmero, respectivamente
27
.

numero

digitos base
base

o | d
digitos

digitos digito
| digito
digito

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Para calcular el valor real del nmero, debemos aadir a los no terminales base, digitos
y digito un atributo base que pueda tener los valores 8 y 10. El atributo valor poseer
el valor del no terminal asociado, en base decimal. Aadimos, pues, las reglas semnticas
para calcular este atributo:

P

R
(1) numero

digitos base numero.valor = digitos.valor
digitos.base = base.base
(2) base

o base.base = 8
(3) | d base.base = 10
(4) digitos
1


digitos
2

digito
digitos
1
.valor = digito.valor +
digitos
2
.valor

* digitos
1
.base
digitos
2
.base = digitos
1
.base
digito.base = digitos
1
.base
(5) digitos

digito digitos.valor = digito.valor
digito.base = digitos.base
(6) digito

0 digito.valor = 0
(7) | 1 digito.valor = 1
(8) | 2 digito.valor = 2
(9) | 3 digito.valor = 3
(10) | 4 digito.valor = 4
(11) | 5 digito.valor = 5
(12) | 6 digito.valor = 6
(13) | 7 digito.valor = 7
(14) | 8 digito.valor = 8
(15) | 9 digito.valor = 9
Con las reglas semnticas previas, la gramtica atribuida consigue que el atributo
numero.valor posea el valor del nmero en base decimal. Sin embargo, existen senten-

27
Podra tratarse de una gramtica atribuida para la fase de anlisis lxico. Existen herramientas que
utilizan estos tipos de gramticas para identificar los componentes lxicos del lenguaje a procesar
[ANTLR, JavaCC].
Anlisis Semntico en Procesadores de Lenguaje
30
cias de este lenguaje que, aunque sean sintcticamente correctas, no lo son semnticamente.
Esta condicin se produce cuando un nmero octal posea algn dgito 8 o 9. Esta restric-
cin hace que las sentencias 185o y 109o no deban ser semnticamente correctas.
As, ampliaremos la gramtica atribuida a una cudrupla que posea, adems de gramtica,
atributos y reglas, un conjunto de predicados:

P B
(14) digito

8 digito.base > 8
(15) digito

9 digito.base >= 10
Ntese cmo las nicas producciones que poseen restricciones semnticas son las dos l-
timas, ya que es donde aparecen los dos dgitos no permitidos en los nmeros octales.
Adems se ha escrito el conjunto de rutinas semnticas de modo que el no terminal
digito posea el atributo heredado base, precisamente para poder comprobar que sta
sea superior a ocho.

Ejemplo 15: En el Ejemplo 9 se especificaba mediante una gramtica libre de contexto (G) el
siguiente lenguaje:

(1) S

sentencias
(2) sentencias
1


declaracion ; sentencias
2

(3) sentencias
1


expresion ; sentencias
2

(4) sentencias

(5) declaracion

int id
(6) declaracion

float id
(7) expresion

id
(8) expresion

cte_entera
(9) expresion

cte_real
(10) expresion
1


( expresion
2
)
(11) expresion
1


expresion
2
+ expresion
3

(12) expresion
1


expresion
2
expresion
3

(13) expresion
1


expresion
2
* expresion
3

(14) expresion
1


expresion
2
/ expresion
3

(15) expresion
1


expresion
2
= expresion
3

Para implementar un compilador, las restricciones semnticas identificadas en el lenguaje
son: comprobar que lo que est a la izquierda de una asignacin es correcto (un lvalue);
comprobar que un identificador empleado en una expresin haya sido declarado previa-
mente; inferir el tipo de las expresiones, ratificando que las asignaciones sean correctas
(nunca asignar a un entero un real); asegurar que un identificador no est previamente de-
clarado.
Para poder implementar la gramtica atribuida, se utilizarn los siguientes atributos (A):
Del terminal id, el atributo id.valor es la cadena de caracteres que represen-
ta el identificador.
El atributo tipo es un carcter indicando el tipo del no terminal asociado: I
para el tipo entero y F para el real.
El atributo declaracion.id es un par (producto cartesiano) en el que el
primer valor es una cadena de caracteres (el valor del identificador) y el segundo
ser un carcter (el tipo asociado a dicho identificador).
Gramticas Atribuidas
31
El atributo ids es un contenedor asociativo o diccionario (map) que ofrece la
gestin de una clave de tipo cadena de caracteres (identificador) y un valor ca-
rcter asociado a cada una de las claves (su tipo). El contenedor vaco es nil.
La insercin de pares se lleva a cabo con la operacin +. El acceso a un valor a
partir de una clave, se obtiene con el operador [] si no existe un valor asocia-
do a la clave solicitada, devuelve nil.
El atributo expresion.lvalue es un valor lgico indicando si la expresin
puede estar a la izquierda de la asignacin.
Con estos atributos, se codifican las siguientes reglas semnticas (R) para la evalua-
cin de los mismos:

P R
(1) sentencias.ids = nil
(2) declaracion.ids = sentencias
1
.ids
sentencias
2
.ids = sentencias
1
.ids+declaracion.id
(3) expresion.ids = sentencias
1
.ids
sentencias
2
.ids = sentencias
1
.ids
(4)
(5) declaracion.id = (id.valor, I)
(6) declaracion.id = (id.valor, F)
(7) expresion.lvalue = true
expresion.tipo = expresion.ids[id.valor]
(8) expresion.lvalue = false
expresion.tipo = I
(9) expresion.lvalue = false
expresion.tipo = F
(10) expresion
2
.ids = expresion
1
.ids
expresion
1
.lvalue = expresion
2
.lvalue
expresion
1
.tipo = expresion
2
.tipo
(11) expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = false
expresion
1
.tipo=mayorTipo(expresion
2
.tipo,expresion
3
.tipo)
(12) expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = false
expresion
1
.tipo=mayorTipo(expresion
2
.tipo,expresion
3
.tipo)
(13) expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = false
expresion
1
.tipo=mayorTipo(expresion
2
.tipo,expresion
3
.tipo)
(14) expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = false
expresion
1
.tipo=mayorTipo(expresion
2
.tipo,expresion
3
.tipo)
(15) expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = expresion
2
.lvalue
expresion
1
.tipo=expresion
2
.tipo

char mayorTipo(char t1,char t2) {
if ( t1 == F || t2 == F ) return F;
return I;
}
Anlisis Semntico en Procesadores de Lenguaje
32
Una vez especificado en modo en el que se deben calcular cada uno de los atributos, limita-
remos la sintaxis del lenguaje mediante el ltimo elemento de la gramtica atribuida: un
conjunto de predicados asociados a las producciones de la gramtica (B), que establece las
restricciones semnticas del lenguaje.

P B
(5) declaracion.ids[id.valor] == nil
(6) declaracion.ids[id.valor] == nil
(7) expresion.ids[id.valor] != nil
(15) expresion
2
.lvalue
expresion
2
.tipo!=I || expresion
3
.tipo!=F
En este ejemplo se aprecia la principal diferencia entre gramtica atribuida y definicin diri-
gidas por sintaxis. Las primeras, al no poder generar efectos colaterales, han de representar
la tabla de smbolos como un atributo (ids) de varios smbolos no terminales. En las defi-
niciones dirigidas por sintaxis, la tabla de smbolos es una estructura de datos global a la
que se accede desde las reglas semnticas. Adicionalmente, el mecanismo de deteccin de
errores se desarrolla con cdigo adicional dentro de las reglas semnticas, en lugar de espe-
cificarlo aparte en un conjunto de predicados (B). Las definiciones dirigidas por sintaxis son
menos formales, pero ms pragmticas.



33
4 Tipos de Gramticas Atribuidas
Como hemos visto, las gramticas atribuidas ofrecen un modo de decorar o anotar
un rbol sintctico (concreto o abstracto) de un modo declarativo, sin identificar explcita-
mente el modo en el que deban ejecutarse las reglas semnticas en el caso de que realmen-
te se puedan ejecutar. En funcin de las propiedades que cumpla una gramtica atribuida,
podremos decir si se puede evaluar cualquier rbol asociado a un programa de entrada e
incluso podremos determinar un orden especfico de ejecucin de las reglas semnticas.
Existen multitud de trabajos, bibliografa e investigacin relativa a gramticas atri-
buidas. De este modo, los distintos tipos de gramticas aqu presentados son lo ms repre-
sentativos, existiendo otros tipos que no mencionamos.
4.1. Atributos Calculados en una Produccin
Los atributos calculados en una produccin p de la gramtica libre de contexto aso-
ciada a una gramtica atribuida son los que cumplan la siguiente condicin:

} ); ( ; 1 ), ( , ); ( ) ... , , ( : / { ) (
3 2 1
P p VN VT X k i X A a a p R a a a a f a a p AC
k k
= =
Los atributos calculados asociados a una produccin son, pues, aqullos cuyo valor
es calculado en una regla semntica asociada a dicha produccin.
4.2. Gramtica Atribuida Completa
Una gramtica atribuida se dice que es completa si satisface las siguientes condicio-
nes [Waite84]:
= ) ( ) ( ), ( X AH X AS G VN X I
) ( ) ( ) ( ), ( X A X AH X AS G VN X = U
) ( ) ( , : , p AC X AS X p P p
) ( ) ( , : , p AC X AH X Y p P p
La primera condicin obliga a que un mismo atributo de la gramtica no pueda ser
sintetizado al mismo tiempo que heredado. En el segundo caso, la restriccin impuesta es
que todo atributo ha de sintetizarse o heredarse, es decir, no podr existir un atributo al que
nunca se le asigne valor alguno.
Las dos ltimas condiciones buscan un mismo objetivo: que un a atributo sintetiza-
do o heredado siempre se le asigne un valor, en toda produccin en la aparezca en la parte
izquierda o derecha de la misma, respectivamente. Si un atributo es sintetizado en una pro-
duccin, en el resto de producciones en el que el no terminal asociado aparezca en la parte
izquierda, deber ser calculado. La misma condicin, aplicada a las partes derechas de las
producciones, deber satisfacerse en el caso de los atributos heredados.
Anlisis Semntico en Procesadores de Lenguaje
34
El significado real de que una gramtica atribuida sea completa es que haya sido es-
crita de un modo correcto. Pongamos un ejemplo con el caso de gramticas libres de con-
texto. Este tipo de gramticas se utiliza para describir lenguajes sintcticamente. Sin embar-
go, podemos escribir una gramtica sucia, en la que existan smbolos no terminales que no
tengan una produccin asociada (smbolos muertos) o producciones cuyo smbolo no ter-
minal de la parte izquierda nunca se emplee en otra produccin (smbolos inaccesibles).
As, aunque una gramtica libre de contexto es un mecanismo para expresar la sintaxis de
un lenguaje de programacin, si escribimos una gramtica sucia no estaremos describiendo
ningn lenguaje.
La misma problemtica puede surgir cuando escribimos gramticas atribuidas. Po-
dra darse el caso de que, siguiendo una notacin especfica, tratsemos de describir atribu-
tos de un programa mediante una gramtica atribuida. Si dicha gramtica no es completa,
no estaremos describiendo realmente evaluaciones de los atributos, puesto que cu clculo
no se podra llevar a cabo ante todos los programas de entrada. Por ello, la comprobacin
de que una gramtica atribuida sea completa es una verificacin de que se est expresando
correctamente la asignacin de valores a los atributos.
Ejemplo 16: Dada la gramtica atribuida del Ejemplo 13, en la que G era:

(1) expresion

termino masTerminos
(2) masTerminos
1


+ termino masTerminos
2

(3) | - termino masTerminos
2

(4) |

(5) termino

factor masFactores
(6) masFactores
1


* factor masFactores
2

(7) | / factor masFactores
2

(8) |

(9) factor

CTE_ENTERA
y sus reglas semnticas:

(1) masTerminos.operando1 = termino.valor
expresion.valor = masTerminos.valor
(2) masTerminos
2
.operando1=masTerminos
1
.operando1+termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(3) masTerminos
2
.operando1=masTerminos
1
.operando1-termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(4) masTerminos
1
.valor = masTerminos
1
.operando1
(5) masFactores.operando1 = factor.valor
termino.valor = masFactores.valor
(6) masFactores
2
.operando1=masFactores
1
.operando1*factor.valor
masFactores
1
.valor = masFactores
2
.valor
(7) masFactores
2
.operando1=masFactores
1
.operando1/factor.valor
masFactores
1
.valor = masFactores
2
.valor
(8) masFactores
1
.valor = masFactores
1
.operando1
(9) factor.valor = CTE_ENTERA.valor
Los atributos calculados y, para cada caso, si son heredados o sintetizados en una produc-
cin se muestra en la siguiente tabla:

Produccin Atributos Calculados Atributo Heredado o Sintetizado
(1) masTerminos.operando1
expresion.valor
Heredado
Sintetizado
Tipos de Gramticas Atribuidas
35
Produccin Atributos Calculados Atributo Heredado o Sintetizado
(2) masTerminos.operando1
masTerminos.valor
Heredado
Sintetizado
(3) masTerminos.operando1
masTerminos.valor
Heredado
Sintetizado
(4) masTerminos.valor
Sintetizado
(5) masFactores.operando1
termino.valor
Heredado
Sintetizado
(6) masFactores.operando1
masFactores.valor
Heredado
Sintetizado
(7) masFactores.operando1
masFactores.valor
Heredado
Sintetizado
(8) masFactores.valor
Sintetizado
(9) factor.valor
Sintetizado
Vemos en la tabla anterior que los atributos sintetizados (expresion.valor,
masTerminos.valor, termino.valor, masFactores.valor, factor.valor y
CTE_ENTERA.valor) no son heredados para ninguna produccin. Del mismo modo, los
heredados (masTerminos.operando1 y masFactores.operando1) nunca se sinteti-
zan. Por ello, se satisface la primera condicin de gramtica completa.
La comprobacin de la segunda condicin para que la gramtica sea completa, es buscar
algn atributo que no se haya calculado y se emplee, es decir, algn atributo que no perte-
nezca a la tabla anterior. Si existiere uno sin ser sintetizado ni heredado, la gramtica no
sera completa. El nico atributo que no se calcula en ninguna produccin es
CTE_ENTERA.valor que, por definicin, es sintetizado por el analizador lxico.
La tercera condicin obliga a que, para todas las producciones, los atributos sintetizados
del no terminal de la izquierda se calculen. Esta comprobacin se puede llevar a cabo me-
diante una tabla:

Produccin No Terminal Izquierda Atributos Sintetizados Calculados?
(1) expresion expresion.valor
S
(2) masTerminos masTerminos.valor
S
(3) masTerminos masTerminos.valor
S
(4) masTerminos masTerminos.valor
S
(5) termino termino.valor
S
(6) masFactores masFactores.valor
S
(7) masFactores masFactores.valor
S
(8) masFactores masFactores.valor
S
(9) factor factor.valor
S
Finalmente, la ltima restriccin indica que, para todas las producciones, los atributos here-
dados de los smbolos gramaticales de la parte derecha debern ser calculados:

Produccin No Terminal Derecha Atributos Heredados Calculados?
(1) termino
masTerminos

masTerminos.operando1

S
(2) termino
masTerminos

masTerminos.operando1

S
(3) termino
masTerminos

masTerminos.operando1

S
Anlisis Semntico en Procesadores de Lenguaje
36
Produccin No Terminal Derecha Atributos Heredados Calculados?
(4)

(5) factor
masFactores

masFactores.operando1

S
(6) factor
masFactores

masFactores.operando1

S
(7) factor
masFactores

masFactores.operando1

S
(8)

(9)

Se puede concluir, pues, que la gramtica atribuida anterior es completa.

Ejemplo 17: Supngase que para la misma gramtica que hemos presentado en el ejemplo
anterior, las reglas semnticas son:

(1) masTerminos.operando1 = termino.valor
expresion.valor = masTerminos.valor
(2) masTerminos
2
.operando1=masTerminos
1
.operando1+termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(3) masTerminos
2
.operando1=masTerminos
1
.operando1-termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(4)
(5) masFactores.operando1 = factor.valor
termino.valor = masFactores.valor
(6) masFactores
2
.operando1=masFactores
1
.operando1*factor.valor
masFactores
1
.valor = masFactores
2
.valor
(7) masFactores
2
.operando1=masFactores
1
.operando1/factor.valor
masFactores
1
.valor = masFactores
2
.valor
(8) masFactores
1
.valor = masFactores
1
.operando1
(9) factor.valor = CTE_ENTERA.valor
La gramtica anterior no es completa, ya que en la produccin 4 el no terminal de la iz-
quierda posee un atributo sintetizado (masTerminos.valor) que no se calcula. No se
cumple, por tanto, la tercera condicin de gramtica completa.
Ntese cmo el resultado de que la gramtica no sea completa implica que ante determina-
dos programas de entrada, no se puedan calcular los atributos del rbol. As, por ejemplo,
el programa de entrada 33, generara el siguiente rbol anotado:
Tipos de Gramticas Atribuidas
37
termino
(valor=33)
masTerminos
(operando1=33
valor=?)
expresion
(valor=?)

Regla 1.2
Regla 1.1
factor
(valor=33)
masFactores
(operando1=33
valor=33)
CTE_ENTERA
(valor=33)
Regla 9
Regla 5.1
Regla 8
Regla 5.2

Figura 9: rbol con anotaciones para el programa de entrada 33.
El hecho de que un atributo sea sintetizado, hace que su valor se calcule cuando el no ter-
minal se encuentra en su parte izquierda. Al no haberse calculado en la cuarta produccin,
hace que este valor no est disponible en la segunda regla de la primera produccin. As, el
valor que obtendra la expresin sera desconocido. Para evitar estos errores surge el con-
cepto de gramtica completa, obligando, entre otras cosas, a que todo atributo sintetizado
sea calculado cuando su no terminal aparezca a la izquierda de una produccin.

Ejemplo 18: Dada la siguiente gramtica atribuida:

(1) S

A G S.s = A.s + G.s
(2) A

F G G.h = F.s
A.s = G.s
(3) F

TOKEN_A F.s = TOKEN_A.s
(4) | TOKEN_B F.s = TOKEN_B.s
(5) G
1


TOKEN_C G
1
.s = G
1
.h + TOKEN_C.s
(6) | G
2
TOKEN_D G
2
.h = G
1
.h
G
1
.s = G
1
.h + TOKEN_D.s
G
2
.s = G
1
.h
No es completa por dos motivos:
El atributo G.s es sintetizado en la produccin 5 y heredado en la produccin 6.
El atributo heredado G.h no se calcula en la primera produccin, apareciendo el sm-
bolo gramatical G en la parte derecha de la misma.

4.3. Gramtica Atribuida Bien Definida
Una gramtica es bien definida
28
(tambin denominada no circular) si para todas las
sentencias del lenguaje generado por su gramtica libre de contexto, es posible calcular los
valores de los atributos de todos sus smbolos gramaticales [Waite84]. Este proceso de de-

28
Well-defined aatribute grammar.
Anlisis Semntico en Procesadores de Lenguaje
38
coracin o anotacin del rbol sintctico ante un programa de entrada se denomina eva-
luacin de la gramtica [Aho90].
Para que una gramtica sea bien definida (no circular), deber ser posible encontrar
un modo de calcular la gramtica ante cualquier programa de entrada. Esto implica saber en
qu orden se han de ejecutar las reglas semnticas ante cualquier programa, para poder eva-
luar la gramtica
29
. As, una gramtica es completa si asigna correctamente valores a sus
atributos y ser bien definida si, adems, es posible encontrar un orden de ejecucin de sus
reglas semnticas ante cualquier programa de entrada. Por lo tanto, toda gramtica atribuida
bien definida es completa.
Existe un algoritmo para calcular si una gramtica est bien definida [Jazayeri75]. Su
principal inconveniente es que posee una complejidad computacional exponencial.
Ejemplo 19: La siguiente gramtica atribuida:

<S>

<A> <B> A.a = B.b
B.b = A.a + 1
<A>

A
<B>

B
es una gramtica atribuida completa, puesto que los dos nicos atributos A.a y B.b son
heredados y en todas las producciones en las que A y B aparecen en la parte derecha, stos
son calculados. Sin embargo no est bien definida puesto que, dado el nico programa de
entrada vlido AB, es imposible evaluar la gramtica calcular el valor de los dos atributos.

4.4. Gramticas S-Atribuidas
Una gramtica es S-atribuida si todos sus atributos son sintetizados. Esta caracters-
tica especfica de determinadas gramticas atribuidas es empleado para preestablecer un
orden de ejecucin de sus rutinas semnticas como veremos en 5.2.
La gramtica del Ejemplo 11 es una gramtica S-atribuidas puesto que nicamente
posee atributos sintetizados.
4.5. Gramticas L-Atribuidas
Una gramtica es L-atribuida si para cada produccin de la forma:
n i VN VT X VN X X X X X
i n
1 , , , ...
0 2 1 0

todos los atributos heredados de X
j
, 1 jn, son calculados en funcin de:
Los atributos de los smbolos
1 2 1
,..., ,
j
X X X
Los atributos heredados de
0
X
La definicin anterior indica que, para que una gramtica sea L-atribuida, los atribu-
tos heredados de la parte derecha de toda produccin han de calcularse en funcin de los
heredados del no terminal de la izquierda y/o en funcin de los atributos de los smbolos
gramaticales de la parte derecha de la produccin, ubicados a su izquierda. En el caso de

29
Estudiaremos en mayor profundidad el clculo de orden de evaluacin de las reglas semnticas de una
gramtica atribuida en 1.
Tipos de Gramticas Atribuidas
39
que la condicin se satisfaga para una definicin dirigida por sintaxis, se dice que sta es
con atributos por la izquierda [Aho90].
Si nos fijamos en la definicin de gramtica L-atribuida, nos daremos cuenta que
especifica una restriccin de los atributos heredados de la gramtica. Si una gramtica es S-
atribuida, no posee atributo heredado alguno y, por tanto, es tambin L-atribuida: toda
gramtica S-atribuida es tambin L-atribuida. Vemos pues cmo la expresividad de las se-
gundas es superior a las de las primeras ya que se trata de un subconjunto.
Ejemplo 20: La gramtica atribuida completa presentada en el Ejemplo 13 y en el Ejemplo 16, es
una gramtica L-atribuida puesto que sus atributos heredados satisfacen la restriccin indi-
cada:

P Atributo Heredado Calculado Depende de Restriccin
(1) masTerminos.operando1 termino.valor
1
(2) masTerminos
2
.operando1 masTerminos
1
.operando1
termino.valor
2
1
(3) masTerminos
2
.operando1 masTerminos
1
.operando1
termino.valor

2
1
(5) masFactores.operando1 factor.valor
1
(6) masFactores.operando1 masFactores
1
.operando1
factor.valor
2
1
(7) masFactores.operando1 masFactores
1
.operando1
factor.valor
2
1
En la tabla anterior se indican los atributos heredados calculados en cada una de las pro-
ducciones. La tercera columna indica los valores empleados para realizar dicho clculo. Por
ltimo, la cuarta columna muestra si el atributo empleado para el clculo (el de la tercera
columna) es un heredado del no terminal de la izquierda (1 restriccin) o un atributo de un
smbolo gramatical de la parte derecha, situado a la izquierda del atributo calculado (2
restriccin).
Obviamente, al tener la gramtica atributos heredados, no es S-atribuida.

Ejemplo 21: La gramtica atribuida del Ejemplo 14:

P

R
(1) numero

digitos base numero.valor = digitos.valor
digitos.base = base.base
(2) base

o base.base = 8
(3) | d base.base = 10
(4) digitos
1


digitos
2

digito
digitos
1
.valor = digito.valor +
digitos
2
.valor

* digitos
1
.base
digitos
2
.base = digitos
1
.base
digito.base = digitos
1
.base
(5) digitos

digito digitos.valor = digito.valor
digito.base = digitos.base
(6) digito

0 digito.valor = 0
(7) | 1 digito.valor = 1
(8) | 2 digito.valor = 2
(9) | 3 digito.valor = 3
(10) | 4 digito.valor = 4
(11) | 5 digito.valor = 5
Anlisis Semntico en Procesadores de Lenguaje
40
P

R
(12) | 6 digito.valor = 6
(13) | 7 digito.valor = 7
(14) | 8 digito.valor = 8
(15) | 9 digito.valor = 9
No es una gramtica atribuida S-atribuida porque los atributos digitos.base y
digito.base son atributos heredados.
No es una gramtica L-atribuida puesto que en la primera produccin digitos.base
se calcula en funcin de base.base, y el smbolo gramatical del segundo atributo se
encuentra a la derecha del primero, en la produccin.
Si analizados las condiciones que se han de satisfacer para que la gramtica sea comple-
ta, nos daremos cuenta de que los cumple ( 4).

4.6. Traduccin de Gramticas S-Atribuidas a L-Atribuidas
Donald E. Knuth demostr cmo toda gramtica L-atribuida poda ser traducida a
otra gramtica S-atribuida que reconozca el mismo lenguaje, mediante un esquema de tra-
duccin de atributos heredados a atributos sintetizados.
Ejemplo 22: En el Ejemplo 12 empleamos la siguiente definicin dirigida por sintaxis con
atributos por la izquierda, para insertar en una tabla de smbolos los identificadores decla-
rados en un determinado lenguaje de programacin:

P R
declaracion

tipo
variables ;
variables.tipo = tipo.tipo
tipo

int tipo.tipo = I
tipo

float tipo.tipo = F
variables
1


id ,
variables
2

ts.insertar(id.valor,variables
1
.tipo)
variables
2
.tipo = variables
1
.tipo
variables

id ts.insertar(id.valor,variables.tipo)
Esta definicin dirigida por sintaxis se puede traducir a otra equivalente (que reconozca el
mismo lenguaje e inserte la misma informacin en la tabla de smbolos) tan solo con el
empleo de atributos sintetizados:

P R
declaracion

variables
id ;
ts.insertar(id.valor,variables.tipo)
variables
1


variables
2
id ,
variables
1
.tipo = variables
2
.tipo
ts.insertar(id.valor,variables
2
.tipo)
| tipo variables
1
.tipo = tipo.tipo
tipo

int tipo.tipo = I
| float tipo.tipo = F

Como hemos visto en el ejemplo anterior, el resultado de traducir una gramtica de
L a S-atribuida es que las producciones y las reglas semnticas de la nueva gramtica sern
ms complejas y difciles de entender por el desarrollador del compilador. No es por tanto
aconsejable esta traduccin, a no ser que sea estrictamente necesario. Este caso se podra
Tipos de Gramticas Atribuidas
41
dar si, por ejemplo, no tuvisemos un evaluador de gramticas L-atribuidas, sino uno que
nicamente reconociese gramticas S-atribuidas como es el caso de yacc/bison [Ma-
son92].
En ocasiones sucede que la estructura y evaluacin de una gramtica atribuida pare-
ce demasiado compleja. Es comn que se est dando el caso que no se haya escrito la gra-
mtica de un modo adecuado, haciendo demasiado uso de atributos sintetizados. La rees-
critura de la misma empleando ms atributos heredados suele reducir la complejidad de la
gramtica, as como su evaluacin.

43
5 Evaluacin de Gramticas Atribuidas
Como hemos mencionado en la definicin de gramtica atribuida ( 3.3), el orden
de evaluacin de sus atributos ante una sentencia de entrada no es llevada a cabo de un
modo imperativo, sino declarativo. En una gramtica atribuida (o definicin dirigida por
sintaxis) no se especifica el modo en el que se han de calcular los atributos, sino los valores
que stos deben tomar en funcin de otros. La evaluacin de los mismos se llevar a cabo
posteriormente mediante una herramienta evaluadora de gramticas atribuidas, un determi-
nado recorrido del AST o empleando un mecanismo ms dirigido hacia la sintaxis del len-
guaje, conocido como esquema de traduccin.
En este punto analizaremos cmo se debe llevar a cabo la evaluacin de los atribu-
tos de una gramtica atribuida, ya bien sea de un modo automtico gracias a la utilizacin
de una herramienta, o mediante alguna traduccin de sta a cdigo por parte del programa-
dor.
5.1. Grafo de Dependencias
El tercer valor que constitua una gramtica atribuida era un conjunto de ecuaciones
de atributos, tambin llamadas reglas semnticas ( 3.2). En ellas se establecen las relaciones
entre los valores de los atributos definidos en la gramtica atribuida asociada, mediante
ecuaciones del modo: ) ... , , ( :
3 2 1 k
a a a a f a = , siendo a y k i a
i
1 , atributos de los sm-
bolos gramaticales de una produccin asociada.
De la ecuacin general anterior, se deduce que el valor del atributo a depende de
los valores de los atributos k i a
i
1 , , implicando que sea necesario que se evale la regla
semntica para a en esa produccin despus de haberse evaluado las reglas semnticas que
definan los valores de k i a
i
1 , [Aho90].
Dada una gramtica atribuida, cada produccin tendr asociada un grafo de de-
pendencias locales
30
en el que se establecen, mediante un grafo dirigido, las dependencias
existentes entre todos los atributos que aparecen en la produccin [Wilhelm95]. Dicho gra-
fo tendr un nodo por cada uno de los atributos que aparezcan en las reglas semnticas
asociadas a la produccin. Aparecer una arista desde un nodo k i a
i
1 , hasta otro a,
siempre que a dependa de k i a
i
1 , ; es decir, siempre que exista una regla semntica
) ... , , ( :
3 2 1 k
a a a a f a = asociada a dicha produccin, indicando que es necesario calcular
k i a
i
1 , previamente a la regla semntica.
En la representacin de un grafo de dependencias locales de una produccin, cada
nodo que representa un atributo de un smbolo gramatical X estar agrupado con el resto
de atributos (nodos) asociados al mismo smbolo gramatical. As, el grafo estar estructura-

30
Production-local dependency graph.
Anlisis Semntico en Procesadores de Lenguaje
44
do en base al rbol sintctico y, por tanto, se suele representar superpuesto al subrbol sin-
tctico correspondiente a la produccin.
Ejemplo 23. En el Ejemplo 15 presentamos una gramtica atribuida que comprobaba la validez
semntica de las expresiones de un pequeo lenguaje de programacin. Para la ltima pro-
duccin tenamos las siguientes reglas semnticas asociadas:

P R
expresion
1

expresion
2
= expresion
3

expresion
2
.ids = expresion
1
.ids
expresion
3
.ids = expresion
1
.ids
expresion
1
.lvalue = expresion
2
.lvalue
expresion
1
.tipo=expresion
2
.tipo
Para la produccin anterior, el grafo de dependencias locales es el mostrado en la Figura 10:
expresion
expresion expresion = ids ids
ids tipo lvalue
tipo lvalue tipo lvalue

Figura 10: Grafo de dependencias locales para la produccin que define las asignaciones.
Ntese como las dependencias de los atributos, explicitadas en las reglas semnticas, estn
representadas mediante aristas dirigidas. Adems, cada atributo de un smbolo gramatical
est ubicado al lado de ste, superponiendo el grafo de dependencias al subrbol sintctico
(en tono gris) correspondiente a la produccin asociada.

Dado un programa de entrada perteneciente al lenguaje descrito por la gramtica
atribuida, su grafo de dependencias es la unin de los grafos de dependencias locales de
las producciones empleadas para reconocer el programa, representando cada nodo del r-
bol sintctico creado [Louden97].
Ejemplo 24. Empleando la misma gramtica atribuida del Ejemplo 15, el siguiente programa:
Satisface las restricciones sintcticas y semnticas del lenguaje definido
por dicha gramtica atribuida. Para ello, se definieron unos atributos y
un conjunto de reglas semnticas que defina la dependencia entre ellos.
Haciendo uso de la gramtica libre de contexto y de las reglas semnti-
cas definidas sobre sta, podremos obtener el siguiente grafo de depen-
dencias:
int i;
float r;
r=i*0.5;
Evaluacin de Gramticas Atribuidas
45
expresion
expresion expresion
=
expresion expresion *
id
id
cte_real
declaracion
;
sentencias
declaracion
;
sentencias
;
sentencias

sentencias
S
int id
float id
ids
ids
ids valor
valor
valor
valor
id
id
ids
ids
ids ids
ids ids
ids ids
tipo lvalue tipo lvalue
tipo lvalue tipo lvalue
tipo lvalue

Figura 11: Grafo de dependencias para el clculo de atributos, ante el programa especificado.
Sobre el rbol sintctico se ha ubicado todo atributo asociado a los smbolos gramaticales.
En cada una de las derivaciones del rbol (agrupacin de un nodo y sus hijos) generada por
la correspondiente produccin de la gramtica, se representa el grafo de dependencias loca-
les. El grafo resultante es el grafo de dependencias obtenido para el programa de entrada.
A modo de ejemplo, centrmonos en la produccin sptima (expresion id) y sus
reglas semnticas asociadas. Esta produccin tiene, en el programa de ejemplo, dos deriva-
ciones y, por tanto, dos subrboles asociados los dos en los que aparece id como nodo
hoja y expresion como su nodo padre. Las dos reglas semnticas asociadas a esta pro-
duccin son:

expresion.lvalue = true
expresion.tipo = expresion.ids[id.valor]
La primera no denota ninguna dependencia, puesto que puede ejecutarse sin requerir la
ejecucin de ninguna otra regla. Sin embargo, la segunda revela una dependencia del atribu-
to expresion.tipo respecto a los dos atributos expresion.ids e id.valor. Por
este motivo, se aprecia en el grafo de dependencia para los dos subrboles que represen-
tan las dos derivaciones una arista dirigida que representa las dependencias indicadas.

Si nos encontramos describiendo una definicin dirigida por sintaxis, puede darse el
caso de que una regla semntica sea la invocacin a un procedimiento o a algn mtodo
que no devuelva ningn valor y que, por tanto, dicha regla no sea empleada para asignar un
valor a un atributo. En este caso, para calcular el grafo de dependencias debe inventarse un
Anlisis Semntico en Procesadores de Lenguaje
46
atributo ficticio para el no terminal del lado izquierdo de la produccin, haciendo que de-
penda de los parmetros pasados al procedimiento.
Ejemplo 25. Recordemos la definicin dirigida por sintaxis presentada en el Ejemplo 12:

P R
declaracion

tipo
variables ;
variables.tipo = tipo.tipo
tipo

int tipo.tipo = I
tipo

float tipo.tipo = F
variables
1


id ,
variables
2

ts.insertar(id.valor,variables
1
.tipo)
variables
2
.tipo = variables
1
.tipo
variables

id ts.insertar(id.valor,variables.tipo)
La problemtica comentada surge en las reglas semnticas en las que se inserta un identifi-
cador en la tabla de smbolos. Al permitir los efectos colaterales de insercin de datos en
una estructura global en lugar de representar sta mediante atributos de la gramtica, nos
encontramos con la problemtica de que, para ejecutar la rutina de la ltima produccin:

ts.insertar(id.valor,variables.tipo)
es necesario que previamente se hayan hallado los valores de id.valor y
variables.tipo. Sin embargo, al no asignar stos a ningn otro atributo, no sabemos a
quin otorgar esta dependencia. Alfred Aho resuelve esta problemtica estableciendo la
dependencia con un atributo ficticio del no terminal de la parte izquierda (nodo padre). As,
podremos representar el grafo de dependencias, para la entrada float a,b;, del siguiente
modo:
variables
id
tipo ; variables
declaracion
,
id float
valor
tipo
valor
tipo

tipo

Figura 12: Grafo de dependencias para la entrada float a,b;.
El atributo inventado, ha sido representado con un punto.

Ordenamiento topolgico del grafo de dependencias
El grafo de dependencias de un programa de entrada para una gramtica atribuida
dada (o definicin dirigida por sintaxis) representa el conjunto de restricciones relativas al
orden que un algoritmo de evaluacin de la gramtica ha de satisfacer para calcular todos
los atributos. De este modo, cualquier algoritmo de evaluacin de la gramtica deber cal-
Evaluacin de Gramticas Atribuidas
47
cular un nodo (atributo) del grafo de dependencias antes de evaluar los atributos depen-
dientes de ste. Una ordenacin de los nodos de un grafo de dependencias que cumpla
dicha restriccin se denomina ordenamiento topolgico del grafo.
Un ordenamiento topolgico de un grafo de dependencias es todo ordenamiento
k
m m m ,..., ,
2 1
de los nodos del grafo tal que las aristas vayan desde los nodos que aparecen
primero en el orden a los que parecen ms tarde; es decir, si
j i
m m es una arista desde
i
m a
j
m , entonces
i
m aparece antes que
j
m en el orden topolgico [Aho90]. Todo orde-
namiento topolgico de un grafo de dependencias establece un orden vlido en el que se
pueden evaluar las reglas semnticas asociadas a los nodos del rbol sintctico.
La condicin necesaria y suficiente para que exista al menos un ordenamiento topo-
lgico para un grafo de dependencias es que el grafo sea acclico. Este tipo de grafos recibe
el nombre de grafos acclicos dirigidos (DAG, Directed Acyclic Graphs). Una gramtica atribuida
no circular (bien definida) es aquella para la que cualquier posible grafo de dependencias es
acclico de ah la importancia de crear gramticas atribuidas no circulares, para que pueda
evaluarse sta ante cualquier programa de entrada.
Ejemplo 26. A raz del grafo de dependencias del Ejemplo 25, podemos establecer un ordena-
miento topolgico de los nodos del mismo. Para ello, numeramos los atributos conforme a
la dependencia que puedan tener entre s:
variables
id
tipo ; variables
declaracion
,
id
float
valor
tipo
valor
tipo

tipo
1
2
3 4 5
6 7

Figura 13: Numeracin de los nodos del grafo de dependencias, estableciendo un ordenamiento topol-
gico sobre el mismo.
Los nmeros iniciales son asignados a los nodos que no poseen ninguna dependencia de
otro nodo. Una vez hecho esto, se numeran los nodos consecutivamente conforme depen-
dan de otros nodos que posean un valor menor. Pueden existir distintas numeraciones.
Un ordenamiento topolgico de los nodos del grafo establece un orden vlido en el que se
pueden evaluar las reglas semnticas asociadas a los nodos del rbol. De este modo, el or-
den de ejecucin de las reglas semnticas ser el indicado por los nodos del grafo. Si un
nodo del grafo corresponde a un smbolo terminal, no habr que calcular su atributo pues-
to que ste ya fue sintetizado por el analizador lxico.

Nmero
de nodo
Atributo Regla semntica donde se calcula Produccin
1
id.valor
Analizador Lxico Lxico
Anlisis Semntico en Procesadores de Lenguaje
48
Nmero
de nodo
Atributo Regla semntica donde se calcula Produccin
2
id.valor
Analizador Lxico Lxico
3
tipo.tipo tipo.tipo = F
3
4
variables.tipo variables.tipo = tipo.tipo
1
5
. ts.insertar(id.valor,
variables
1
.tipo)
4
6
varirables.tipo variables
2
.tipo =
variables
1
.tipo
4
7
ts.insertar(id.valor,
variables.tipo)
5
Puede, pues, convertirse el programa declarativo especificado con la definicin dirigida por
sintaxis en un programa imperativo que, empleando el rbol sintctico como principal es-
tructura de datos, ejecute la siguiente secuencia de sentencias
31
:

tipo.tipo
3
= F
variables.tipo
4
= tipo.tipo
3

ts.insertar(id.valor
2
,variables.tipo
4
)
variables.tipo
6
= variables.tipo
4

ts.insertar(id.valor
1
,variables.tipo
6
)

Ejemplo 27. En el Ejemplo 14 se present la siguiente gramtica atribuida que evaluaba el valor
y la correccin semntica de nmeros decimales y octales:
P

R
(1) numero

digitos base numero.valor = digitos.valor
digitos.base = base.base
(2) base

o base.base = 8
(3) | d base.base = 10
(4) digitos
1


digitos
2

digito
digitos
1
.valor = digito.valor +
digitos
2
.valor

* digitos
1
.base
digitos
2
.base = digitos
1
.base
digito.base = digitos
1
.base
(5) digitos

digito digitos.valor = digito.valor
digito.base = digitos.base
(6) digito

0 digito.valor = 0
(7) | 1 digito.valor = 1
(8) | 2 digito.valor = 2
(9) | 3 digito.valor = 3
(10) | 4 digito.valor = 4
(11) | 5 digito.valor = 5
(12) | 6 digito.valor = 6
(13) | 7 digito.valor = 7
(14) | 8 digito.valor = 8
(15) | 9 digito.valor = 9
Cabra preguntarnos cul sera un orden de evaluacin de los atributos ante la entrada de la
sentencia 71o. Su grafo de dependencias con un ordenamiento topolgico vlido del mis-
mo es:

31
A los atributos de los nodos se les ha aadido el subndice de su ordenacin topolgica, para evitar
ambigedades entres las distintas ocurrencias (instancias) de cada atributo.
Evaluacin de Gramticas Atribuidas
49
digitos
digito
digitos
1
base
numero
o
digito
10valor
7
3base
1valor
2valor
4base
5base
6base
7base
8valor
9valor

Figura 14: Grafo de dependencias para la entrada 71o".
Tras el ordenamiento identificado con la numeracin de los nodos del grafo, podemos
concluir que una secuencia vlida de ejecucin de las reglas semnticas es la siguiente:

digito.valor
1
= 7
digito.valor
2
= 1
base.base
3
= 8
base.base
4
= base.base
3

digito.base
5
= base.base
4

digitos.base
6
= base.base
4

digito.base
7
= digitos.base
6

digitos.valor
8
= digito.valor
1

digitos.valor
9
= digito.valor
2
+digitos.valor
8
*digitos.base
6

numero.valor
10
= digitos.valor
9

Ntese cmo, tras la ejecucin de las sentencias previas, el valor del atributo
digito.valor es igual a 57 valor de 71 en octal.

Evaluacin de una gramtica atribuida
Los pasos necesarios para evaluar una gramtica atribuida se pueden precisar como
sigue. Se utiliza la gramtica libre de contexto subyacente para construir, a partir del pro-
grama de entrada, su rbol sintctico (o su AST en funcin de si la gramtica es concreta o
abstracta). Se construye un grafo de dependencias para el programa de entrada. Se establece
un orden topolgico de evaluacin de las reglas semnticas de la gramtica atribuida. Se
ejecutan las reglas semnticas siguiendo el ordenamiento calculado, traduciendo as la gra-
mtica atribuida a un programa imperativo que trabaja sobre una estructura de datos: el
rbol sintctico. Recordemos que para que esto pueda efectuarse ante cualquier programa
de entrada, la condicin necesaria y suficiente es que la gramtica sea no circular y, por
tanto, todo grafo de dependencias sea acclico.
Para llevar a cabo el proceso de evaluacin de una gramtica atribuida existen prin-
cipalmente dos mtodos [Louden97, Aho90]:
Mtodos de rbol sintctico
32
. En el momento de procesamiento del lenguaje,
estos mtodos obtienen un orden de evaluacin a partir de un ordenamiento
topolgico del grafo de dependencias, construido para cada entrada. Para poder
llevarlo a cabo, se tiene que comprobar la no-circularidad de la gramtica atri-

32
Parse tree methods.
Anlisis Semntico en Procesadores de Lenguaje
50
buida, cuya ejecucin es de complejidad exponencial [Jazayeri75]
33
. Adicional-
mente, la creacin del grafo de dependencias y la obtencin de un ordenamien-
to topolgico cada vez que se procesa un programa, supone una complejidad
adicional.
Existen herramientas que implementan este mtodo generando, tras escribir la
gramtica con una sintaxis determinada, un evaluador de la misma si sta no es
circular; ejemplos de estas herramientas son FNC-2 [FNC2], Ox [Bischoff92],
Elegant [Jansen93] o lrc [LRC].
Mtodos basados en reglas
34
. La alternativa al mtodo anterior, adoptado por
prcticamente la totalidad de los desarrollos, se basa en analizan las reglas de la
gramtica atribuida en el momento de la construccin del compilador, fijando a
priori el orden de evaluacin de las mismas. La gramtica atribuida se clasifica (
4) y se establece para ella el mecanismo de evaluacin ms propicio. Aunque es-
te mtodo es menos general que el anterior, en la prctica es posible encontrar
una gramtica atribuida que cumpla estas propiedades.
Los siguientes puntos dentro de esta seccin estarn enfocados a analizar cmo,
en funcin del tipo de gramtica, se puede evaluar sta empleando un mtodo
basado en reglas.
5.2. Evaluacin de Atributos en una Pasada
Cuando el procesador de lenguaje es de una pasada, la evaluacin de los atributos
de una gramtica atribuida (y por tanto la ejecucin de cada una de sus reglas semnticas) es
llevada a cabo al mismo tiempo que se produce el anlisis sintctico ( 2.2). Histricamente,
la posibilidad de que un compilador pudiese llevar a cabo todas sus fases durante el anlisis
sintctico en una nica pasada era de especial inters por el ahorro computacional y de
memoria que representaba. En la actualidad, al existir mayor capacidad de cmputo y me-
moria de los computadores, dicho caracterstica no cobra tanta importancia. Al mismo
tiempo, la complejidad de los lenguajes actuales hace que sea obligatorio su procesamiento
en varias pasadas.
En el punto anterior sealbamos cmo los mtodos basados en reglas son capaces
de identificar una forma de ejecutar las reglas semnticas de la gramtica atribuida en fun-
cin de propiedades que han de satisfacer dichas reglas. As, cuando deseemos evaluar los
atributos de una gramtica atribuida en una sola pasada, en funcin su tipo de reglas se
podr llevar a cabo este proceso con un anlisis sintctico u otro. Es decir, el modo en el
que escribamos las reglas de la gramtica atribuida determinar el tipo de anlisis sintctico
que podremos utilizar ascendente o descendente.
Las principales propiedades de una gramtica atribuida que indican el modo en el
que stas deban ser evaluadas son las caractersticas de sus atributos (sintetizados y hereda-
dos) y, por tanto, la clasificacin de gramticas S y L-atribuidas ( 4).
Un factor importante es que la mayora de los algoritmos de anlisis sintctico pro-
cesan el programa de entrada de izquierda a derecha (por esta razn los analizadores sintc-
ticos ascendentes o LR, y descendentes o LL, comienzan con una L
35
). Este orden implica

33
Puesto que sta es una condicin de la gramtica atribuida, la computacin slo debera llevarse a cabo
una nica vez tras la escritura de la gramtica atribuida y no cada vez que se procese un programa de
entrada.
34
Rule-based methods.
35
La L (left) indica que el programa de entrada es analizado de izquierda a derecha. Dada una sentencia
de entrada, ste es el orden en el que el analizador lxico le pasa los tokens al analizador sintctico.
Evaluacin de Gramticas Atribuidas
51
una restriccin en la evaluacin, puesto que la utilizacin de los atributos de los elementos
terminales de la gramtica debe seguir este mismo orden.
Evaluacin ascendente de gramticas S-atribuidas
Toda gramtica S-atribuida se puede evaluar ascendentemente, calculando los atri-
butos todos ellos sintetizados conforme el programa de entrada es analizado [Aho90]. El
analizador sintctico deber almacenar en su pila los valores de los atributos sintetizados
asociados a cada uno de los smbolos gramaticales. En el momento en el que se lleve a cabo
una reduccin, se calcularn los valores de los nuevos atributos sintetizados (del no termi-
nal de la izquierda de la produccin) en funcin de los atributos que estn en la pila (de los
no terminales de la parte derecha, entre otros). Este mecanismo es el llevado a cabo por la
herramienta yacc/bison.
El poder clasificar una gramtica atribuida como S-atribuida ofrece, por tanto, dos
beneficios frente al concepto general de gramtica atribuida:
1. Sin necesidad de calcular el grafo de dependencias, se conoce a priori el orden
de ejecucin de las reglas semnticas. No es necesario ordenar topolgicamente
el grafo (ni siquiera crearlo) para conocer su orden de evaluacin.
2. Slo es necesario visitar una vez cada nodo del rbol sintctico creado ante la
entrada de un programa
36
. Una vez ejecutada la regla semntica asociada al no-
do del rbol, no necesitar volver a procesar ste. Esta propiedad conlleva una
clara mejora de eficiencia en tiempo de compilacin.
Evaluacin descendente de gramticas L-atribuidas
Dada una gramtica atribuida que cumpla las condiciones de gramtica L-
atribuida
37
, podr ser evaluada mediante un analizador sintctico descendente recursivo:
La gramtica libre de contexto deber ser LL
38

Cada smbolo no terminal ser traducido a una funcin (mtodo) que reciba sus
atributos heredados como parmetros y devuelva sus atributos sintetizados en
cada invocacin. Cada mtodo asociado a un no terminal ha de realizar, adems
del anlisis sintctico, la evaluacin de sus reglas semnticas asociadas.
Los atributos heredados de un no terminal A debern ser calculados antes de la
invocacin a su mtodo asociado, y stos debern ser pasados como parmetros
a la misma.
Los atributos sintetizados de un no terminal A debern ser calculados en la im-
plementacin de su funcin (mtodo) y devueltos tras su invocacin.
La principal ventaja de este algoritmo, adicionalmente a conocer a priori el recorri-
do de evaluacin de las reglas semnticas, es que nicamente tiene que invocarse una vez a
cada mtodo representante de cada nodo del rbol sintctico.
Ejemplo 28. En el Ejemplo 13 se defina la siguiente gramtica L-atribuida para evaluar el valor
de una expresin:


36
Aunque en los procesadores de una pasada el rbol no se crea explcitamente, ste s existe, represen-
tndose cada uno de sus nodos como un contexto en la pila del reconocedor.
37
O una definicin dirigida por sintaxis con atributos por la izquierda.
38
La gramtica deber ser descendente y acorde con el algoritmo de anlisis sintctico, es decir, gramti-
ca LL1 si el algoritmo tiene un lookahead 1 o LL(k) si el algoritmo permite un k determinado.
Anlisis Semntico en Procesadores de Lenguaje
52
P

(1) expresion

termino masTerminos
(2) masTerminos
1


+ termino masTerminos
2

(3) | - termino masTerminos
2

(4) |

(5) termino

factor masFactores
(6) masFactores
1


* factor masFactores
2

(7) | / factor masFactores
2

(8) |

(9) factor

CTE_ENTERA

R
(1) masTerminos.operando1 = termino.valor
expresion.valor = masTerminos.valor
(2) masTerminos
2
.operando1=masTerminos
1
.operando1+termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(3) masTerminos
2
.operando1=masTerminos
1
.operando1-termino.valor
masTerminos
1
.valor = masTerminos
2
.valor
(4) masTerminos
1
.valor = masTerminos
1
.operando1
(5) masFactores.operando1 = factor.valor
termino.valor = masFactores.valor
(6) masFactores
2
.operando1=masFactores
1
.operando1*factor.valor
masFactores
1
.valor = masFactores
2
.valor
(7) masFactores
2
.operando1=masFactores
1
.operando1/factor.valor
masFactores
1
.valor = masFactores
2
.valor
(8) masFactores
1
.valor = masFactores
1
.operando1
(9) factor.valor = CTE_ENTERA.valor
Es una gramtica LL1, por lo que implementar un analizador sintctico descendente recur-
sivo predictivo con un lookahead de 1 es relativamente sencillo: traducimos cada no terminal
en un mtodo recursivo de una clase Sintactico y cada terminal a una comprobacin de
que el token esperado es el actual denominada comnmente match [Watt00].
Al mismo tiempo que se reconoce la estructura sintctica del programa de entrada, es posi-
ble evaluar los atributos de la gramtica atribuida. Los mtodos que representan los no
terminales de la gramtica recibirn tantos parmetros como atributos heredados posean, y
devolvern los valores de sus atributos sintetizados
39
. En el cuerpo de los mtodos se tra-
ducirn las reglas semnticas a cdigo, calculando los atributos heredados de un no termi-
nal previamente a su invocacin para, en su llamada, pasrselos como parmetros. Los atri-
butos sintetizados de los smbolos de la parte derecha se obtendrn como retorno de su
invocacin. Finalmente, el mtodo deber devolver el clculo de los atributos sintetizados
del no terminal de la parte izquierda de la produccin.
Siguiendo este esquema de traduccin, podremos codificar en Java la primera produccin y
sus reglas semnticas del siguiente modo:

/** Mtodo que reconoce sintcticamente el
* no terminal "expresion".<br/>
* Produccin: expresion -> terminos masTerminos<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (ninguno) y
* devuelve los sintetizados (expresion.valor)<br/>
*/
public int expresion() {

39
El modo de devolver mltiples valores por un mtodo vara en funcin del leguaje de programacin
elegido: con registros, direcciones de memoria, vectores de elementos o incluso empleando herencia.
Evaluacin de Gramticas Atribuidas
53
// * Regla: masTerminos.operando1 = termino.valor
int masTerminosOperando1=termino();
// expresion.valor = masTerminos.valor
return masTerminos(masTerminosOperando1);
}
La traduccin de la parte derecha de la produccin, en lo referente al anlisis sintctico, es
trivial: se traducen los no terminales a invocaciones. En el caso de la traduccin de las re-
glas semnticas, se obtiene el atributo sintetizado termino.valor tras la invocacin a
termino. ste es utilizado para asignrselo al atributo heredado
masTerminos.operando1 primera regla. Se le pasa como parmetro a su invocacin y
el atributo sintetizado que nos devuelve es precisamente el que devolver expresion
segunda regla.
Siguiendo con este esquema, podremos traducir las producciones 2, 3 y 4, as como sus
reglas semnticas:

/** Mtodo que reconoce sintcticamente el no
* terminal "masTerminos".<br/>
* Produccion: masTerminos1 -> '+' termino masTerminos2<br/>
* Produccion: masTerminos1 -> '-' termino masTerminos2<br/>
* Produccion: masTerminos1 -> lambda<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados
* (masTerminos.operando1) y devuelve los sintetizados
* (masTerminos.valor)<br/>
*/
private int masTerminos(int operando1) {
int token=lexico.getToken();
switch (token) {
case '+': lexico.match('+');
// * Regla: masTerminos2.operando1=masTerminos1.operando1+termino.valor
int masTerminos2Operando1=masTerminos(operando1+termino());
// * Regla: masTerminos1.valor = masTerminos2.valor
return masTerminos2Operando1;
case '-': lexico.match('-');
// * Regla: masTerminos2.operando1=masTerminos1.operando1-termino.valor
masTerminos2Operando1=masTerminos(operando1-termino());
// * Regla: masTerminos1.valor = masTerminos2.valor
return masTerminos2Operando1;
default: // * lambda
// * Regla: masTerminos1.valor = masTerminos1.operando1
return operando1;
}
}
Al tratarse de un analizador descendente recursivo predictivo con un lookahead igual a 1, lo
que se hace para distinguir por qu parte derecha producir es consultar, precisamente, el
valor de dicho lookahead. Sabiendo el componente lxico actual, podremos conocer por cul
de las tres producciones derivar. Ntese cmo la aparicin de un elemento terminal en la
parte derecha es traducida a una invocacin al mtodo match del analizador lxico. La
traduccin del resto de reglas semnticas es igual que el comentado previamente.
Siguiendo este esquema es posible evaluar la gramtica L-atribuida ante cualquier programa
de entrada, invocando una nica una vez a cada mtodo asociado a cada smbolo gramatical
del rbol. Para consultar cualquier otra faceta de la implementacin en Java de este evalua-
dor, consltese el apndice B.

El reconocimiento sintctico del programa de entrada y su evaluacin (clculo de
los atributos) se lleva a cabo tras la invocacin del mtodo asociado al no terminal inicial.
El valor devuelto ser el conjunto de atributos sintetizados del no terminal inicial.
Anlisis Semntico en Procesadores de Lenguaje
54
Cuando un mtodo es invocado, recibir como parmetros los valores de sus atri-
butos heredados, deber invocar a los no terminales de la parte derecha, calcular sus atri-
butos sintetizados y, finalmente, los retornar. Este proceso se hace de un modo recursivo.
Fjese como el orden de invocacin de los no terminales de la parte derecha tiene que ser
de izquierda a derecha, puesto que sta la restriccin impuesta en la definicin de gramtica
L-atribuida ( 4):
Si un atributo heredado de un smbolo de la parte derecha depende de un atri-
buto heredado del no terminal de la izquierda, este valor ya ha sido calculado
puesto que se ha recibido como parmetro en el mtodo que se est ejecutando.
Si un atributo heredado de un smbolo de la parte derecha depende de un atri-
buto de otro smbolo de la parte derecha, el segundo ha de estar la izquierda del
primero (si no, no sera L-atribuida). Puesto que la invocacin de mtodos se
realiza siguiendo un orden de izquierda a derecha, los atributos necesarios esta-
rn siempre disponibles ya que sus mtodos asociados fueron invocados pre-
viamente.
Esta tcnica de evaluacin descendente de gramticas atribuidas es la llevada a cabo
por las herramientas descendentes AntLR [ANTLR] y JavaCC [JavaCC].
Evaluacin ascendente de atributos heredados
Hemos comentado cmo una gramtica S-atribuida puede ser fcilmente evaluada
por un analizador sintctico ascendente. Para ello, la pila del analizador deber aumentarse
para albergar los valores de los atributos de los smbolos gramaticales. En cada reduccin
se evaluarn los atributos sintetizados del no terminal de la izquierda, empleando pare ello
los valores de los atributos de la parte derecha de la produccin.
En la clasificacin de gramticas presentadas en 4 veamos cmo las gramticas S-
atribuidas constituan un subconjunto de las L-atribuidas y, por tanto, posean una expresi-
vidad menor. As, el poder calcular atributos heredados mediante un analizador sintctico
ascendente siempre representar una caracterstica positiva del mismo.
Dada la siguiente gramtica atribuida:

<S>

<A> <B> B.e = f(A.s)
<A>

a A.s = a.s
<B>

b B.s = g(B.e)
Existe la necesidad de un atributo heredado B.e. Este caso es el mismo que se daba
en el Ejemplo 12. El atributo heredado, en un esquema de evaluacin ascendente, no se
poda pasar a la ltima produccin para que pueda calcularse B.s. Sin embargo, la gramti-
ca anterior se puede rescribir aadiendo un no terminal vaco justo antes del smbolo que
necesita el atributo heredado.

(1) <S>

<A> <C> <B>
(2) <A>

a A.s = a.s
(3) <B>

b B.s = g(topepila-1.e)
(4) <C>

C.e = f(topepila.s)
El lenguaje reconocido por la nueva gramtica no vara puesto que el no terminal
introducido produce el vaco. Sin embargo, la modificacin de la misma permite simular el
clculo del atributo heredado B.e. Puesto que el no terminal C se ha puesto en la primera
produccin justo antes del no terminal B, cuando C sea reducido por el vaco en la cuarta
produccin podr acceder a todos los atributos de los que depende, al estar stos en la pila.
Evaluacin de Gramticas Atribuidas
55
En nuestro ejemplo depende nicamente de A.s y podr acceder a l puesto que siempre
se encontrar en el tope de la pila ya que est justo a su izquierda en la primera produc-
cin
40
.
Una vez calculado el valor de C.e, ste se posicionar en el tope de la pila al haber-
se reducido la cuarta produccin. Puesto que B es el siguiente smbolo de C, la siguiente
reduccin ser aqulla en la que B aparezca en la parte izquierda produccin 3. Cmo
podr la regla semntica de la tercera produccin acceder al atributo B.e? Como su parte
derecha est en el tope de la pila, y B justo debajo, deber restar al tope de la pila tantos
elementos como smbolos aparezcan en su parte derecha. En nuestro caso slo est el sm-
bolo terminal b, por lo que el acceso a B.e ser acceder al tope de la pila menos uno.
Hemos visto cmo es posible, siempre que se tenga acceso a los valores de la pila
de un reconocedor ascendente, simular atributos heredados en un analizador sintctico
ascendente. Esta traduccin tiene un conjunto de limitaciones que estn en funcin del
modo en el que se ha escrito la gramtica. Dichas limitaciones pueden ser consultadas en
[Scott00] y [Aho90].
El generador de analizadores sintcticos ascendentes yacc/bison ofrece, de un mo-
do implcito, esta traduccin de definiciones dirigidas por sintaxis. Esta herramienta permi-
te ubicar la asignacin de atributos heredados como cdigo C dentro de una produccin.
La siguiente especificacin yacc/bison, produce el mismo resultado que nuestra gramtica
atribuida:

S: A {$<e>$=f($<s>1);} B
;
A: a {$<s>$=$<s>1;}
;
B: b {$<s>$=g($<e>-1);}
;
En yacc/bison, la regla semntica ubicada en el medio de la primera produccin es
reemplazada por un nuevo smbolo no terminal inventado. Adicionalmente se aade una
regla en la que este nuevo no terminal produce el vaco, y su regla semntica asociada es
una traduccin del cuerpo descrito en el medio de la primera produccin. Por este motivo,
al acceder a $$ en el medio de una produccin no se hace referencia al no terminal de la
izquierda, sino al atributo del no terminal inventado. Es decir, yacc/bison traduce el cdigo
anterior a:

S: A $$1 B
;
A: a {$<s>$=$<s>1;}
;
B: b {$<s>$=g($<e>-1);}
;
$$1: {$<e>$=f($<s>0);}
;
Este nuevo cdigo posee precisamente la traduccin hecha previamente por noso-
tros, empleando la sintaxis adecuada de la herramienta yacc/bison.
Hemos visto cmo las gramticas S y L-atribuidas pueden evaluarse en una nica
pasada al mismo tiempo que se lleva a cabo el anlisis sintctico. Adems, para ambas se
puede identificar un modo de evaluarlas a priori, sin necesidad de establecer un grafo de
dependencias y un ordenamiento topolgico. Las gramticas S-atribuidas son un subcon-
junto de las L-atribuidas y se pueden evaluar ascendente y descendentemente, respectiva-

40
Si dependiese de ms smbolos ubicados a su izquierda (situados todos en la parte derecha de la pro-
duccin) podra obtener sus valores mediante el acceso a los registros anteriores al tope de la pila.
Anlisis Semntico en Procesadores de Lenguaje
56
mente. Surge la irona de que, de un modo opuesto, las gramticas ascendentes (LR) poseen
ms expresividad que las descendentes (LL) [Scott00]. As, las gramticas ascendentes per-
miten representar sintcticamente un mayor nmero de lenguajes, pero stas poseen una
menor capacidad a la hora de representar un clculo de propiedades (atributos) para las
distintas construcciones del lenguaje. Por este motivo surgen dos soluciones prcticas:
Las herramientas ascendentes permiten acceder directamente a la pila del reco-
nocedor, ofreciendo al desarrollador del compilador la posibilidad de simular
atributos heredados mediante traducciones (explcitas o implcitas) de la gram-
tica.
Las herramientas descendentes amplan su capacidad de reconocimiento sintc-
tico mediante tcnicas como el backtracking selectivo, modificacin del lookahead,
notacin extendidas (EBNF) o el empleo de predicados semnticos.
5.3. Evaluacin de Atributos en Varias Pasadas
En el punto anterior ( 5.2) hemos analizado cmo pueden evaluarse las gramticas
atribuidas en el desarrollo de procesadores de una sola pasada, empleando para ello mto-
dos basados en reglas ( 5.1): establecimiento esttico de un orden de ejecucin de las re-
glas semnticas, en funcin de las caractersticas de la gramtica atribuida.
Los procesadores de lenguaje de una pasada poseen los beneficios de ser ms efi-
cientes (menor tiempo de compilacin) y requerir menos memoria. Sin embargo, es ms
sencillo realizar el anlisis semntico como un recorrido del AST puesto que refleja de un
modo sencillo la estructura del programa de entrada, se puede elegir el orden de evaluacin
en funcin de la propiedad analizada y, finalmente, se pueden dar tantas pasadas al AST
como sea necesario. A modo de ejemplo, David Watt [Watt00] identifica que las dos tareas
principales a llevar a cabo por un lenguaje de programacin tpico (con tipos y mbitos
estticos) son la comprobacin de mbitos y tipos
41
. Para ello, el analizador semntico pue-
de desarrollarse en dos pasadas:
Identificacin: Aplicando las reglas de mbitos que define el lenguaje, cada utili-
zacin de un identificador en una expresin ser resuelta conociendo su entidad
concreta dentro del programa de entrada. El resultado de este recorrido del
AST es que cada nodo de tipo identificador tendr asociado una referencia a su
smbolo y tipo apropiados.
Comprobacin de tipos: Posteriormente a la pasada de identificacin, se va re-
corriendo en AST para, aplicando las reglas semnticas de inferencia de tipos
del lenguaje, poder asignar un tipo a las distintas construcciones del programa.
Conforme se van infiriendo los tipos, se realizarn las comprobaciones de que
las operaciones llevadas a cabo con los mismos sean las correctas
42
.
El nmero de pasadas al AST que son necesarias para llevar a cabo el anlisis se-
mntico de un lenguaje est funcin de sus reglas semnticas.

41
La comprobacin de mbitos (scope rules) valida la correcta utilizacin de los identificadores en cada
mbito, y aplica las reglas de ocultacin en mbitos anidados. La comprobacin de tipos (type rules)
conlleva aplicar las reglas semnticas para inferir los tipos y restringir las operaciones que se pueden
aplicar a cada expresin, en funcin de su tipo.
42
Por ejemplo, al resultado de la invocacin de una funcin (o mtodo), slo se le podr aplicar el opera-
dor [] si el tipo devuelto es un vector si acepta la operacin [].
Evaluacin de Gramticas Atribuidas
57
Recorrido del AST
Hemos visto cmo en el desarrollo de compiladores reales es necesario procesar el
programa de entrada en mltiples pasadas [Scott00]. En ocasiones, el propio anlisis se-
mntico ya requiere ms de una pasada (identificacin y comprobacin de tipos) y el resto
las fases siguientes realizan su cometido con pasadas aparte (generacin de cdigo interme-
dio, generacin y optimizacin de cdigo, e interpretacin). El AST es comnmente la es-
tructura de datos empleada para cada una de las fases
43
, y cada una de ellas es implementa-
da con distintos recorridos sobre el AST.
Para realizar un compilador de varias pasadas, ser necesario un diseo del mismo
que nos permita realizar cada una de las pasadas sin cambiar la estructura del AST y sepa-
rando claramente el cdigo asociado a cada una de ellas. Una solucin ampliamente utiliza-
da
44
para este tipo de problemas es el patrn de diseo Visitor [GOF02].
Un patrn de diseo es una descripcin de un conjunto de clases y objetos que se
comunican entre s, empleados para resolver un problema general de diseo dentro un con-
texto particular [GOF02]. Un patrn de diseo nombra, abstrae e identifica los aspectos
clave de una estructura comn de diseo til para ser reutilizada en la resolucin de una
serie de problemas similares.
En el caso que nos atae, el patrn de diseo Visitor es capaz de modelar distintas
operaciones a llevar a cabo sobre una misma estructura de datos, permitiendo definir nue-
vas operaciones sin modificar la estructura del diseo. La estructura esttica de este patrn
es la mostrada en la Figura 15.

43
En la fase de optimizacin de cdigo, es comn traducir este estructura de datos en un grafo dirigido
acclico [Muchnick97].
44
Existen herramientas de desarrollo de compiladores como SableCC [SableCC] que hacen uso intensivo
de este patrn de diseo.
Anlisis Semntico en Procesadores de Lenguaje
58
VisitorConcretoA
visitar(e : ElementoConcretoA)
visitar(e : ElementoConcretoB)
VisitorConcretoB
visitar(e : ElementoConcretoA)
visitar(e : ElementoConcretoB)
ElementoConcretoA
aceptar(v : Visitor)
operacinA()
ElementoConcretoB
aceptar(v : Visitor)
operacinB()
v->visitar(this)
Elemento
aceptar(v : Visitor)
Visitor
visitar(e : ElementoConcretoA)
visitar(e : ElementoConcretoB)
Cliente
Estructura
nn
v->visitar(this)

Figura 15: Diagrama de clases del patrn de diseo Visitor.
Las distintas abstracciones del diseo son las que siguen:
Elemento. Clase comnmente abstracta que define el mtodo polimrfico
aceptar, recibiendo un Visitor como parmetro. En nuestro problema es la
clase base de todo nodo del AST.
Elementos Concretos. Cada una de las estructuras concretas a recorrer. Adicio-
nalmente a implementar el mtodo aceptar como una visita concreta, podrn
tener una estructura y comportamiento propio. Son cada una de las estructuras
sintcticas del lenguaje, representadas como nodos del AST.
Estructura. Es una coleccin de elementos. En el caso de un AST, cada nodo
poseer la coleccin de sus elementos mediante referencias a la clase
Elemento y, por tanto, esta abstraccin estar dentro de los nodos
45
.
Visitor. Clase comnmente abstracta que define una operacin de visita por ca-
da elemento concreto de la estructura a recorrer. Cada uno de sus mtodos se
deber redefinir (en las clases derivadas) implementando el recorrido especfico
de cada nodo.
Visitor Concreto. Cada uno de los recorridos de la estructura ser definido por
un Visitor concreto, separando su cdigo en clases distintas. La especificacin
de los recorridos vendr dada por la implementacin de cada uno de sus mto-
dos de visita, que definirn el modo en el que se recorre cada nodo del AST.
stos podrn acceder a los nodos de la estructura mediante el parmetro recibi-
do.

45
Realmente, esta coleccin de nodos por parte de cada nodo es diseada mediante otro patrn de diseo
denominado Composite.
Evaluacin de Gramticas Atribuidas
59
Siguiendo este patrn, el analizador sintctico crear el AST con la estructura des-
crita por Elemento y sus clases derivadas. Despus, para cada una de las distintas pasadas,
se crear un Visitor concreto y se le pasar el mensaje aceptar al nodo raz del AST con el
Visitor como parmetro. El Visitor concreto definir el orden y modo de evaluar un conjun-
to de atributos del AST. Como el orden de ejecucin de cada una de las pasadas es secuen-
cial, los atributos calculados que sean necesarios de una pasada a otra podrn asignarse a
atributos de cada uno de los objetos del AST. Por ejemplo, el tipo de cada una de las expre-
siones inferido en fase de anlisis semntico es necesario para llevar a cabo tareas de gene-
racin de cdigo.
Ejemplo 29. Dada la sintaxis del siguiente lenguaje en notacin yacc/bison:

expresion: expresion '+' expresion
| expresion '-' expresion
| expresion '*' expresion
| expresion '/' expresion
| expresion '=' expresion
| '-' expresion
| '(' expresion ')'
| CTE_ENTERA
| CTE_REAL
;
Se puede crear un AST en la fase de anlisis sintctico, al igual que se hizo en el Ejemplo 7,
con la siguiente estructura:
ConstanteEntera
aceptar(v : Visitor*)
ExpresionUnaria
operador : char
operando : Expresion*
aceptar(v : Visitor*)
getOperando() : Expresion*
Expresion
valor : double
codigo : ostringstream
tipo : char
aceptar(v : Visitor*)
11
ExpresionBinaria
operador : char
operando1 : Expresion*
operando2 : Expresion*
aceptar(v : Visitor*)
getOperando1() : Expresion*
getOperando2() : Expresion*
2
ConstanteReal
aceptar(v : Visitor*)

Figura 16: Estructura esttica de los AST empleados para las expresiones del lenguaje presentado.
Para implementar un compilador e intrprete del lenguaje, identificamos las siguientes pa-
sadas al AST:
La pasada de anlisis semntico que infiere el tipo de toda expresin y comprueba que
se cumplen todas las reglas semnticas. El nico caso semnticamente incorrecto del
lenguaje debido a su sencillez es comprobar que no se asigne un entero a un real
46
.
Aadimos el atributo tipo al nodo raz del AST, puesto que nodos los nodos deben
tener asociado un tipo.

46
Realmente, todas las asignaciones son incorrectas porque tratamos nicamente con constantes. Sin
embargo, lo especificaremos as para, en un futuro, poder aadir identificadores a las expresiones.
Anlisis Semntico en Procesadores de Lenguaje
60
Una pasada sera la generacin de cdigo. Aadimos, pues, un atributo al nodo raz del
AST para albergar el cdigo de cada construccin sintctica del lenguaje.
Otra pasada sera el clculo del valor de la expresin. Este escenario podra darse tanto
en la optimizacin de cdigo de un compilador, como en la implementacin de un in-
trprete.
Finalmente, para poder hacer trazas del procesador de lenguaje en tiempo de desarrollo,
identificaremos una pasada que muestre el contenido del AST.
A modo de ejemplo, la siguiente sera la implementacin en C++ de los mtodos del Visitor
que calcula el valor de la expresin:

#include "visitorcalculo.h"
#include <cassert>

void VisitorCalculo::visitar(ExpresionUnaria *e) {
assert(e->getOperador()=='-');
// * Calculo el valor del operando
e->getOperando()->aceptar(this);
e->valor= - e->getOperando()->valor;
}

void VisitorCalculo::visitar(ExpresionBinaria *e) {
// * Calculo el valor de los operandos
e->getOperando1()->aceptar(this);
e->getOperando2()->aceptar(this);
// * Calculo el valor del nodo
switch (e->getOperador()){
case '+': e->valor=e->getOperando1()->valor +
e->getOperando2()->valor; break;
case '-': e->valor=e->getOperando1()->valor -
e->getOperando2()->valor; break;
case '*': e->valor=e->getOperando1()->valor *
e->getOperando2()->valor; break;
case '/': e->valor=e->getOperando1()->valor /
e->getOperando2()->valor; break;
case '=': e->valor=e->getOperando2()->valor; break;
default: assert(0);
}
if (e->getOperando1()->tipo=='I' && e->getOperando2()->tipo=='I')
e->valor=(int)e->valor;
}

void VisitorCalculo::visitar(ConstanteEntera *c) {}

void VisitorCalculo::visitar(ConstanteReal *c) {}
El clculo de un nodo que sea una expresin unaria requiere primero el clculo de su nico
operando. Para ello, invoca recursivamente al mtodo aceptar de la expresin almacena-
da como nico operando. Una vez calculada sta, asigna al atributo del nodo
ExpresionUnaria el valor calculado, cambiado de signo.
Respecto al clculo de las expresiones binarias el proceso es similar. Primero se calcula las
dos subexpresiones mediante visitas recursivas, para posteriormente asignar el valor ade-
cuado en funcin del operador. Finalmente, la visita de las dos constantes no requieren el
clculo de las mismas puesto que los nodos del AST que modelan estos terminales ya po-
seen dicho valor se le pasa en su construccin.
La generacin de cdigo es lleva a cabo por el Visitor VisitorGC. Una implementacin en
C++ es:

#include "visitorgc.h"
#include <cassert>

Evaluacin de Gramticas Atribuidas
61
void VisitorGC::visitar(ExpresionUnaria *e) {
assert(e->getOperador()=='-');
// * Genero el cdigo del operando
e->getOperando()->aceptar(this);
e->codigo<<e->getOperando()->codigo.str();
// * Genero el cdigo de negacin
e->codigo<<"\t"<<e->tipo<<"NEG\n"; // * Instruccin INEG o FNEG
}

void VisitorGC::visitar(ExpresionBinaria *e) {
// * Cdigo para apilar el primer operando
e->getOperando1()->aceptar(this);
e->codigo<<e->getOperando1()->codigo.str();
// * Es necesario convertir el op1 de entero a real?
if (e->getOperando1()->tipo=='I' && e->tipo=='F' )
e->codigo<<"\tITOF\n"; // * Integer to Float
// * Cdigo para apilar el segundo operando
e->getOperando2()->aceptar(this);
e->codigo<<e->getOperando2()->codigo.str();
// * Es necesario convertir el op2 de entero a real?
if (e->getOperando2()->tipo=='I' && e->tipo=='F' )
e->codigo<<"\tITOF\n"; // * Integer to Float
// * Tipo del operador
e->codigo<<'\t'<<e->tipo; // I o F
// * Operador
switch (e->getOperador()){
case '+': e->codigo<<"ADD\n"; break;
case '-': e->codigo<<"SUB\n"; break;
case '*': e->codigo<<"MUL\n"; break;
case '/': e->codigo<<"DIV\n"; break;
case '=': e->codigo<<"STORE\n"; break;
default: assert(0);
}
if (e->getOperando1()->tipo=='I' && e->getOperando2()->tipo=='I')
e->valor=(int)e->valor;
}

void VisitorGC::visitar(ConstanteEntera *c) {
// * Apila un entero
c->codigo<<"\tPUSHI\t"<<(int)(c->valor)<<"\n";
}

void VisitorGC::visitar(ConstanteReal *c) {
// * Apila un real
c->codigo<<"\tPUSHF\t"<<c->valor<<"\n";
}
Como vimos en la estructura del AST, cada nodo tiene un buffer de memoria (de tipo
ostringstream) con su cdigo destino de pila asociado. Podra haberse hecho con un
nico buffer pasado como parmetro en cada visita, necesitando as menos memoria. Sin
embargo, la utilizacin de un atributo con el cdigo generado para cada no terminal, es un
mecanismo tpico cuando se utilizan gramticas atribuidas en la generacin de cdigo [Lou-
den97].
La visita de una constante implica su generacin de cdigo. La traduccin es utilizar la ins-
truccin de bajo nivel PUSH anteponiendo el tipo de la constante (I o F) y el valor a apilar.
Ntese cmo se emplea el atributo tipo del nodo para anteponer el tipo a la sentencia.
Esto es posible porque previamente el Visitor de anlisis semntico infiri el tipo de cada
subexpresin.
Para las expresiones unarias, se genera el cdigo de su operando (que apilar el valor de la
subexpresin) y posteriormente se genera una instruccin NEG con su tipo adecuado. En
las expresiones binarias se genera el cdigo de las dos subexpresiones y despus el de la
operacin. Posteriormente a la generacin de cada subexpresin, como el anlisis semnti-
co calcul previamente el tipo de ambas subexpresiones, se generan instrucciones de coer-
cin (promocin) a bajo nivel. La instruccin ITOF convierte el valor entero del tope de la
Anlisis Semntico en Procesadores de Lenguaje
62
pila a un valor real; sta es generada cuando el operando es entero y la expresin binaria
real.
Podemos ver el cdigo principal, dejando el anlisis sintctico y la construccin del AST a
la herramienta yacc/bison:

#include "visitorsemantico.h"
#include "visitorgc.h"
#include "visitorcalculo.h"
#include "visitormostrar.h"
#include <iostream>
using namespace std;

int main() {
yyparse();
VisitorSemantico semantico;
ast->aceptar(&semantico);
if (ast->tipo=='E')
cerr<<"El programa no es semnticamente vlido."<<endl;
else {
cout<<"\nAnlisis semntico finalizado correctamente.\n";
VisitorCalculo calculo;
ast->aceptar(&calculo);
cout<<"\nValor de la expresin: "<<ast->valor<<endl;
VisitorGC gc;
ast->aceptar(&gc);
cout<<"\nCdigo generado:\n"<<ast->codigo.str()<<endl;
}
VisitorMostrar traza(cout);
cout<<"\nAST generado:\n";
ast->aceptar(&traza);
cout<<endl;
delete ast;
}
Dada la siguiente entrada: 3*(2+-1)/3.2
El procesador implementado generar la siguiente salida:

Anlisis semntico finalizado correctamente.

Valor de la expresin: 0.9375

Cdigo generado:
PUSHI 3
PUSHI 2
PUSHI 1
INEG
IADD
IMUL
ITOF
PUSHF 3.2
FDIV

AST generado:
( / ( * 3 ( + 2 ( - 1 ) ) ) 3.2 )
Para consultar la implementacin completa del AST, los cuatro Visitor implementados y el
resto del procesador consltese el apndice A.

Un ejemplo ms amplio de una implementacin completa de un procesador de len-
guaje en Java, empleando distintos recorridos del AST mediante el patrn de diseo Visitor,
puede consultarse en [Watt00].
Evaluacin de Gramticas Atribuidas
63
Evaluacin de gramticas S-atribuidas
Como en el caso de la evaluacin de atributos en una sola pasada ( 5.2), este tipo
de gramtica posibilita su evaluacin mediante una nica visita de cada uno de los nodos
del rbol, con el consecuente beneficio de eficiencia.
En el caso de las gramticas S-atribuidas, la evaluacin de la gramtica se obtiene
mediante un recorrido del rbol en profundidad postorden. Si se emplea el patrn de dise-
o Visitor para recorrer un rbol sintctico a partir de una gramtica S-atribuida, su recorri-
do postorden en profundidad implicar la visita de todos los hijos de cada nodo, para pos-
teriormente computar su regla semntica asociada. Dependiendo de la fase en la que nos
encontremos, las visitas de los nodos producirn una determinada accin: identificacin de
smbolos, inferencia y comprobacin de tipos, generacin y optimizacin de cdigo, o in-
cluso interpretacin.
Ejemplo 30. En el Ejemplo 29, la pasada de clculo del valor de la expresin representada por el
AST supone una gramtica S-atribuida. La gramtica abstracta (sobre el AST) es:

(1) Expresion
Binaria:
Expresion
1


Expresion
2
Operador Expresion
3

(2) Expresion
Unaria:
Expresion
1


Operador Expresion
2

(3) Constante
Entera:
Expresion

CTE_ENTERA
(4) Constante
Real:
Expresion

CTE_REAL
La notacin empleada para representar la gramtica del AST es la identificada por Michael
L. Scott [Scott00]. La sintaxis A:B en la parte izquierda de una produccin de una gramti-
ca abstracta significa que A es un nodo del AST que se crea al reconocer la produccin aso-
ciada al no terminal B. Adems, A es un tipo de B y, por tanto, A puede aparecer siempre
que B aparezca en la parte derecha de una produccin. De este modo, la relacin entre A y
B puede modelarse en orientacin a objetos como una relacin de generalizacin (heren-
cia): A es una clase derivada (ms especfica) que B (clase abstracta del AST).
Para la gramtica abstracta anterior, la fase de clculo de la expresin se puede representar
con las siguientes reglas semnticas:

(1) switch(opearador) {
case +: Expresion
1
.valor = Expresion
2
.valor +
Expresion
3
.valor; break;
case -: Expresion
1
.valor = Expresion
2
.valor -
Expresion
3
.valor; break;
case *: Expresion
1
.valor = Expresion
2
.valor *
Expresion
3
.valor; break;
case /: Expresion
1
.valor = Expresion
2
.valor /
Expresion
3
.valor; break;
case =: Expresion
1
.valor = Expresion
3
.valor; break;
}
(2) Expresion
1
.valor = - Expresion
2
.valor;
(3) Expresion.valor = CTE_ENTERA.valor
(4) Expresion.valor = CTE_REAL.valor
Ntese cmo la gramtica es S-atribuida puesto que todos los atributos son sintetizados. La
evaluacin del rbol puede hacerse mediante un recorrido en profundidad postorden, eva-
luando el atributo valor de los nodos hijos y posteriormente calculando el valor de la sub-
expresin padre.

Anlisis Semntico en Procesadores de Lenguaje
64
En los dos ejemplos anteriores se puede ver la traduccin llevada a cabo entre una
gramtica atribuida (o definicin dirigida por sintaxis) sobre una sintaxis abstracta de un
lenguaje, y su representacin mediante un AST. Los nodos concretos y abstractos de cada
nodo, as como las relaciones de herencia, se obtienen a partir de los no terminales de la
gramtica y las etiquetas aadidas a cada produccin. Los atributos de cada smbolo grama-
tical se traducen a atributos de cada una de las clases que modelan el rbol vase el
Ejemplo 29 y Ejemplo 30.
Adicionalmente a la traduccin previa, vlida para cualquier tipo de gramtica, una
gramtica S-atribuida indicar un modo de codificar el mtodo visitar asociado a cada
uno de los nodos, pudindose as realizar una traduccin al patrn Visitor. Cada mtodo
visitar representa la ejecucin de la regla semntica asociada a la produccin, cuya parte
izquierda es el nodo pasado como parmetro. Puesto que, para calcular (sintetizar) los atri-
butos del nodo que se est visitando es necesario calcular previamente los atributos (sinteti-
zados) de sus hijos, se invocar inicialmente a los mtodos aceptar (y por tanto al mto-
do visitar) para todos sus hijos y, posteriormente, se ejecutar la regla semntica asocia-
da a la produccin. Siguiendo este esquema de traduccin de reglas semnticas a mtodos
de visita, una gramtica atribuida podr ser evaluada mediante un recorrido en profundidad
postorden de un rbol sintctico.
Evaluacin de gramticas L-atribuidas
Las gramticas L-atribuidas, al igual que las S-atribuidas, pueden evaluarse en una
nica visita de todos sus nodos, con un criterio de recorrido establecido a priori. Tras
haberse creado una estructura de rbol sintctico a partir de una gramtica concreta o abs-
tracta, el problema que nos queda por abordar es cmo codificar los mtodos de visita, es
decir, en qu orden se ha de evaluar la gramtica.
Recordemos que las gramticas S-atribuidas son un subconjunto de las L-atribuidas.
El modo de calcular los atributos sintetizados de ambas gramticas fue analizado en el pun-
to anterior. Para estos atributos debemos seguir el esquema de evaluacin mediante un
recorrido postorden de rbol. El estudio que ahora nos atae es relativo a los atributos
heredados de la gramtica. Aqu, si recordamos la definicin de gramtica L-atribuida ( 4),
comentbamos cmo los atributos heredados podan calcularse en funcin de:
Los atributos heredados del nodo padre. En este caso, el atributo del nodo pa-
dre (nodo actual, sobre el que se est ejecutando el mtodo visitar) debe uti-
lizarse para calcular un atributo heredado, antes de invocar al mtodo aceptar
con el nodo poseedor de dicho atributo. Por tanto, este recorrido en profundi-
dad emplea una evaluacin preorden.
Los atributos de la parte derecha, situados a la izquierda del smbolo gramatical.
Para poder asignar a un nodo A un atributo de un nodo B, situado a su izquier-
da, se visitar B (pasndole el mensaje aceptar) y posteriormente se evaluar
el atributo de A. ste es un recorrido inorden.
Vemos, pues, cmo el recorrido de una gramtica L-atribuida se lleva a cabo con
una nica visita de cada nodo mediante un recorrido en profundidad. Se evaluarn los atri-
butos sintetizados con un recorrido postorden, y los heredados combinando inorden y pre-
orden, en funcin de sus dependencias con otros atributos.
Ejemplo 31: Dada la sintaxis dirigida por sintaxis del Ejemplo 12:

declaracion

tipo
variables ;
variables.tipo = tipo.tipo
Evaluacin de Gramticas Atribuidas
65
tipo

int tipo.tipo = I
| float tipo.tipo = F
variables
1


id ,
variables
2

ts.insertar(id.valor,variables.tipo)
variables
2
.tipo=variables
1
.tipo
| id ts.insertar(id.valor,variables.tipo)
El mtodo visitar asociado al nodo declaracin de su rbol sintctico ser:

void Visitor::visitar(Declaracion *e) {
// * Se visita el no terminal tipo
e->getTipo()->aceptar(this);
// * Reglas semntica de la primera produccin:
// variables.tipo = tipo. tipo
e->getVariables()->tipo=e->getTipo()->tipo;
// * Se visita el no terminal variables
e->getVariables()->aceptar(this);
}
Se ve cmo la ejecucin de la regla (asignacin de un atributo heredado a partir de un atri-
buto de un nodo hermano a su izquierda) es evaluada mediante un recorrido inorden. El
otro clculo del atributo heredado tipo asociado al no terminal Variables se codifica
con el siguiente mtodo de visita:

void Visitor::visitar(Variables *e) {
// * Se ejecuta la regla que depende del heredado del padre
ts.insertar(e->getID(),e->tipo);
if (e->getVariables()) {
// * Reglas semntica que depende de un hermano de la izquierda
e->getVariables()->tipo=e->tipo;
// * Se visita al no terminal variables de la parte derecha
e->getVariables()->aceptar(this);
}
}
En este caso, el recorrido es preorden: primero se ejecuta la insercin en la tabla de smbo-
los y la asignacin de tipos, para posteriormente llevarse a cabo la visita. Aunque no se
muestre, la evaluacin del atributo tipo para segunda y tercera produccin se realiza post-
orden ya que tipo es un atributo sintetizado.

Otras evaluaciones con una nica visita
En la evaluacin de gramticas con una nica pasada, indicbamos cmo un incon-
veniente era que, al realizarse todas las fases en la misma pasada, la obtencin de compo-
nentes lxicos (tokens) segua siempre una ordenacin de izquierda a derecha respecto al
archivo fuente ( 5.2). sta no es una restriccin en el caso de realizar un procesador de
lenguaje en varias pasadas, puesto que el rbol sintctico ya posee en su estructura todos los
componentes lxicos, pudiendo acceder a stos sin necesidad de seguir un orden preesta-
blecido.
Por la diferencia existente entre los compiladores de una y varias pasadas, descrita
en el prrafo anterior, es posible que una gramtica, sin ser ni S ni L-atribuida, pueda ser
evaluada con una nica visita de sus nodos ante cualquier programa de entrada. Adems, el
orden de evaluacin podr ser descrito de un modo esttico siguiendo un mtodo basado
en reglas definido en 5.1. La principal consideracin para evaluar los atributos durante el
recorrido del rbol es que los atributos heredados en un nodo se calculen antes de que el
nodo sea visitado, y que los atributos sintetizados se calculen antes de abandonar el nodo
[Aho90].
Anlisis Semntico en Procesadores de Lenguaje
66
Un primer ejemplo de este tipo de gramticas es aqul que, sin ser L-atribuida,
cumple que la evaluacin de todos sus atributos heredados puede llevarse a cabo con un
orden predeterminado, mediante una nica visita de los no terminales de la parte derecha.
Ejemplo 32. Sea la siguiente gramtica atribuida:

(1) A

L M L.h = 1
M.h = h(L.s)
A.s = i(M.s)
(2) | Q R R.h = 2
Q.h = k(R.s)
A.s = l(Q.s)
(3) L

TOKEN_L L.s = f
1
(TOKEN_L.s,L.h)
(4) M

TOKEN_M M.s = f
2
(TOKEN_M.s,M.h)
(5) Q

TOKEN_Q Q.s = f
3
(TOKEN_Q.s,R.h)
(6) R

TOKEN_R R.s = f
4
(TOKEN_R.s,Q.h)
Se puede demostrar que no es ni S ni L-atribuida y que s es una gramtica completa y bien
definida. Adicionalmente, se puede evaluar su rbol sintctico con una nica visita de cada
uno de sus nodos, ante cualquier programa de entrada.
La primera produccin posee un atributo sintetizado (A.s) y los heredados (M.h y L.h)
dependen de los smbolos de su izquierda o de los heredados de su nodo padre. As, el or-
den de visita nica es: asignacin de L.h, visita de L, asignacin de M.h, visita de M y asig-
nacin de A.s.
En la segunda produccin, la condicin de gramtica L-atribuida no se satisface; el orden
anterior de evaluacin no es vlido. Sin embargo, se aprecia cmo los atributos heredados
tienen una dependencia de sus hermanos por la derecha, pudindose establecer el siguiente
ordenamiento de evaluacin con una nica visita de cada nodo: asignacin de R.h, visita de
R, asignacin de Q.h, visita de Q y asignacin de A.s.
El resto de producciones se puede evaluar con un recorrido postorden.

Otro tipo de gramticas atribuidas que pueden evaluarse con una nica visita de un
nodo de su rbol sintctico es aqul cuyos atributos heredados no dependan de ningn
atributo sintetizado, tan solo de otros heredados [Louden97]. As, cada mtodo visitar
asociado a un nodo del rbol tendr la siguiente forma:

void Visitor::visitar(Nodo *n) {
for (cada nodo hijo de n) {
asignar los atributos heredados del hijo;
pasar el mensaje aceptar(this) al hijo;
}
asignar los atributos sintetizados de n;
}
Evaluacin de gramticas atribuidas bien definidas
En este punto analizaremos la evaluacin de gramticas atribuidas bien definidas
(no circulares) que no estn dentro de las clasificaciones previas, es decir, que requieran
necesariamente ms de una visita a alguno de los nodos del rbol. Si bien existen herra-
mientas y algoritmos de anlisis y evaluacin que procesan este tipo de gramticas, en el
caso pragmtico de desarrollo de procesadores de lenguajes se suelen evitar por la inefi-
ciencia y complejidad que implican [Scott00].
En la prctica, si en una fase de un compilador es necesario describir una gramtica
que requiera varias pasadas, se suele descomponer sta en distintas gramticas atribuidas
Evaluacin de Gramticas Atribuidas
67
que decoran el rbol, evaluando secuencialmente una tras otra. Un caso tpico es el separar,
dentro de la fase de anlisis semntico, la gramtica que lleva a cabo el proceso de identifi-
cacin de la que infiere y comprueba los tipos del lenguaje [Watt00]. Con la primera gram-
tica se decora el rbol con los smbolos y tipos de todos los identificadores declarados en el
programa (clases, mtodos, atributos, objetos...). En la segunda visita se decoran las distin-
tas construcciones del lenguaje ante el programa de entrada con el tipo inferido, para pro-
cesar todas las comprobaciones de tipos necesarias.
Ejemplo 33. Supngase la siguiente gramtica libre de contexto:

(1) S

expresion
(2) expresion
1


expresion
2
/ expresion
3

(3) expresion

CTE_ENTERA
(4) expresion

CTE_REAL
Se quiere ampliar con una gramtica atribuida para que el no terminal inicial (S) posea un
atributo S.valor de tipo real que posea el valor de la expresin. La expresin puede ser
entera o real
47
. En el caso de que la expresin sea real, todas las divisiones de la misma de-
ben ser reales (ninguna ser entera, aunque ambos operandos sean enteros). Como ejemplo,
la sentencia 5/2/2.0 tendr deber evaluarse con valor 1.25
48
. En el caso de que la ex-
presin sea entera, todas sus divisiones se realizarn despreciando la parte decimal del re-
sultado. A modo de ejemplo, 5/2/2 se evaluar con el valor de 1.
Del problema anterior se deduce que habr que calcular primero el tipo de la expresin y
despus llevar a cabo su procesamiento. El atributo que se asignar a toda subexpresin
para conocer su tipo ser tiposub, con los posibles valores entero y real. Una vez se
conozca el atributo de todas las subexpresiones de una expresin, se podr conocer el tipo
global de la expresin: este valor se guardar en el atributo tipoexp.
Empleando los atributos definidos, la siguiente gramtica atribuida resuelve el problema:

(1) expresion.tipoexp = expresion.tiposub
S.valor = expresion.valor
(2) if (expresion
2
.tiposub==real || expresion
3
.tiposub==real)
expresion
1
.tiposub = real
else expresion
1
.tiposub = entero
expresion
2
.tipoexp = expresion
1
.tipoexp
expresion
3
.tipoexp = expresion
1
.tipoexp
if (expresion
1
.tipoexp==entero)
expresion
1
.valor = (int)(expresion
2
.valor) /
(int)(expresion
3
.valor)
else expresion
1
.valor = expresion
2
.valor/expresion
3
.valor
(3) expresion.tiposub = entero
expresion.valor = CTE_ENTERA.valor
(4) expresion.tiposub = real
expresion.valor = CTE_REAL.valor
Esta gramtica no es ni S ni L-atribuida, pero si es completa y est bien definida. Posee
atributos heredados y sintetizados. El atributo expresion.tipoexp es heredado y
expresion.tiposub es sintetizado. El primero depende del segundo (primera produc-
cin), as que es necesario sintetizar expresion.tiposub previamente al clculo de
expresion.tipoexp. Del mismo modo, expresion.valor es un atributo sintetizado
dependiente del heredado expresion.tipoexp segunda produccin.

47
Si la expresin es entera, el atributo S.valor tendr el valor entero de la expresin.
48
Ntese que en los lenguajes como C o Java, la evaluacin de dicha expresin es 1.0 en lugar de 1.25.
Anlisis Semntico en Procesadores de Lenguaje
68
En funcin de la dependencia de los atributos, ser imposible evaluar la gramtica con una
nica visita de los nodos del rbol. Una evaluacin viable ser llevar a cabo una visita en
recorrido postorden para evaluar el atributo sintetizado expresion.tiposub. Mediante
una segunda visita del rbol, se podr ir calculando el valor expresion.tipoexp con un
recorrido preorden; al mismo tiempo, pero con evaluacin postorden, se podr evaluar el
atributo sintetizado valor.
Mediante la separacin del objetivo final de una fase de un procesador de lenguaje en sub-
objetivos, se puede recorrer el rbol con una nica visita de cada nodo para cada uno de los
subobjetivos empleando para ello, por ejemplo, el patrn de diseo Visitor ( 5.3). Ser el
desarrollador del compilador el encargado de dividir en varias pasadas las distintas fases del
desarrollo del procesador, en funcin de los requisitos del lenguaje.

El enfoque ms prctico de evaluacin de gramticas atribuidas que requieren ms
de una visita de los nodos del rbol es el presentado anteriormente. Sin embargo, pueden
emplearse otros algoritmos de evaluacin o herramientas como FNC-2 [FNC2], Ox [Bis-
choff92], Elegant [Jansen93] o lrc [LRC] amn de obtener una menor eficiencia.
Existe un algoritmo empleado para evaluar en mltiples pasadas una gramtica atri-
buida no circular denominado algoritmo de evaluacin bajo demanda
49
[Engelfriet84].
ste puede aplicarse como una modificacin del patrn de diseo Visitor [GOF02]. Consis-
te en reemplazar cada atributo de los nodos del rbol con un mtodo que ejecute la parte
de la regla semntica asociada a su evaluacin, devolviendo su valor. Para evaluar un atribu-
to, su mtodo asociado deber ser invocado y ste, a su vez, llamar a todos los mtodos
asociados a los atributos de los que dependa. El algoritmo recursivo finaliza si la gramtica
atribuida no es circular.
La tcnica de evaluacin bajo demanda no lleva a cabo una ordenacin topolgica
del grafo de dependencias ( 5.1), sino que hace uso de que las rutinas semnticas definen
dicho grafo de forma implcita. El mayor inconveniente de este algoritmo es su baja efi-
ciencia, ya que la misma regla semntica es ejecutada en mltiples ocasiones. En el peor de
los casos, la complejidad computacional es exponencial en relacin con el nmero de atri-
butos de la gramtica [Engelfriet84].
5.4. Rutinas Semnticas y Esquemas de Traduccin
Al igual que existen herramientas que construyen analizadores sintcticos a partir de
gramticas libres de contexto, tambin existen herramientas automticas que generan eva-
luadores de gramticas atribuidas o, en la mayor parte de los casos, definiciones dirigidas
por sintaxis. En muchas ocasiones, las herramientas de desarrollo de procesadores de len-
guaje ofrecen la posibilidad de especificar, de un modo imperativo en lugar de declarativo,
las reglas semnticas de las definiciones dirigidas por sintaxis (gramticas atribuidas). Esta
notacin es la que se conoce como esquema de traduccin (dirigida por sintaxis): una
gramtica libre de contexto en la que se asocian atributos con los smbolos gramaticales y se
insertan rutinas semnticas dentro de las partes derecha de las producciones [Aho90]. Las
rutinas semnticas son, a su vez, fragmentos de cdigo que el desarrollador del compila-
dor escribe normalmente entre llaves {} dejando explcito el momento en el que la
herramienta ha de ejecutar la misma, durante su proceso de anlisis.
La principal diferencia entre las herramientas que emplean gramticas atribuidas y
aqullas que ofrecen esquemas de traduccin es que en las segundas el desarrollador especi-

49
Demand-driven algorithm.
Evaluacin de Gramticas Atribuidas
69
fica el momento en el que se ha de ejecutar el cdigo. Sin embargo, en las gramticas atri-
buidas y definiciones dirigidas por sintaxis, el proceso de evaluacin de los atributos debe
ser resuelto por la propia herramienta. La evaluacin de una gramtica atribuida, conlleva
procesos como la creacin y ordenamiento topolgico de un grafo de dependencias, o la
limitacin a priori de las caractersticas de la gramtica vase 1.
La mayora de las herramientas que generan analizadores sintcticos ofrecen la po-
sibilidad de aadir rutinas semnticas, definiendo as un esquema de traduccin. Herra-
mientas como yacc/bison [Johnson75], ANTLR [ANTLR] o JavaCC [JavaCC] permiten
entremezclar rutinas semnticas con las producciones de las gramticas libres de contexto.
Puesto que los esquemas de traduccin ejecutan las rutinas semnticas de un modo
imperativo, el modo en el que se deriven las distintas producciones de la gramtica variar
el orden de ejecucin de las rutinas. De este modo, el diferenciar si un esquema de traduc-
cin emplea un anlisis descendente o ascendente es fundamental para la ubicacin de sus
rutinas semnticas.
En los generadores de analizadores sintcticos descendentes que incorporan es-
quemas de traduccin (por ejemplo JavaCC o ANTLR), las rutinas semnticas pueden apa-
recer en cualquier parte de la parte derecha de la produccin. Una rutina semntica al co-
mienzo de la parte derecha de una produccin ser ejecutada cuando el analizador tome la
decisin de derivar por dicha produccin. Una rutina situada en el medio de la parte dere-
cha de una produccin se ejecutar una vez haya derivado todos los smbolos de la parte
derecha, ubicados a su izquierda.
Como se aprecia en el prrafo anterior, las limitaciones de los esquemas de traduc-
cin basados en analizadores descendentes son los mismos que los identificados para la
evaluacin descendente de gramticas L-atribuidas en una nica pasada ( 5.2). Adicional-
mente a estas limitaciones, hay que aadir que la ubicacin de las rutinas semnticas, dentro
de la parte derecha de cada produccin, sea en los sitios oportunos. Las restricciones para
ubicar las rutinas son [Aho90]:
Un atributo heredado para un smbolo en el lado derecho de una produccin se
debe calcular antes que dicho smbolo.
Una rutina semntica no debe utilizar atributos sintetizados de un smbolo gra-
matical que est a la derecha de ella.
Un atributo sintetizado para el no terminal de la izquierda slo se puede calcular
posteriormente a los atributos de los que depende. La rutina semntica que cal-
cula estos atributos se suele colocar al final del lado derecho de la produccin.
Ejemplo 34. En el Ejemplo 13 se present una gramtica L-atribuida para evaluar los valores de
expresiones, mediante una gramtica descendente (LL). Al haber empleado una gramtica
atribuida, las reglas semnticas nicamente indican la produccin en a la que estn asocia-
das. Este modo declarativo de representar las reglas semnticas hace que un evaluador de
gramticas ms o menos complejo sea el encargado de calcular el orden de ejecucin de
las mismas.
En el caso de un esquema de traduccin, la ubicacin de las reglas (rutinas) semnticas,
juega un papel importante: es la persona que escribe la gramtica la encargada de indicar
cundo se va a ejecutar la rutina semntica, en funcin de la posicin seleccionada. El es-
quema de traduccin se limitar a ejecuta sta con un orden preestablecido, sin necesidad
de establecer previamente un ordenamiento topolgico o una clasificacin de la gramtica.
La gramtica atribuida del Ejemplo 13 podr ser traducida, por tanto, al siguiente esquema
de traduccin descendente:
Anlisis Semntico en Procesadores de Lenguaje
70

expresion

termino
{masTerminos.operando1 = termino.valor}
masTerminos
{expresion.valor = masTerminos.valor}
masTerminos
1


+ termino
{masTerminos
2
.operando1 =
masTerminos
1
.operando1+termino.valor}
masTerminos
2
{masTerminos
1
.valor = masTerminos
2
.valor}
| - termino
{masTerminos
2
.operando1 =
masTerminos
1
.operando1-termino.valor}
masTerminos
2

{masTerminos
1
.valor = masTerminos
2
.valor}
|

{masTerminos
1
.valor=masTerminos
1
.operando1}
termino

factor
{masFactores.operando1 = factor.valor}
masFactores
{termino.valor = masFactores.valor}
masFactores
1


* factor
{ masFactores
2
.operando1 =
masFactores
1
.operando1*factor.valor}
masFactores
2

{ masFactores
1
.valor = masFactores
2
.valor
}
| / factor
{ masFactores
2
.operando1 =
masFactores
1
.operando1/factor.valor}
masFactores
2

{masFactores
1
.valor = masFactores
2
.valor}
|

{masFactores
1
.valor=masFactores
1
.operando1}
factor

CTE_ENTERA
{factor.valor = CTE_ENTERA.valor}
La ejecucin de las reglas comienza por producir el no terminal expresion. ste produci-
r el smbolo termino, obteniendo su atributo sintetizado termino.valor y asignndo-
selo al heredado masTerminos.operando1 antes de su produccin. Tras haber calcula-
do el nico atributo heredado de masTerminos, se podr producir dicho no terminal. El
esquema general es asignar los heredados antes de producir y calcular los sintetizados en las
rutinas ubicadas en una produccin.
El momento en el que el esquema de traduccin ejecuta una rutina semntica es exacta-
mente el mismo en el que se derivara por un no terminal que estuviese en lugar de la ruti-
na.

En el caso de los analizadores ascendentes, no es posible ubicar rutinas semnticas
en cualquier zona de la parte derecha de una produccin, puesto que el reconocedor no
sabe en todo momento en que produccin se halla reduce nicamente cuando comprueba
que el tope de la pila es igual a la parte derecha de una produccin. Si se trabaja con gram-
ticas S-atribuidas, los esquemas de traduccin asociados debern poseer sus rutinas semn-
ticas al final de todas las producciones. stas se ejecutarn cuando se reduzca su produc-
cin asociada.
Evaluacin de Gramticas Atribuidas
71
Si la herramienta ascendente que nos ofrece un esquema de traduccin permite si-
mular atributos heredados como es el caso de yacc/bison ser necesario conocer las tra-
ducciones empleadas para simular dichos atributos, tales como la ubicacin de rutinas se-
mnticas en el medio de una produccin, o el acceso a cualquier elemento de la pila (vistos
en el punto 5.2, dentro del epgrafe Evaluacin ascendente de atributos heredados).
Otra caracterstica aadida a los esquemas de traduccin ms avanzados es la posi-
bilidad de procesar tanto gramticas de anlisis sintctico (gramticas concretas), como
gramticas del resto de fases de un procesador de lenguaje (gramticas abstractas). Son ca-
paces de validar la estructura sintctica, no slo de componentes lxicos (tokens), sino tam-
bin de nodos de un AST. Empleando los mismos esquemas de traduccin se puede im-
plementar cualquier fase de un compilador. Estos tipos de esquemas de traduccin, en los
que los elementos terminales de su gramtica son nodos de un AST en lugar de tokens, se
denominan tree walkers implementados por ANTLR y JavaCC, entre otros.
Ejemplo 35. El siguiente esquema de traduccin, empleando la sintaxis de AST de la herramienta
ANTLR [ANTLR], reconoce rboles de expresiones sencillas de un lenguaje de programa-
cin:

expresion returns [int r=0] {int op1,op2;}
: #(MAS op1=expresion op2=expresion) { r=op1+op2; }
| #(MENOS op1=expresion op2=expresion) {r=op1-op2;}
| #(POR op1=expresion op2=expresion) {r=op1*op2;}
| #(ENTRE op1=expresion op2=expresion) {r=op1/op2;}
| #(MODULO op1=expresion op2=expresion) {r=op1%op2;}
| cte:CTE_ENTERA {r=Integer.parseInt(cte.getText());}
;
Para comprender el ejemplo sin conocer la herramienta, se han indicado los elementos de la
gramtica en negrita. La sintaxis (#Padre Hijo1 Hijo2 Hijo3) representa un nodo
en el que Padre es el nodo raz y sus descendientes son Hijo1, Hijo2 e Hijo3. As, el
esquema de traduccin previo reconoce el AST de una expresin, al mismo tiempo que
evala su valor ste es el objetivo del cdigo Java incluido como rutinas semnticas.


73
6 Comprobacin de Tipos
En el punto 2.1 de este libro mostrbamos un conjunto de comprobaciones que
el analizador semntico de un procesador de lenguaje debera llevar a cabo. El anlisis se-
mntico acepta programas cuya estructura es vlida. El analizador semntico, mediante la
comprobacin de cumplimiento de las reglas semnticas del lenguaje, reconoce si las es-
tructuras aceptadas por el analizado sintctico pertenecen al lenguaje. De este modo, el
analizador semntico realiza todas las comprobaciones necesarias no llevadas a cabo por el
analizador sintctico, asegurndose de que un programa de entrada pertenezca al lenguaje.
Si bien hemos mostrado un conjunto variado de ejemplos de comprobacin semn-
tica llevada a cabo por esta fase del procesador de lenguaje ( 2.1), la tarea ms amplia y
compleja de esta fase es la encomendada al comprobador de tipos
50
: es la parte del anli-
sis semntico encargada de asegurar que el tipo de toda construccin del lenguaje coincida
con el previsto en su contexto, dentro de las reglas semnticas del lenguaje. Si dichas reglas
no se cumplen, se produce un conflicto u error de tipo
51
.
Ejemplo 36. La siguiente sentencia en el lenguaje ANSI C:

f(*v[i+j]);
Es sintcticamente correcta, pero el comprobador de tipos de la fase de anlisis semntico
deber comprobar un conjunto de restricciones que se han de cumplir en el contexto de la
expresin:
Las variables i y j han de estar declaradas, tener definas en su tipo la operacin suma,
y calcular (inferir) el tipo de dicha operacin.
Respecto al tipo calculado, deber ser entero o convertible implcitamente (promocio-
nable) a entero, puesto que en C los ndices de un array han de ser enteros.
La variable v deber estar declarada y poseer como tipo un array o puntero (en C el
operador [] se puede aplicar a punteros) de punteros a otro tipo T. De este modo, se
podrn aplicar los dos operadores [] y * a dicho identificador.
Finalmente, la funcin (o procedimiento) f tendr que haber sido declarada previamen-
te y poseer un nico parmetro de tipo T o promocionable a un tipo T.
Todas estas tareas debern ser llevadas a cabo por una parte del analizador semntico: el
comprobador de tipos.

Tambin es comn que los compiladores necesiten el resultado de la inferencia o
clculo de tipos llevada a cabo por el analizador semntico, para la fase de generacin de
cdigo. A modo de ejemplo, el operador + del lenguaje de programacin Java, genera dis-
tinto cdigo en el caso de que los dos operandos sean nmeros enteros, nmero reales o,
incluso, cadenas de caracteres. En Java, el operador + posee una semntica de suma para

50
Type checker.
51
Type clash.
Anlisis Semntico en Procesadores de Lenguaje
74
los nmeros enteros y reales, y una semntica de concatenacin en el caso de cadenas de
caracteres; para cada uno de los tres casos, el generador de cdigo producir distintas ins-
trucciones.
La comprobacin de tipos puede llevarse a cabo en tiempo de compilacin (estti-
ca), en tiempo de ejecucin (dinmica) o en ambos casos. Cada implementacin de un
comprobador de tipos (en tiempo de compilacin o ejecucin) tiene en cuenta un conjunto
elevado de reglas semnticas asociadas a los tipos del lenguaje, haciendo variar de un modo
significativo esta parte del anlisis de un lenguaje a otro. De este modo, habr que tener en
cuenta conceptos como sistemas, expresiones y equivalencia de tipos, polimorfismo, sobre-
carga, coercin y conversin de tipos. Todos estos conceptos, as como mtodos para dise-
ar e implementar comprobadores de tipos, sern los abordados en este punto.
6.1. Beneficios del Empleo de Tipos
Incluso antes introducir las distintas definiciones de tipo de un lenguaje de progra-
macin, ser interesante preguntarnos por qu existen los tipos y qu tiene de positivo su
empleo [Pierce02]:
Fiabilidad. La comprobacin esttica de tipos reduce el nmero de errores que
un programa puede generar en tiempo de ejecucin. ste es el beneficio ms
obvio ya que, gracias a la deteccin temprana de los errores, el programador
podr reparar stos de un modo casi inmediato y no cuando se est ejecutando
la aplicacin, pudiendo incluso haber sido implantada.
Abstraccin: Otra ventaja de emplear tipos en los lenguajes de programacin
es que su uso fuerza al programador a dividir el problema en diversos tipos de
mdulos, de un modo disciplinado. Los tipos identifican la interfaz de los m-
dulos (funciones, clases, paquetes o componentes) proporcionando una simpli-
ficacin de los servicios que ofrece cada mdulo; un tipo de contrato parcial en-
tre los desarrolladores del mdulo y sus clientes.
El estructurar sistemas complejos en distintos mdulos con interfaces claras
hace que los diseos puedan poseer una mayor abstraccin, de modo que las in-
terfaces puedan ser diseados y debatidos de un modo independiente a su pos-
terior implementacin.
Legibilidad: Un tipo de una entidad (variable, objeto o funcin) transmite in-
formacin acerca de lo que se intenta hacer con ella, constituyendo as un modo
de documentacin del cdigo.
Eficiencia: Como hemos comentado en la propiedad anterior, una entidad de
un programa declarada con un tipo especfico indica informacin relativa a lo
que se intenta hacer con ella. De este modo, al conocer el tipo de las construc-
ciones del lenguaje, se podr generar cdigo de carcter ms especfico y efi-
ciente que si no tuvisemos esa informacin. En el caso de no poseer tipos en
tiempo de compilacin, debera descubrirse la ejecucin especfica dinmica-
mente con la consecuente prdida de eficiencia.
Ejemplo 37. En el lenguaje de programacin Smalltalk [Goldberg83], el paso de un mensaje a un
objeto desencadena la bsqueda, en tiempo de ejecucin, de un mtodo con el mismo
nombre en el rbol de herencia. Sin embargo, en el caso de C++ la invocacin a un mto-
do no virtual es resuelta por el compilador en tiempo de compilacin
52
, pudiendo ejecutarla

52
Si el mtodo es virtual, es necesario una indireccin mediante una tabla de mtodos virtuales.
Comprobacin de Tipos
75
dinmicamente en un tiempo constante, muy inferior al necesitado por Smalltalk. Posterio-
res optimizaciones del lenguaje Smalltalk-80 emplearon sistemas de tipos para mejorar su
rendimiento en tiempo de ejecucin [Atkinson86].

6.2. Definicin de Tipo
Existen tres modos de definir el concepto de tipo, en funcin de distintos puntos
de vista, todos ellos compatibles entre s [Scott00]:
Denotacional: Desde este punto de vista, un tipo es un conjunto de valores.
Un valor es de un tipo determinado si pertenece al conjunto denotado por di-
cho tipo. Una variable u objeto es de un tipo dado si puede garantizarse que sus
posibles valores estn siempre dentro del conjunto descrito por su tipo.
Este punto de vista del concepto de tipo es definido por uno de los mtodos
ms comunes de especificar la semntica de un lenguaje: la semntica denota-
cional ( 1.1), en la que un conjunto de valores suele definirse como un domi-
nio. Los tipos de un lenguaje de programacin son dominios en la formalizacin
denotacional. Este concepto de tipo est enfocado a la representacin de sus
posibles valores, es decir, a su significado o valor. De este modo, la definicin
denotacional de tipo est muy ligada a la fase de generacin de cdigo, ya que en
esta fase se genera un programa de salida con igual semntica que el programa
de entrada, pero expresada en otro lenguaje de programacin [Watt00].
Basado en la abstraccin: Desde este punto de vista, un tipo es una interfaz
consistente en un conjunto de operaciones que se pueden realizar sobre ste.
Estas operaciones sern aplicables a una variable u objeto de dicho tipo, y po-
seern una semntica bien definida.
Este modo de ver los tipos est especialmente dirigido a la fase de anlisis se-
mntico de un procesador de lenguaje. Esta fase ha de comprobar, a partir del
tipo de una expresin, que las operaciones aplicadas sobre sta sean correctas.
La semntica de cada una de las operaciones, est relacionada con la fase de ge-
neracin de cdigo.
Constructivo: Un tipo es definido como un conjunto de tipos simples
53
(tam-
bin llamados predefinidos, bsicos o primitivos), o bien un tipo compuesto o
construido formado a partir de otros tipos.
Un tipo simple es un tipo atmico cuya estructura interna no puede ser modifi-
cada por el programador (integer, float, boolean...). Un tipo construido
o compuesto es un tipo construido por el programador a partir de un construc-
tor de tipos (record, array, set...) aplicado a otros tipos bsicos o cons-
truidos.
Este modo de ver los tipos posee principalmente un carcter interno al compi-
lador, indicando un modo de representarlos para implementar las distintas fases
del procesador de lenguaje. Como veremos en breve, el punto de vista construc-
tivo de los tipos es representado por las expresiones de tipo de un compilador.

53
Built-in type.
Anlisis Semntico en Procesadores de Lenguaje
76
6.3. Expresin de Tipo
Una expresin de tipo es un modo de expresar el tipo de cualquier construccin de
un lenguaje, es decir, es la forma en el que un procesador de lenguaje representa cada tipo
del lenguaje que procesa. Las expresiones de tipo se centran en la definicin constructiva de
un tipo. As, una expresin de tipo es, o bien un tipo bsico, o el resultado de aplicar un
constructor de tipos a otras expresiones de tipos.
Cada compilador representar internamente sus expresiones de tipo de un modo
distinto, haciendo uso de la expresividad que le ofrezca el lenguaje de programacin em-
pleado en su implementacin. Inicialmente nos centraremos en una representacin inde-
pendiente de la implementacin, para posteriormente mostrar una representacin basada en
un diseo orientado a objetos.
Ejemplo 38. El lenguaje de programacin Java, posee el tipo primitivo entero (int). Desde el
punto de vista denotacional, este tipo de datos denota el conjunto de nmero enteros com-
prendido entre 2.147.483.648 y 2.147.483.647 [Gosling00]. Centrndonos en el punto de
vista basado en la abstraccin, las operaciones permitidas son de comparacin, igualdad,
aritmticas, incremento, decremento, desplazamiento y operaciones a nivel de bit. Desde el
punto de vista constructivo, el tipo entero es un tipo bsico.
Una expresin de tipo de un tipo bsico es el propio tipo bsico, en nuestro caso: int.
Distintas implementaciones la expresin de tipo int pueden ser un entero, carcter, cade-
na de caracteres o incluso un objeto.
En el caso del lenguaje de programacin C, el mismo tipo posee una semntica denotacio-
nal no necesariamente igual. Este lenguaje deja el rango de posibles valores como depen-
diente de la implementacin, sin establecer sus posibles valores de un modo general. Sin
embargo, s limita el conjunto de operaciones que se pueden aplicar a este tipo; stas son
similares, pero no exactamente las mismas que las de Java. Ntese cmo en ambos casos la
expresin de tipo y su implementacin pueden ser iguales que las del lenguaje Java.

Ejemplo 39. El lenguaje de programacin Pascal posee los siguientes tipos simples: boolean,
char, integer y real. Sus expresiones de tipo pueden ser respectivamente boolean,
char, integer y real. Sus posibles valores y operaciones estn definidos en la especifi-
cacin del lenguaje [Pascal82]. La siguiente gramtica libre de contexto representa un sub-
conjunto de las posibles expresiones de Pascal.

(1) expresion

false
(2) expresion

true
(3) expresion

cte_caracter
(4) expresion

cte_entera
(5) expresion

cte_real
(6) expresion
1


expresion
2
AND expresion
3

(7) expresion
1


expresion
2
OR expresion
3

(8) expresion
1


NOT expresion
2

(9) expresion
1


( expresion
2
)
(10) expresion
1


expresion
2
+ expresion
3

(11) expresion
1


expresion
2
expresion
3

(12) expresion
1


expresion
2
* expresion
3

(13) expresion
1


expresion
2
/ expresion
3

(14) expresion
1


expresion
2
div expresion
3

(15) expresion
1


expresion
2
> expresion
3

(16) expresion
1


expresion
2
< expresion
3

Comprobacin de Tipos
77
(17) expresion
1


expresion
2
= expresion
3

A partir de la siguiente gramtica libre de contexto, debemos definir una gramtica atribui-
da que nos diga si la expresin posee errores de tipo.

P R
(1) expresion.et = boolean
(2) expresion.et = boolean
(3) expresion.et = char
(4) expresion.et = integer
(5) expresion.et = real
(6) expresion
1
.et = boolean
(7) expresion
1
.et = boolean
(8) expresion
1
.et = boolean
(9) expresion
1
.et = expresion
2
.et
(10) expresion
1
.et = MayorTipo(expresion
2
.et,expresion
3
.et)
(11) expresion
1
.et = MayorTipo(expresion
2
.et,expresion
3
.et)
(12) expresion
1
.et = MayorTipo(expresion
2
.et,expresion
3
.et)
(13) expresion
1
.et = MayorTipo(expresion
2
.et,expresion
3
.et)
(14) expresion
1
.et = integer
(15) expresion
1
.et = boolean
(16) expresion
1
.et = boolean
(17) expresion
1
.et = boolean

ExpTipo mayorTipo(ExpTipo t1,ExpTipo t2) {
if ( t1==real || t2==real ) return real;
return integer;
}
Una vez calculado el atributo sintetizado expresion.et que posee la expresin de tipo
en el caso de que la operacin sea correcta, podemos identificar los casos de error como un
conjunto de predicados:

P B
(6) expresion
2
.et==boolean && expresion
3
.et==boolean
(7) expresion
2
.et==boolean && expresion
3
.et==boolean
(8) expresion
2
.et==boolean
(10) realOentero(expresion
2
.et,expresion
3
.et)
(11) realOentero(expresion
2
.et,expresion
3
.et)
(12) realOentero(expresion
2
.et,expresion
3
.et)
(13) realOentero(expresion
2
.et,expresion
3
.et)
(14) expresion
2
.et==integer && expresion
3
.et==integer
(15) realOentero(expresion
2
.et,expresion
3
.et) ||
(expresion
2
.et==char && expresion
3
.et==char)
(16) realOentero(expresion
2
.et,expresion
3
.et) ||
(expresion
2
.et==char && expresion
3
.et==char)
(17) realOentero(expresion
2
.et,expresion
3
.et) ||
(expresion
2
.et==char && expresion
3
.et==char)

boolean realOentero(ExpTipo t1,ExpTtipo t2) {
return (t1==integer || t2==real) &&
(t1==integer || t2==real );
}

Otro modo de obtener los posibles errores es definiendo una nueva expresin de tipo,
error, que denote un error de tipo al tratar de aplicar una operacin no definida sobre un
Anlisis Semntico en Procesadores de Lenguaje
78
tipo determinado. En este caso, la expresin deber tener un tipo distinto a error para ser
semnticamente correcta.

Existen lenguajes en los que es posible crear nuevos tipos simples. Ejemplos tpicos
son los tipos enumerados y subrango de lenguajes como Pascal o Ada. Estos tipos no son
construidos, puesto que no utilizan expresiones de tipo para construir los nuevos tipos.
Ejemplo 40. En el lenguaje Pascal, un tipo subrango de valores enteros compren-
didos entre 0 y 9 puede definirse del siguiente modo:

type Digito = 0..9;
Del mismo modo, un tipo enumerado de tres enteros distintos puede definirse, en el len-
guaje C, del siguiente modo:

typedef enum { rojo, verde, azul } Color;
Tanto los valores enumerados como los subrangos son un subconjunto del tipo entero.
Expresiones de tipo de los dos ejemplos pueden ser 0..9 y [rojo,verde,azul]. N-
tese cmo los valores de los que estn compuestos las expresiones tipo no son a su vez
expresiones de tipo. Por este hecho, los tipos enumerados y subrango no son compuestos,
sino simples.

Dado un conjunto de tipos simples, los lenguajes de programacin permiten al
usuario crear nuevos tipos, empleando constructores de tipos tales como struct, array,
union o class. Estos constructores pueden ser vistos como funciones que, recibiendo
un conjunto de tipos como parmetros, devuelven un nuevo tipo compuesto, cuya estruc-
tura depende del constructor empleado. El conjunto de valores que pueden albergar las
entidades de estos tipos construidos, al igual que las operaciones que sobre ellos se pueden
aplicar, dependern del constructor y tipos empleados para componer el nuevo tipo.
Ejemplo 41. En este ejemplo mostraremos una representacin de las expresiones de tipo, para
un subconjunto de los constructores de tipo del lenguaje Pascal.
Vectores (arrays). Un vector denota una agrupacin o coleccin de elementos homogneos.
Su semntica suele representarse mediante la asociacin de un ndice (comnmente de tipo
entero) a un elemento del tipo empleado para construir el array. La operacin ms comn
ser, pues, el acceso a un elemento a partir de un ndice tpicamente representado con el
operador [] o ().
Si T es una expresin de tipo e I es una expresin de tipo subrango, entonces la expresin
de tipo array(I,T) denota el tipo vector (array) de elementos de tipo T e ndices I. As,
la siguiente declaracin:

var a: array [1..10] of integer;
hara que un compilador de Pascal asociase la expresin de tipo
array(1..10,integer) al identificador a.
Punteros. Desde el punto de vista semntico, un puntero es un modo indirecto para refe-
renciar un elemento del tipo empleado en su construccin. Por esta caracterstica son muy
empleados para definir estructuras recursivas. Algunas implementaciones hacen que un
puntero denote una direccin de memoria; en otras, simplemente albergan el identificador
nico de un objeto o variable. Desde el punto de vista basado en la abstraccin, las opera-
Comprobacin de Tipos
79
ciones ms comunes son: desreferenciar para acceder al elemento al que apuntan, asigna-
cin entre punteros, y obtencin y liberacin de memoria.
Si T es una expresin de tipo, entonces pointer(T) es una expresin de tipo que repre-
senta el tipo puntero a un elemento de tipo T. A modo de ejemplo, la siguiente declaracin
en Pascal:

var b: array [-2..2] of real;
declara la variable b con tipo array(-2..2, pointer(real))
Productos. Un producto o tupla de dos tipos denota el producto cartesiano de los distin-
tos valores que cada uno de los tipos puede tomar. Una variable de este tipo poseer una
combinacin de valores cada uno de los tipos empleados en la construccin de la tupla. La
operacin bsica aplicada a este tipo es el acceso a un valor concreto de los posedos por la
tupla.
El lenguaje ML [Milner84] posee el concepto de tupla de un modo explcito, mediante el
constructor de tipos *. Por ejemplo, la siguiente sentencia en ML declara una variable
persona como una tripleta, dentro del producto cartesiano que representa todas las posi-
bles combinaciones de los tres tipos string, string e int:

val persona = (Suarez, Ricardo, 9234194) : string * string * int
Si T
1
y T
2
son expresiones de tipo, entonces la expresin de tipo T
1
xT
2
denota el tipo
producto (cartesiano) de elementos de tipo T
1
y T
2
. As, la anterior declaracin deber aso-
ciar a persona la expresin de tipo string x string x int
Si, en ML, se desea acceder al nombre de la persona, el operador que nos ofrece dicha fun-
cionalidad es #. De este modo, la expresin #2(persona) nos devuelve Ricardo.
Aunque el lenguaje Pascal no posee explcitamente la construccin del tipo tupla (produc-
to), s puede utilizarse ste para representar elementos como la lista de parmetros de una
funcin o los tipos de un registro o unin.
Registros. Un registro es un tipo de producto (tupla) en el que los distintos elementos del
mismo (campos) poseen un identificador nico. Su denotacin es la misma que la de los
productos, pero desde el punto de vista de la abstraccin, el acceso a cada uno de los ele-
mentos se hace mediante el identificador nico del campo en lugar de emplear la posicin
del mismo. Es comn asociar el operador punto a esta operacin.
Dadas dos expresiones de tipo T
1
y T
2
, y dos identificadores de tipo N
1
y N
2
, la expre-
sin de tipo Record((N
1
x T
1
) x (N
2
x T
2
)) denota el producto cartesiano de los
dos tipos T
1
y T
2
, donde N
1
y N
2
sern los nombres empleados para identificar los dos
valores de T
1
y T
2
respectivamente. La siguiente declaracin en Pascal:

TYPE cadena = array [1..255] of char;
puntero = real;
registro = record
direccion: cadena;
importe: puntero;
end;
VAR r:registro;
asocia a la variable r la expresin de tipo:
record( (direccion x array(1..255,char)) x
(importe x pointer(real)) )
Anlisis Semntico en Procesadores de Lenguaje
80
Uniones. Determinados lenguajes, como C, poseen el concepto de unin. Otros, como
Pascal o Ada, aaden a los registros la posibilidad de tener campos variantes: campos que
hacen variar la composicin del registro, en funcin del valor que tome un campo especial
llamado selector. Tanto el concepto de unin, como el de campo variante de un registro,
denotan la unin disjunta de cada uno de sus campos se pueden dar cualquiera de ellos,
pero nicamente uno al mismo tiempo.
En el lenguaje ANSI C, la siguiente declaracin de la variable entero_o_real hace que
sta pueda tener la unin disjunta de un valor entero o real:

union miunion {
int entero;
float real;
} entero_o_real;
El lenguaje de programacin ANSI C define las operaciones vlidas a aplicar sobre una
unin como las mismas existentes para los registros [Kernighan91]: asignacin, obtencin
de direccin y acceso al miembro. Tampoco existe comprobacin adicional de acceso a un
campo incorrecto
54
. Para la fase de anlisis semntico, no sera necesario, pues, crear un
nuevo constructor de tipo para las uniones. Sin embargo, la fase de generacin de cdigo s
lo requiere, ya que las direcciones de memoria de cada campo no sern las mimas que en el
caso de los registros. Mediante un constructor de tipo union, se podra asociar a la varia-
ble entero_o_real la expresin de tipo:
union( (entero x int) x (real x float) )
Funciones. Una variable de tipo funcin denota una transformacin de elementos de un
tipo, a elementos de otro tipo. En los lenguajes funcionales, las funciones son elementos de
primer categora
55
, entendiendo que pueden ser pasados y devueltos como parmetros de
otras funciones, o aparecer incluso ubicados en cualquier estructura de datos. En determi-
nados lenguajes imperativos, esta facilidad aparece gracias a los punteros a funciones. De
este modo, es factible poseer variables de tipo (puntero a) funcin. Ser por tanto necesario
representar dicha expresin de tipo al procesar estos lenguajes. La operacin principal a
aplicar sobre una funcin es requerir la transformacin que denota, es decir, invocarla.
Si T
1
y T
2
son dos expresiones de tipo, la expresin de tipo T
1
T
2
representa el conjunto
de funciones que lleva a cabo transformaciones de un dominio T
1
a un dominio T
2
. A con-
tinuacin mostramos ejemplos de funciones en Pascal y sus expresiones de tipos:

Funciones Expresiones de Tipo
Function f(a:char):integer; char pointer(integer)
Procedure p(i:integer);
integer void
La funcin estndar mod (integer x integer) integer
Ntese cmo, sin que el lenguaje de programacin Pascal posea el tipo void como los
lenguajes basados en C se puede emplear esta expresin de tipo para representar los pro-
cedimientos, como casos especiales de funciones. Podra crearse un constructor de tipos
distinto para los procedimientos, y no sera necesaria la utilizacin de la expresin de tipo
void. Sin embargo, se perdera el tratamiento comn que se le da a funciones y procedi-
mientos, desde la fase de anlisis semntico y generacin de cdigo.

54
En otros lenguajes como Ada, s se lleva a cabo esta comprobacin dinmicamente.
55
Fist-class.
Comprobacin de Tipos
81
Otro elemento a destacar es cmo las funciones que reciben ms de un parmetro emplean
un producto o tupla de los tipos de cada uno de sus parmetros, como tipo base para llevar
a cabo la transformacin.
Clases. Las clases denotan un tipo de objetos que posee una estructura y comportamiento
comn. La mayora de los lenguajes orientados a objetos poseen el concepto de clase para
indicar un tipo de objeto. A la hora de representar este tipo por parte de un procesador de
lenguaje, es necesario tener en cuenta un conjunto de caractersticas propias de los modelos
computacionales orientados a objetos. Centrndonos en el anlisis semntico, las caracters-
ticas principales a tener en cuenta a la hora de representar las clases son:
Encapsulamiento (encapsulacin). Las clases poseen, al igual que los registros,
un conjunto de campos (atributos) que son identificados mediante un nombre.
stos representan la estructura de cada uno de los objetos. Adicionalmente po-
seen comportamiento descrito por un conjunto de mtodos o funciones miem-
bro. stas tambin son accesibles mediante un identificador nico.
Ocultacin de la informacin. Cada uno de los campos (atributos o mtodos)
puede poseer un grado de ocultacin de informacin. Ejemplos de distintos ni-
veles de ocultacin son los grados pblico, privado, protegido y package defini-
dos en el lenguaje de programacin Java. Un compilador ha de conocer qu ni-
vel de ocultacin ha sido empleado para cada miembro, para as poder compro-
bar la validez semntica de los posibles accesos.
Herencia y polimorfismo. La herencia es una relacin de generalizacin estable-
cida entre clases que denota una jerarqua de tipos: una clase que deriva de otra
ser un subtipo de la misma. De este modo, un analizador semntico deber
permitir la aparicin de un subtipo donde se requiere una expresin de un tipo
determinado. Las expresiones de tipo debern representar de algn modo las re-
laciones de generalizacin entre las clases, para poder llevar a acabo el anlisis
semntico.
Enlace dinmico. Esta faceta est relacionada con la fase de generacin de cdi-
go. El paso de un mensaje a un objeto puede desencadenar ejecuciones de dis-
tintos mtodos en funcin del tipo de ste. La resolucin del mtodo a invocar
se produce en tiempo de ejecucin, y este mecanismo se conoce con el nombre
de enlace dinmico. El generador de cdigo ha de tener en cuenta esta propie-
dad y deber generar el cdigo de paso de un mensaje como el acceso a una ta-
bla de mtodos virtuales [Louden97]. Cada objeto tendr una tabla con las di-
recciones de los mtodos a ejecutar ante la recepcin de cada mensaje. Estas di-
recciones son dinmicas y su valor indicar qu mtodo ha de ser ejecutado pa-
ra ese objeto concreto.
Existen otras caractersticas como mtodos de clase y mtodos abstractos que poseen de-
terminados lenguajes, pero no todos.

Implementacin
Hemos visto cmo las expresiones de tipo representan el tipo de las construcciones
sintcticas de un lenguaje. Hemos visto tambin una notacin ideada por Alfred Aho, para
representar las mismas [Aho90]. El caso que ahora nos atae es cmo representar las ex-
presiones de tipo de un lenguaje, a la hora de implementar un procesador del mismo.
Anlisis Semntico en Procesadores de Lenguaje
82
Para un lenguaje que nicamente posee tipos simples, podremos utilizar una repre-
sentacin mediante enteros, enumerados, caracteres o cadena de caracteres, por ejemplo.
Sin embargo, no todos estos tipos sern vlidos cuando el lenguaje a procesar posea algn
constructor de tipo. Por ejemplo, si adicionalmente a los tipos simples existe el constructor
de tipo puntero, no ser posible emplear enteros, enumerados y caracteres, puesto que,
mediante el constructor de tipos pointer, se puede construir infinitos tipos:
pointer(char), pointer( pointer(char) ), pointer( pointer( pointer
(char)))...
Empleado cadenas de caracteres para representar las expresiones de tipos no po-
seemos la restriccin previa. Se puede representar cada expresin de tipo con la notacin
descrita por Alfred Aho. No obstante, cada vez que queramos extraer un tipo que forma
parte de otro tipo compuesto, deberamos procesar la cadena de caracteres con la compleji-
dad que ello conlleva. En el caso de los punteros no sera excesivamente complejo, pero
aparecera mayor dificultad conforme surgiesen nuevos constructores de tipos. Se podra
representar de un modo ms sencillo con estructuras de datos recursivas. La representacin
de las expresiones de tipo mencionadas podra pasar a ser, en el lenguaje C, la siguiente:

typedef enum enum_tipos {
entero, caracter, logico, real, puntero
} tipo_t;

typedef struct struct_puntero {
tipo_t tipo;
struct struct_puntero *puntero;
} expresion_tipo;
Esta estructura de tipos sera ms fcil de manipular que una cadena de caracteres,
cuando existiesen ms constructores de tipos. La obtencin de los tipos empleados para
componer cada uno de los tipos construidos ser a travs del acceso a un campo del regis-
tro.
La principal limitacin de este tipo de estructura de datos es que, precisamente, slo
modela datos. En la fase de anlisis semntico y generacin de cdigo ser necesario asociar
a estas estructuras un conjunto de rutinas, tales como comprobaciones de validez semntica
o generacin de cdigo intermedio. Existirn rutinas especficas para cada expresin de
tipo, y otras comunes a todas ellas. Haciendo uso de tcnicas ofrecidas por lenguajes orien-
tados a objetos tales como encapsulamiento, herencia y polimorfismo podremos resolver
esta problemtica de un modo ms sencillo.
El problema de representar estructuras compuestas recursivamente de un modo je-
rrquico, aparece en diversos contextos dentro del campo de la computacin. El patrn de
diseo Composite [GOF02] ha sido utilizado para modelar y resolver este tipo de problemas.
Permite crear estructuras compuestas recursivamente, tratando tanto los objetos simples
como los compuestos de un modo uniforme. Podremos representar su modelo esttico
mediante el siguiente diagrama de clases:
Comprobacin de Tipos
83
Hoja
operacion1()
operacion2()
Cliente
Composite
operacion1()
operacionEspecifica1()
Componente
operacion1()
operacion2()
n

Figura 17: Diagrama de clases del patrn de diseo Composite.
Los distintos elementos del modelo son:
Componente. Clase normalmente abstracta que declara la interfaz de todos los
elementos, independientemente de que sean simples o compuestos. Puede im-
plementar en sus mtodos un comportamiento por omisin, o declararlo como
abstracto para que cada uno de los subtipos lo implementen.
Hoja. Representa cada nodo hoja de la estructura jerrquica. Un nodo hoja no
tienen ningn tipo hijo. Definir en sus mtodos las operaciones concretas
para ese nodo especfico.
Compuesto (Composite). Modela aquellos nodos que se construyen como com-
posicin de otros nodos, almacenando referencias a sus nodos hijo. Imple-
menta las operaciones en funcin de los hijos que posee, e incluso en funcin
de los resultados de cada una de las operaciones de sus hijos.
Siguiendo este patrn, cada expresin de tipo primitivo ser una clase hoja, y los ti-
pos compuestos con cada constructor de tipo sern clases Composite. Las operaciones co-
munes a todos los tipos, propias de la fase de anlisis semntico y de generacin de cdigo,
sern ubicadas en la clase componente. Si existe un comportamiento por omisin, ser im-
plementado a este nivel. Cada clase derivada redefinir la operacin general definida en el
componente, para su caso especfico u obtendr, en el caso de existir, su comportamiento
por defecto. Adicionalmente, cualquier clase derivada podr definir mtodos especficos
propios de su expresin de tipo, sin necesidad de que stos estn declarados en su clase
base.
Ejemplo 42. Mostraremos en este ejemplo cmo, empleando el patrn de diseo Composite, las
expresiones de tipo introducidas en el Ejemplo 41 pueden ser modeladas mediante un len-
guaje orientado a objetos. Posteriormente (en el Ejemplo 44) se implementar, con este
diseo de expresiones de tipo, un comprobador de tipos de un subconjunto del lenguaje
Pascal, mediante una definicin dirigida por sintaxis.
Las expresiones de tipo del lenguaje sern los tipos simples char, integer y subrango
este ltimo, slo vlido para construir arrays. Adicionalmente se va a introducir un tipo
void para los procedimientos, y error para indicar la existencia de un error de tipo. En el
caso de que una expresin posea un error de tipo, el comprobador de tipos debera dar un
mensaje al usuario y, o bien finalizar el proceso, o bien recuperarse ante el error y seguir
procesando el programa.
Respecto a los constructores de tipo, tendremos pointer, array (nicamente con sub-
rangos de nmeros enteros), (funcin) y record. El diagrama de clases que modela
todas las posibles expresiones de tipo, es el siguiente:
Anlisis Semntico en Procesadores de Lenguaje
84
Char
get Bytes()
expresionTipo()
Integer
getBytes()
expresionTipo()
Void
expresionTipo()
Error
expresionTipo()
Pointer
getBytes()
expresionTipo()
flecha()
getDe()
equivalente()
Array
desde
hasta
getBytes()
expresionTipo()
corchete()
getDe()
equivalente()
Record
getBytes()
expresionTipo()
punto()
getCampos()
equivalente()
ExpresionTipo
getBytes() : int
expresionTipo() : string
flecha() : ExpresionTipo*
corchete(t : ExpresionTipo*) : ExpresionTipo*
parentesis(p : vector<ExpresionTipo*>) : ExpresionTipo*
punto(c : string) : ExpresionTipo*
equivalente(et : ExpresionTipo*) : ExpresionTipo*
1
a
1
1
de
1
: st ri ng
campos
: st ri ng
Function
getBytes()
expresionTipo()
parentesis()
getDevolucion()
getParametros()
equivalente()
1
devolucion
1
: st ri ng
parametros
: st ri ng

Figura 18: Diagrama de clases empleado para modelar expresiones de tipo.
La clase que juega el papel de componente en el patrn Composite es la clase abstracta
ExpresionTipo. sta posee el comportamiento general de todas las expresiones de ti-
pos, alguno de ellos predefinido por omisin. Los mtodos de ejemplo definidos son:
Mtodos flecha, corchete, parentesis y punto: Estos mtodos calculan (infie-
ren) el tipo resultante tras aplicar un operador del lenguaje, a partir de una expresin de
tipo (el objeto implcito) y, en algn caso, otras expresiones de tipo pasadas como pa-
rmetro. La implementacin por omisin es devolver siempre el tipo Error, indicando
as que dicha operacin no est semnticamente definida para el tipo. Cada tipo que
implemente este operador deber redefinir el mtodo relacionado. Por ejemplo, el tipo
Pointer implementa el mtodo flecha puesto que esta operacin est permitida par
este tipo; la expresin de tipo que devuelve es el tipo al que apunta.
Mtodo expresionTipo: Devuelve una cadena de caracteres representativa de su
expresin de tipo, siguiendo la notacin descrita por Aho (Ejemplo 41). Su objetivo
principal es facilitar las tareas de depuracin.
Mtodo getBytes: Es un mero ejemplo de cmo los tipos del lenguaje poseen fun-
cionalidad propia de la fase de generacin de cdigo. Este mtodo devuelve el tamao
en bytes necesario para albergar una variable de ese tipo.
Mtodo equivalente: Indica si dos expresiones de tipo son o no equivalentes entre
s. Posee una implementacin por omisin enfocada a los tipos simples: dos tipos son
equivalentes si son instancias de la misma clase. Existen diversos modos de definir la
equivalencia entre tipos; profundizaremos en esta cuestin en 6.6.
Las clases derivadas poseen mtodos adicionales propios de su comportamiento especfico,
tales como getCampos (registro), getDevolucion y getParametros (funcin), getA
(puntero) y getDe (array).
Comprobacin de Tipos
85
Ntese cmo los tipos compuestos poseen asociaciones al tipo base: un puntero requiere
un tipo al que apunta (a); un array al tipo que colecciona (de); un registro requiere una co-
leccin a sus campos (campos), cualificada por el nombre del campo (string); una fun-
cin requiere una asociacin al tipo que devuelve (devolucion), as como una coleccin
cualificada por el nombre de cada uno de sus parmetros (parametros).
El hecho de que todas las asociaciones estn dirigidas hacia la clase base de la jerarqua,
hace que cada tipo compuesto pueda formarse con cualquier otro tipo incluyendo l mis-
mo gracias al polimorfismo.
A modo de ejemplo, la siguiente expresin de tipo:
record( (c x array(1..10,pointer(char))) x
(f x (integer,pointer(char))->pointer(integer)) x
(p x (char,integer)->void) )
generara dinmicamente el siguiente diagrama de objetos:
: Record
: Array : Function : Function
: Pointer
: Char
: Integer : Pointer
: Char
: Pointer
: Integer
: Char : Integer : Void
campos
desde=1
hasta=10
devolucion devolucion
parametros parametros
a
a a
de

Figura 19: Diagrama de objetos creado a partir de la expresin de tipos especificada.
El diagrama de objetos posee una estructura jerrquica de rbol. Esta estructura, emplean-
do el mismo diagrama de clases, podra haberse creado en memoria mediante un grafo ac-
clico dirigido (DAG), con el consecuente ahorro de memoria. La instanciacin de objetos
mediante esta estructura puede llevarse a cabo implementando una tabla de tipos que no
cree expresiones de tipo ya existentes.
Una implementacin de ejemplo es la mostrada en el apndice C.1.

6.4. Sistema de Tipos
Un sistema de tipos es un conjunto de reglas para asignar expresiones de tipos a las
distintas construcciones de un lenguaje [Aho90]. Para ello, un sistema de tipos deber defi-
nir sus expresiones de tipos, asignar stas a las distintas construcciones sintcticas del len-
guaje, y comprobar que las reglas semnticas de los tipos del lenguaje se cumplan ante cual-
quier programa de entrada. Si no fuere as, generar un error de tipo (type clash), continuan-
do el procesamiento del programa o finalizando, en funcin del tipo del mecanismo de
manejo de errores que implemente [Louden97].
Anlisis Semntico en Procesadores de Lenguaje
86
Un comprobador de tipos de un lenguaje de programacin deber implementar un
sistema de tipos. En las reglas definidas por un sistema de tipos se deber tener en cuenta
conceptos como equivalencia, compatibilidad, conversin e inferencia de tipos.
Ejemplo 43. Como ejemplo de un sistema de tipos, representaremos un subconjunto de
expresiones del lenguaje Pascal. Mediante una definicin dirigida por sintaxis, asignaremos
las expresiones de tipos del Ejemplo 41 a las distintas construcciones del lenguaje.
Recordemos que las expresiones de tipo del lenguaje sern los tipos simples char,
integer, subrango, void y error. Los constructores de tipo que tenemos son
pointer, array (nicamente con subrangos de nmeros enteros), (funcin) y
record.
Un programa vlido es el siguiente:

VAR
vector:array[1..10] of integer;
puntero:^integer;
pDoble:^^integer;
v:^array[1..10] of ^char;
w:array[1..10] of ^char;
f:function(integer,^char):^integer;
p:procedure(^integer);
r:record
dia:integer;
mes:integer;
anio:integer;
end;
BEGIN
45;
'A';
f;
vector[3];
puntero^;
pDoble^^;
pDoble^;
vector[puntero^];
v^[puntero^]^;
w[f(3,w[1])^]^;
p(f(r.dia,w[2]));
END.
A continuacin se muestra una gramtica libre de contexto capaz de reconocer sintctica-
mente el lenguaje:

(1) S

VAR declaraciones
BEGIN expresiones END .
(2) declaraciones
1


declaraciones
2
declaracion ;
(3) |

(4) declaracion

ID : tipo
(5) tipo
1


INTEGER
(6) | CHAR
(7) | ^ tipo
2

(8) | ARRAY [ CTE_ENTERA
1
.. CTE_ENTERA
2
]
OF tipo
2

(9) | FUNCTION ( listatipos ) : tipo
2

(10) | PROCEDURE ( listatipos )
(11) | RECORD listacampos END
(12) listatipos

tipos
(13) |

(14) tipos
1


tipos
2
, tipo
(15) | tipo
Comprobacin de Tipos
87
(16) listacampos
1


listacampos
2
ID : tipo ;
(17) |

(18) expresiones
1


expresiones
2
expresion ;
(19) |

(20) expresion
1


( expresion
2
)
(21) | CTE_ENTERA
(22) | CTE_CARCTER
(23) | ID
(24) | expresion
2
^
(25) | expresion
2
[ expresion
3
]
(26) | expresion
2
. ID
(27) | ID ( listaexps )
(28) listaexps

exps
(29) |

(30) exps
1


exps
2
, expresion
(31) | expresion
Desarrollaremos una definicin dirigida por sintaxis que implemente un sistema de tipos,
asignando expresiones de tipos a cada construccin del lenguaje. Puesto que tenemos de-
claraciones de identificadores, necesitaremos una tabla de smbolos (objeto ts) que ofrezca
las operaciones insertar y buscar.
En la declaracin de una variable, el smbolo no terminal tipo podr definir un atributo
sintetizado que albergue su expresin de tipo:

(5) tipo
1.
et = integer
(6) tipo
1.
et = char
(7) tipo
1.
et = pointer(tipo
2.
et)
(8) tipo
1.
et = array( CTE_ENTERA
1
.valor..CTE_ENTERA
2
.valor,
tipo
2.
et)
La declaracin de los tipos funcin, procedimiento y registro requieren una lista de tipos
para poder coleccionar parmetros y campos. Un modo de representar esta coleccin es
mediante el constructor de tipo producto (cartesiano):

(12) listatipos.et = tipos.et
(13) listatipos.et = ()
(14) tipos
1
.et = ( tipos
2
.et x tipo.et )
(15) tipos
1
.et = ( tipo.et )
(16) listacampos
1
.et = ( listacampos
2
.et x
( ID.valor x tipo.et) )
(17) listacampos
1
= ()
Una vez asignadas las expresiones de tipo producto, tanto a los parmetros de funciones y
procedimientos como a los campos de los registros, podremos acabar de sintetizar el atri-
buto tipo.et:

(9)
tipo
1.
et = listatipos.et tipo
1.
et
(10)
tipo
1.
et = listatipos.et void
(11) tipo
1.
et = record(listacampos.et)
Tras llevar a cabo el clculo del atributo tipo.et, podremos insertar los identificadores
con su tipo adecuado en la tabla de smbolos. De este modo, cuando posteriormente apa-
rezca un identificador en una expresin, podremos conocer su tipo.

(4) ts.insertar(ID.valor, tipo.et)
Anlisis Semntico en Procesadores de Lenguaje
88
El resto de la definicin dirigida por sintaxis tendr por objetivo implementar la parte del
sistema de tipos en la que se asignar expresiones de tipo a cada una de las expresiones del
lenguaje. El sistema de tipos deber, pues, asignar al no terminal expresion un atributo
que represente su tipo. En el caso de haberse producido algn error, el tipo a asignar ser
error.

(20) expresion
1
.et = expresion
2
.et
(21) expresion
1
.et = integer
(22) expresion
1
.et = char
(23) expresion
1
.et = ts.buscar(ID.valor)
(24) if (expresion
2
.et == pointer(t))
expresion
1
.et = t
else expresion
1
.et = error
(25) if (expresion
2
.et==array(i,t) && expresion
3
.et==integer)
expresion
1
.et = t
else expresion
1
.et = error

(26) if (expresion
2
.et == record(et1 x (ID.valor x t) x et2)
expresion
1
.et = t
else expresion
1
.et = error

La primera regla semntica es necesaria para que la gramtica atribuida sea completa. Las
reglas 21 y 22 se limitan a asignar el tipo de la expresin al ser sta una constante. En la
produccin en la que aparece un identificador en la parte derecha, se accede a la tabla de
smbolos para conocer la expresin de tipo del identificador. Si ste no se ha declarado
previamente en el programa si no est en la tabla, el mtodo buscar devolver el tipo
error.
En el caso del operador de desreferenciar, la comprobacin es que la expresin a la que se
aplica ste ha de ser de tipo puntero. La expresin de tipo inferida es el tipo apuntado por
el puntero. Para los arrays la inferencia del tipo es similar. Sin embargo, es necesario com-
probar que la expresin utilizada como ndice sea de tipo entero. Ntese cmo, en el caso
ms general, no podremos saber en tiempo de compilacin si la expresin est comprendi-
da en el subrango del array, ya que este valor ser evaluado en tiempo de ejecucin
56
.
Para los registros, la comprobacin del operador punto es, por un lado, ratificar que el tipo
de la expresin al que se le aplica dicho operador es de tipo registro. Por otra parte, debe-
mos encontrar un campo con igual nombre que el identificador ubicado en la parte derecha
del punto. Si no fuere as, el tipo inferido ser error. En la notacin empleada en el ejem-
plo, la expresin de tipo record(et1 x (ID.valor x t) x et2) indica que et1 y
et2 pueden ser cualquier expresin de tipo incluyendo ninguna. De este modo, t hace
referencia a la expresin de tipo asociada al campo que coincide con el valor del identifica-
dor.
En la invocacin a funciones y procedimientos, vuelve a aparecer el producto de expresio-
nes de tipos:

(28) listaexps.et = exps.et
(29) listaexps.et = ()
(30) exps
1
.et = ( exps
2
.et x expresion.et )
(31) exps
1
.et = ( expresion.et )

56
Puede haber casos en los que un compilador pueda dar un error. Por ejemplo, el acceso v[23] si el
array v fue declarado con el subrango 1..10. Sin embargo, esto no es siempre factible: v[i] depende
del valor que posea la variable i en la ejecucin del programa.
Comprobacin de Tipos
89
Una vez inferido el tipo de los parmetros, podremos comprobar la validez semntica de la
invocacin:

(27)
if ( ts.buscar(ID.valor)==(tp td) && tp==listaexps.et )
expresion
1
.et = td
else expresion
1
.et = error
La regla anterior comprueba que el identificador empleado en la invocacin haya sido de-
clarado como funcin o procedimiento, y que el tipo de todos los parmetros reales coinci-
da con los tipos de los parmetros formales. Si la condicin es verdadera, el tipo inferido
resultante de la invocacin ser el tipo que devuelva la funcin void en el caso de un
procedimiento. En cualquier otro caso, el tipo resultante ser error.
Una vez implementado el sistema de tipos, es posible conocer el tipo de toda construccin
sintctica. El comprobador de tipos deber verificar que todas las reglas semnticas relati-
vas a los tipos se cumplen. En nuestro caso, podemos identificar los siguientes puntos en
los que el comprobador podra dar un mensaje de error:

P B
(4) !ts.existe(ID.valor)
(18) expresiones
1
.et != error
El comprobador de tipos deber verificar que la expresin no haya sintetizado una expre-
sin de tipo error, y que un identificador no sea declarado ms de una vez.

Ejemplo 44. En este ejemplo se mostrar cmo, a partir de la representacin de las expresiones
de tipo diseadas en el Ejemplo 42, la implementacin del sistema de tipos del ejercicio
anterior es realmente evidente. El procesamiento del lenguaje fuente ser llevado a cabo en
una nica pasada, gracias a la sencillez del mismo y a que no se va a desarrollar la fase de
generacin de cdigo. El siguiente fragmento de la especificacin yacc/bison realiza las
fases de anlisis sintctico y semntico:

programa: VAR declaraciones BEGINPR expresiones END '.'
;
declaraciones: declaraciones declaracion ';'
| /* vacio */
;
declaracion: ID ':' tipo { if (ts.existe($1)) {
cerr<<"Error en linea "<<yylineno;
cerr<<". "<<$1<<" ya declarado.\n";
}
ts.insertar($1,$3);
}
;
tipo: INTEGER { $$=new Integer; }
| CHAR { $$=new Char; }
| '^' tipo { $$=new Pointer($2); }
| ARRAY '[' CTE_ENTERA DOS_PUNTOS CTE_ENTERA ']' OF tipo
{ $$=new Array($3,$5,$8); }
| FUNCTION '(' listatipos ')' ':' tipo
{ $$=new Function($6,*$3); delete $3; }
| PROCEDURE '(' listatipos ')'
{ $$=new Function(new Void,*$3); delete $3; }
| RECORD listacampos END
{ $$=new Record(*$2); delete $2; }
;
listatipos: tipos { $$=$1; }
| /* vacio */ { $$=new vector<ExpresionTipo*>; }
;
tipos: tipos ',' tipo { $$=$1; $$->push_back($3); }
| tipo { $$=new vector<ExpresionTipo*>; $$->push_back($1); }
Anlisis Semntico en Procesadores de Lenguaje
90
;
listacampos: listacampos ID ':' tipo ';' { $$=$1; (*$$)[$2]=$4; }
| /* vacio */ { $$=new map<string,ExpresionTipo*>; }
;
expresiones: expresiones expresion ';'
{ Error *e=dynamic_cast<Error*>($2);
if (e) cerr<<*e<<endl;
else {
cout<<"Linea "<<yylineno<<", tipo "<<
$2->expresionTipo()<<", "<<
$2->getBytes()<<" bytes.\n"; }
}
| /* vacio */
;
expresion: '(' expresion ')' { $$=$2; }
| CTE_ENTERA { $$=new Integer; }
| CTE_CARCTER { $$=new Char; }
| ID { $$=ts.buscar($1); }
| expresion '^' { $$=$1->flecha(); }
| expresion '[' expresion ']' { $$=$1->corchete($3); }
| expresion '.' ID { $$=$1->punto($3); }
| ID '(' listaexpresiones ')'
{ $$=ts.buscar($1)->parentesis(*$3); delete $3; }
;
listaexpresiones: comaexpresiones { $$=$1; }
| /* vacio */ { $$=new vector<ExpresionTipo*>; }
;
comaexpresiones: comaexpresiones ',' expresion
{ $$=$1; $$->push_back($3); }
| expresion { $$=new vector<ExpresionTipo*>; $$->push_back($1); }
;
La primera rutina semntica propia del comprobador de tipos es la ubicada en la produc-
cin declaracion: si el identificador que se est declarando ya existe en la tabla de sm-
bolos, se muestra un error semntico al programador.
La construccin de las expresiones de tipo a la hora de declarar un identificador ha sido
implementada mediante el empleo de constructores. Se ha aumentado el smbolo no termi-
nal tipo con un atributo de tipo ExpresionTipo*. Este no terminal tendr siempre la
expresin de tipo asociada. En el caso de los parmetros de una funcin, el producto de
expresiones de tipo se ha representado con la clase vector de la librera estndar de
ISO/ANSI C++. Del mismo modo, para el producto de los campos de un registro se ha
utilizado la clase map [Stroustrup93].
Para las distintas alternativas de producciones en las que expresion aparece en la parte
izquierda, el sistema de tipos se limita a inferir el tipo de cada una de las expresiones. En el
caso de que la expresin sea una constante, su tipo es el de la constante. Si es un identifica-
dor, el tipo se obtiene de la tabla de smbolos si el identificador no pertenece a la tabla, la
expresin de tipo devuelta ser error.
En el resto de alternativas, el sistema de tipos se limita a llamar a un mtodo de la clase
ExpresionTipo, raz de la jerarqua del patrn de diseo Composite, que modela el opera-
dor oportuno: flecha, corchete, punto y parentesis. Estos mtodos tienen un
comportamiento por omisin, el implementado en la clase ExpresionTipo, que se limita
a devolver el tipo error:

ExpresionTipo *ExpresionTipo::flecha() {
return new Error("Operacin ^ no permitida.");
}

ExpresionTipo *ExpresionTipo::corchete(const ExpresionTipo*) {
return new Error("Operacin [] no permitida.");
}

ExpresionTipo *ExpresionTipo::punto(const string&) {
Comprobacin de Tipos
91
return new Error("Operacin . no permitida.");
}

ExpresionTipo *ExpresionTipo::parentesis(const vector<ExpresionTipo*>&)
{
return new Error("Operacin [] no permitida.");
}
Los objetos Error (que modelan la expresin de tipo error) poseen un atributo con el
mensaje de error, y otro con el nmero de lnea en el que ste se produjo. Por omisin, los
cuatro operadores devuelven un error semntico. Cada tipo que defina el operador como
vlido deber redefinir (derogar) este funcionamiento por omisin:

ExpresionTipo *Pointer::flecha() { return a; }

ExpresionTipo *Array::corchete(const ExpresionTipo *e) {
if (!dynamic_cast<const Integer*>(e))
return new Error("El ndice ha de ser de tipo entero.");
return de;
}

ExpresionTipo *Record::punto(const string &campo) {
if (campos.find(campo)==campos.end()) {
ostringstream o;
o<<"El campo \""<<campo<<"\" no esta declarado en el registro.";
return new Error(o.str());
}
return campos[campo];
}

ExpresionTipo *Function::parentesis(const vector<ExpresionTipo*> &v) {
if (v.size()!=parametros.size()) {
ostringstream o;
o<<"La funcin posee "<<parametros.size()<<
" parmetros y se le estn "<<"pasando "<<v.size()<<".";
return new Error(o.str());
}
// * Comparamos la igualdad de los tipos
int equivalente=1;
unsigned i=0;
for (;i<v.size()&&equivalente;i++)
equivalente=parametros[i]->equivalente(v[i]);
if (!equivalente) {
ostringstream o;
o<<"La funcion est declarada con el parmetro nmero "<<i<<
" de tipo "<<parametros[i-1]->expresionTipo()<<
" y se le est pasando una expresin de tipo "<<
v[i-1]->expresionTipo()<<".";
return new Error(o.str());
}
return devolucion;
}
Ntese cmo cada uno de estos mtodos implementa la parte del sistema de tipos asociada
a cada uno de los operadores oportunos. En el caso de que el tipo no coincida, se ejecutar
el funcionamiento por omisin devolviendo el tipo error. Los casos correctos y su se-
mntica son:
Operador flecha sobre el tipo pointer. Simplemente devuelve el tipo al que apunta.
Operador corchete sobre el tipo array. Si el tipo del ndice es entero, devuelve el tipo
que contiene. En caso contrario devuelve el tipo error con el mensaje oportuno.
Operador punto sobre el tipo record. Si el valor del identificador ubicado a la derecha
del punto coincide con uno de los campos del registro, se devuelve la expresin de tipo
asociada; si no, error.
Anlisis Semntico en Procesadores de Lenguaje
92
Operador parntesis sobre el tipo . Si el nmero de parmetros reales y cada un de
sus tipos coincide con el nmero y tipos de los parmetros formales, el tipo inferido es
el tipo que devuelva la funcin void para procedimientos. En caso contrario, error
con un mensaje apropiado.
Una vez el sistema de tipos infiera el tipo de cada expresin, el comprobador de tipos, en la
produccin expresiones, verificar si el tipo inferido es error. En ese caso, mostrar
por la salida estndar de error el nmero de lnea y el mensaje de error, y seguir llevando a
cabo el anlisis del cdigo fuente. Uno de los beneficios de emplear esta expresin de tipo
especial (error) es que, de un modo sencillo, se puede implementar un procesador de
lenguaje capaz de recuperarse ante los errores de tipo.
En el caso de que el tipo inferido en cada expresin sea correcto, a modo de ejemplo se
muestra el nmero de lnea, la expresin de tipo de tipo inferida, y el nmero de bytes que
el generador de cdigo empleara para albergar en memoria el valor de la expresin. Los
mtodos expresionTipo y getBytes son implementados por cada tipo especfico de
un modo recursivo, haciendo uso de la estructura del patrn Composite.
El siguiente programa de entrada genera la salida mostrada:

Archivo de entrada Salida del procesador
VAR
vector:array[1..10] of
integer;
puntero:^integer;
pDoble:^^integer;
v:^array[1..10] of
^char;
w:array[1..10] of ^char;
f:function(integer,^char
):^integer;
p:procedure(^integer);
r:record
dia:integer;
mes:integer;
anio:integer;
end;
BEGIN
45;
'A';
f;
vector[3];
puntero^;
pDoble^^;
pDoble^;
vector[puntero^];
v^[puntero^]^;
w[f(3,w[1])^]^;
p(f(r.dia,w[2]));
END.

















Linea 15, tipo integer, 4 bytes.
Linea 16, tipo char, 1 bytes.
Linea 17, tipo (integer,pointer(char))
->pointer(integer), 4 bytes.
Linea 18, tipo integer, 4 bytes.
Linea 19, tipo integer, 4 bytes.
Linea 20, tipo integer, 4 bytes.
Linea 21, tipo pointer(integer), 4 bytes.
Linea 22, tipo integer, 4 bytes.
Linea 23, tipo char, 1 bytes.
Linea 24, tipo char, 1 bytes.
Linea 25, tipo void, 0 bytes.


Como caso contrario, se muestra un programa en la que cada expresin del mismo posee
un error semntico. Los mensajes son mostrados por el procesador en la salida estndar de
error:

Archivo de entrada Salida del procesador
VAR
vector:array[1..10]
Error en la lnea 15. El identificador "i"
no se ha declarado.
Comprobacin de Tipos
93
Archivo de entrada Salida del procesador
of integer;
puntero:^integer;
pDoble:^^integer;
v:^array[1..10]
of ^char;
w:array[1..10]
of ^char;
f:function(integer,
^char):^integer;
p:procedure(^integer);
r:record
dia:integer;
mes:integer;
anio:integer;
end;
BEGIN
i;
vector['a'];
puntero[3];
vector[puntero];
puntero.campo;
puntero^^;
vector^;
f(3);
f(3,3);
f(p(puntero),w[3]);
r.campo;
END.

Error en la lnea 16. El ndice ha de ser
de tipo entero.
Error en la lnea 17. Operacin [] no
permitida.
Error en la lnea 18. El ndice ha de
ser de tipo entero.
Error en la lnea 19. Operacin . no
permitida.
Error en la lnea 20. Operacin ^ no
permitida.
Error en la lnea 21. Operacin ^ no
permitida.
Error en la lnea 22. La funcin posee 2
parmetros y se le estn pasando 1.
Error en la lnea 23. La funcion est
declarada con el parmetro nmero 2
de tipo pointer(char) y se le est
pasando una expresin de tipo integer.
Error en la lnea 24. La funcion est
declarada con el parmetro nmero 1
de tipo integer y se le est pasando
una expresin de tipo void.
Error en la lnea 25. El campo "campo"
no esta declarado en el registro.


Para consultar cualquier detalle de implementacin, refirase al apndice C.

6.5. Comprobacin Esttica y Dinmica de Tipos
Las comprobaciones de tipos son estticas si stas son llevadas a cabo antes de
que el programa se ejecute, es decir, en tiempo de compilacin. Para poder implementar un
comprobador de tipos esttico, es necesario que toda construccin del lenguaje tenga aso-
ciada un tipo mediante el sistema de tipos.
Ejemplo 45. El siguiente programa en Pascal:

PROGRAM Tipos;
VAR i,j:integer;
r:real;
BEGIN
j:=i*r; {* Error en la comprobacin esttica de tipos *}
END.
Posee un error de tipo puesto que no es posible asignar un nmero real a un nmero ente-
ro. La parte derecha de la asignacin posee un tipo real. El sistema de tipos del lenguaje
Pascal identifica en una de sus reglas que el producto de un entero y un real devuelve un
tipo real. No es necesario ejecutar la aplicacin para que el comprobador de tipos (esttico)
d un mensaje de error en la sentencia de asignacin.

Aquellas comprobaciones de tipos que sean llevadas a cabo en tiempo de ejecucin
por de un procesador de lenguaje se definen como dinmicas. Cualquier verificacin rela-
tiva a los tipos puede llevarse a cabo dinmicamente, ya que es en tiempo de ejecucin
Anlisis Semntico en Procesadores de Lenguaje
94
cuando se puede conocer el valor exacto que toma una expresin. Sin embargo, aunque
estas comprobaciones son ms efectivas, poseen dos inconvenientes:
Las comprobaciones estticas detectan los errores antes de que se ejecute la
aplicacin y, por ello, el programador podr reparar stos de un modo casi in-
mediato y no cuando se est ejecutando la aplicacin.
Las comprobaciones dinmicas ralentizan la ejecucin de la aplicacin.
Muchos lenguajes no hacen una declaracin explcita de los tipos de sus variables
(Lisp, Smalltalk, Python o Perl), teniendo el sistema de tipos que inferir stos en tiempo de
ejecucin para poder llevar a cabo las comprobaciones de tipos.
Ejemplo 46. La siguiente funcin en Lisp:

(defun division(a,b)
(/ a b) )
Devuelve la divisin de dos valores pasados como parmetros. En funcin de los valores
de ambos parmetros, puede devolver un valor entero (si ambos son enteros y la divisin
posee resto cero) real o racional (en funcin de la implementacin), e incluso podr generar
un error en tiempo de ejecucin si uno de los parmetros es, por ejemplo, una lista. La si-
guiente tabla muestra invocaciones de ejemplo, la salida del intrprete y, si lo hubiere, el
mensaje dinmico de error generado por el intrprete Allegro Common Lisp 6.2:

Entrada Salida Tipo Inferido
(division 2 1) 2
entero
(division 2 3) 2/3
racional
(division 2 3.0) 0.6666667
real
(division 8/6 2/3) 2
entero
(division 3/2 1.5) 1.0
real
(division () 1)
Type-Error: nil is not the expected type
number

Vemos como el intrprete evaluado posee un sistema y comprobador de tipos dinmico, ya
que el tipo inferido y las comprobaciones de tipo varan en funcin del valor dinmico de
cada una de las expresiones.

Existen lenguajes (Ada, Java o C#) que poseen comprobacin esttica y dinmica
de tipos. En general, implementan todas las comprobaciones de tipo que puedan llevarse a
cabo en tiempo de compilacin, demorando aqullas que nicamente puedan realizarse
dinmicamente. En este tipo de lenguajes es comn que las comprobaciones de tipo din-
micas, en el caso de que no cumplirse, lancen excepciones en tiempo de ejecucin.
Ejemplo 47. El siguiente programa en Java:

public class Tipos {
public static void main(String args[]) throws Exception {
int array[]=new int[10];
for (int i=0;i<=10;i++)
array[i]=i;
}
}
Comprobacin de Tipos
95
es procesado por el compilador llevando a cabo las comprobaciones de tipos estticas, re-
sultando todas ellas correctas. En la ejecucin, sin embargo, se produce un error dinmico,
lanzando la mquina virtual de Java la excepcin IndexOutOfBoundsException.

Los sistemas ms sofisticados en la inferencia de tipos se dan en determinados len-
guajes funcionales, notablemente en ML, Miranda y Haskell. En estos lenguajes, los pro-
gramadores tienen la posibilidad de declarar explcitamente los tipos de las variables, en
cuyo caso los compiladores se comportan como el resto de lenguajes con comprobacin
esttica de tipos. No obstante, los programadores tambin pueden rehusar dichas declara-
ciones dejando al compilador la tarea de inferir los tipos, basndose en los tipos de las
constantes y la estructura sintctica del lenguaje. Lo realmente interesente es que la inferen-
cia y comprobacin de tipos la realiza estticamente el compilador, no siendo necesaria la
ejecucin de la aplicacin
57
.
Ejemplo 48. El siguiente cdigo es la implementacin de una funcin recursiva del clculo de la
serie de Fibonacci, en el lenguaje ML:

fun fib(n) =
let fun fib_helper(f1, f2, i) =
if i = n then f2
else fib_helper( f2, f1+f2, i+1)
in
fib_helper(0, 1, 0)
end;
La funcin anterior recibe el nmero de la serie como parmetro (n) y nos devuelve su
valor. El cuerpo de la funcin es una invocacin a otra funcin anidada definida dentro de
ella: fib_helper. sta finaliza en el caso de haber sido invocada n veces. En caso contra-
rio, calcula el siguiente valor de la serie.
El compilador de ML asocia el tipo entero al parmetro i, porque se le suma a ste el valor
de la constante 1 en la cuarta lnea. De modo similar, asigna al parmetro n el tipo entero,
ya que esta variable se compara con i en la lnea anterior. La nica invocacin posible a la
funcin fib_helper recibe tres constantes enteras como parmetros; f1, f2 e i sern,
pues, enteros. Puesto que la funcin anidada devuelve en la tercera lnea f2, sta poseer el
tipo entero. Por el mismo motivo, la funcin principal tambin devolver un entero.
Una vez llevado a cabo todo este proceso de comprobacin e inferencia de tipos esttica, el
compilador nos devuelve un mensaje indicndonos la expresin de tipo de la funcin fib:
int int.

Un lenguaje posee un sistema de tipos fuerte
58
si es capaz de asegurar que no se
vaya a aplicar una operacin a un elemento del lenguaje que no soporte dicha operacin
(que no sea de dicho tipo), detectndose siempre los errores de tipo ya bien sea esttica o
dinmicamente [Scott00]. Determinados errores de tipo slo pueden ser detectados din-
micamente.
Ejemplo 49. El siguiente cdigo Java ha de implementar comprobaciones dinmicas y estticas
para hacer que el programa no posea errores de tipo:

import java.io.*;

57
Existen multitud de lenguajes que no requieren la declaracin de los tipos de las variables u objetos:
Smalltalk, Python, Perl y la mayora de lo lenguajes de Scripting (TCL, PHP o {J,Java,ECMA}Script).
Sin embargo, la inferencia de tipos de todos ellos se realiza dinmicamente.
58
Strongly typed. En ocasiones traducido como fuertemente tipificado.
Anlisis Semntico en Procesadores de Lenguaje
96
public class AccesoArray {
public static void main(String args[]) throws Exception {
int array[]=new int[10];
int i=Integer.parseInt( (new BufferedReader(
new InputStreamReader(System.in))).readLine() );
System.out.println(array[i]);
}
}
El valor de la variable i es introducido por el usuario en tiempo de ejecucin. El nico
modo de verificar que no se salga de rango es haciendo la comprobacin dinmicamente.

Se dice que un lenguaje es seguro
59
respecto al tipo, cuando protege la integridad
de sus propias abstracciones y la de las abstracciones creadas por el programador [Pier-
ce02]. Los errores de tipo pueden ser detectados esttica y dinmicamente. Cuando se de-
tecte un error de tipo dinmicamente, un lenguaje seguro debe implementar un mecanismo
para o bien manejar el error (comnmente con excepciones), o bien para finalizar la aplica-
cin sin que sta rompa la integridad de las abstracciones del programa.
Aunque determinados autores, como Luca Cardelli [Cardelli97], afirman que los
lenguajes con comprobacin fuerte de tipos son un subconjunto de los lenguajes con sis-
temas de tipos seguros, en general la mayora de autores suele emplear ambos trminos
como sinnimos.
Ejemplo 50. En Ejemplo 49 se mostr cmo Java implementaba comprobacin dinmica de
tipos, generando una excepcin cuando sta se produce. La seguridad del lenguaje permite,
adems, que el programador puede detectar estas situaciones y tomar la determinacin de
manejar el error del modo que estime oportuno:

import java.io.*;
public class AccesoArray {
public static void main(String args[]) throws Exception {
int array[]=new int[10];
try {
int i=Integer.parseInt( (new BufferedReader(
new InputStreamReader(System.in))).readLine() );
System.out.println(array[i]);
} catch(IndexOutOfBoundsException e) {
System.err.println("Fuera de rango");
}
}
}

Ejemplo 51. El lenguaje de programacin ISO/ANSI C++ no es seguro. Caractersticas como el
uso directo de punteros a memoria y el operador de ahormado (cast) hacen que el lenguaje
no asegure la integridad de sus abstracciones. Un programa de ejemplo:

#include <iostream>
#include <vector>
using namespace std;
int main() {
char s[]="C++ no es un lenguaje seguro";
((vector<int>*)s)->push_back(3);
return 0;
}
El anterior es un programa vlido para un compilador de C++. La segunda lnea del pro-
grama principal ahorma la cadena de caracteres a un puntero a vector de enteros, e inten-

59
Safe.
Comprobacin de Tipos
97
ta insertar un 3 al final. El resultado de la ejecucin del programa anterior es imprevisible
y el comit ISO/ANSI la deja como dependiente de la implementacin [ANSIC++].

La mayor parte de los lenguajes que poseen comprobacin esttica de tipos requie-
ren adicionalmente comprobacin dinmica, para poder ofrecer un sistema de tipos fuerte.
Ada es un ejemplo de este caso. Los campos variantes de los registros de Ada son compila-
dos del mismo modo que en Pascal. Sin embargo, en Ada se genera adicionalmente cdigo
para, en tiempo de ejecucin, comprobar que el acceso a un campo variante sea acorde con
el valor del campo selector. Pascal no lleva a cabo esta comprobacin.
La siguiente tabla muestra la relacin existente entre la seguridad
60
respecto al ti-
po y el momento en el que se lleva a cabo las comprobaciones, refirindonos a casos reales
de lenguajes de programacin:

Sin comproba-
cin de tipos
Slo Comproba-
cin Esttica
Comprobacin Est-
tica y Dinmica
Slo Compro-
bacin Dinmi-
ca
Safe

C#, Miranda, Java,
Haskell, ML.
Lisp, Perl, Small-
talk, Scheme
Unsafe Ensamblador,
BCPL
C, Fortran, Pascal C++

De la tabla anterior se pueden extraer un conjunto de conclusiones:
Los lenguajes que no poseen comprobacin de tipos (como ensamblador o
BCPL) no son seguros.
Los lenguajes que nicamente poseen comprobacin esttica de tipos no pue-
den ser seguros, ya que existen abstracciones de ellos que requieren comproba-
ciones en tiempo de ejecucin por ejemplo los arrays.
La mayor parte de los lenguajes que implementan comprobacin dinmica de
tipos ofrecen seguridad respecto al tipo. Una vez implementadas comprobacio-
nes dinmicas, es comn que se implementen todas. Una excepcin es C++.
Este lenguaje aadi una pequea informacin de tipos en tiempo de ejecucin
(RunTime Type Information, RTTI) para poder recuperar el tipo de un objeto, al
emplear ste en una jerarqua polimrfica [Eckel00]. No se aadieron otro tipo
de comprobaciones dinmicas por motivos de eficiencia.
Un lenguaje seguro no tiene por qu tener comprobacin esttica de tipos.
6.6. Equivalencia de Expresiones de Tipo
En la definicin dirigida por sintaxis mostrada en el Ejemplo 43, aparecan reglas
semnticas como:

if (expresion
2
.et == pointer(t))
En dicha regla, y en cualquier sistema de tipos, es necesario tener una definicin
precisa de cundo dos expresiones de tipo son equivalentes; es decir, en la sentencia ante-

60
Safety.
Anlisis Semntico en Procesadores de Lenguaje
98
rior, qu significa exactamente la igualdad de expresiones de tipo. La equivalencia de tipos
es utilizada intensivamente en todos los sistemas de tipos
61
.
Existen distintas formas en las que un lenguaje define semnticamente la equivalen-
cia entre sus tipos. Surgen adems posibles ambigedades en los lenguajes que permiten
asignar nombres a las expresiones de tipo mediante type en Pascal y Haskell, o con
typedef en C++.
Una primera equivalencia es la equivalencia estructural de expresiones de tipo.
Un sistema de tipos define que dos tipos son estructuralmente equivalentes, nicamente si
poseen la misma estructura, es decir, o son el mismo tipo bsico, o bien estn formadas
aplicando el mismo constructor a tipos estructuralmente equivalentes. Lenguajes tales co-
mo ML, Algol-68 y Modula-3 emplean un sistema de tipos basado en equivalencia estructu-
ral. C++ implementa equivalencia estructural, excepto para clases, registros y uniones. El
lenguaje Pascal no tiene una equivalencia de tipos definida; depende de la implementacin
del compilador.
Ejemplo 52. El siguiente cdigo ISO/ANSI C++:

#include <iostream>
using namespace std;

typedef int entero;
typedef int *punteroEntero;

void fi(int n,int *p) { cout<<n<<' '<<p<<endl; }
void fe(entero n,punteroEntero p) { cout<<n<<' '<<p<<endl; }

int main() {
int n,*pn=&n;
entero e=n;
punteroEntero pe=pn;

fi(e,pe);
fe(n,pn);

return 0;
}
es semnticamente correcto. Un compilador que implemente el estndar ISO/ANSI acep-
tar el programa como vlido, generando un cdigo objeto. Tanto en las dos ltimas asig-
naciones del programa principal, como en las dos invocaciones a funciones, aparece la
equivalencia estructural de tipos que dicho lenguaje implementa en su sistema de tipos.

En 6.3 indicbamos cmo el modo ms comn de modelar las expresiones de ti-
po en un procesador de lenguaje era mediante estructuras compuestas recursivamente. s-
tas se crean de un modo jerrquico, pudiendo dar lugar a estructuras de rbol o ms efi-
cientes estructuras de grafo acclicos dirigidos (DAGs). En el caso de que se estn repre-
sentando las expresiones de tipo con un DAG, cada expresin de tipo tendr una nica
representacin en memoria. Por tanto, la comprobacin de equivalencia, en este caso, se
reduce a verificar la identidad del nodo: si los dos nodos del DAG son el mismo, entonces
las expresiones de tipo son estructuralmente equivalentes.
En el cado de implementar las expresiones de tipo con una estructura de rbol, hace
que stas puedan estar repetidas y que, por tanto, la equivalencia estructural de tipos no est

61
Ntese que no se est abordando el problema de la conversin, implcita o explcita, de tipos que se da
en muchos lenguajes de programacin esto ser tratado en el siguiente punto. Lo que se est acometien-
do aqu es la equivalencia (correspondencia o igualdad) entre tipos.
Comprobacin de Tipos
99
ligada a la identidad de los nodos del rbol. El proceso que deber llevarse a cabo ser
comprobar recursivamente que las estructuras de los dos nodos sean equivalentes.
Ejemplo 53. En el Ejemplo 43 se desarroll una definicin dirigida por sintaxis que implementa-
ba un sistema de tipos de un subconjunto del Pascal. Los tipos simples definidos eran
char, integer, subrango, void y error. Los constructores de tipo que tenamos eran
pointer, array, (funcin) y record.
Para poder implementar un sistema de tipos con equivalencia estructural, el sistema de ti-
pos se centrar en una funcin equivale que recibir dos expresiones de tipo, represen-
tadas mediante un rbol, y devolver si ambas son o no equivalentes.

boolean equivale(ExpTipo t1,ExpTipo t2) {
if (t1==char && t2==char) return true;
if (t1==integer && t2==integer) return true;
if (t1==void && t2==void) return true;
if (t1==a1..b1 && t2==a2..b2) return a1==a2&&b1==b2;
if (t1==pointer(tp1) && t2==pointer(tp2))
return equivale(tp1,tp2);
if (t1==array(a1,b1) && t2==array(a2,b2))
return equivale(a1,a2) && equivale(b1,b2);
if (t1==a1xb1 && t2==a2xb2)
return equivale(a1,a2) && equivale(b1,b2);
if (t1==a1b1 && t2==a2b2)
return equivale(a1,a2) && equivale(b1,b2);
if (t1==record(a) && record(b))
return equivale(a,b);
if (t1==t2) // Nombres de los campos de un registro
return true;
return false; // En el resto de casos
}

Ejemplo 54. Si la implementacin de equivalencia estructural de tipos fuese reutilizada mediante
el patrn de diseo Composite ( 6.3), el algoritmo anterior estara disperso en la implemen-
tacin de un nico mtodo de las distintas clases de la jerarqua. ste es el mtodo
equivalente que mostrbamos en el diagrama de clases del Ejemplo 42. Su comporta-
miento por omisin est enfocado a la equivalencia de los tipos simples: dos tipos bsicos
con equivalentes si son el mismo tipo:

bool ExpresionTipo::equivalente(const ExpresionTipo *et) const {
return typeid(*this)==typeid(*et); // * RTTI
}
La comparacin realizada es respecto a las dos clases de los dos objetos C++. Esta funcio-
nalidad se puede obtener aplicando el operador de igualdad a la devolucin del operador
typeid
62
. La comparacin estructural de los tipos construidos se obtiene mediante un
proceso recursivo de comparacin de equivalencia. La implementacin es muy sencilla:

bool Pointer::equivalente(const ExpresionTipo *et) const {
const Pointer *puntero=dynamic_cast<const Pointer*>(et);
if (!puntero) return false;
return a->equivalente(puntero->a);
}


62
Esta funcionalidad del ISO/ANSI C++ se denomina RTTI y ser explicada con ms detenimiento en
6.7.
Anlisis Semntico en Procesadores de Lenguaje
100
bool Array::equivalente(const ExpresionTipo *et) const {
const Array *array=dynamic_cast<const Array*>(et);
if (!array) return false;
return desde==array->desde && hasta==array->hasta &&
array->equivalente(array->de);
}

bool Function::equivalente(const ExpresionTipo *et) const {
const Function *fun=dynamic_cast<const Function*>(et);
if (!fun) return false;
if (parametros.size()!=fun->parametros.size())
return false;
for (unsigned i=0;i<parametros.size();i++)
if (!parametros[i]->equivalente(fun->parametros[i]))
return false;
return devolucion->equivalente(fun->devolucion);
}

bool Record::equivalente(const ExpresionTipo *et) const {
const Record *record=dynamic_cast<const Record*>(et);
if (!record) return false;
if (campos.size()!=record->campos.size())
return false;
map<string,ExpresionTipo*>::const_iterator it1,it2;
for (it1=campos.begin(),it2=record->campos.begin();
it1!=campos.end();++it1,++it2) {
if (it1->first!=it2->first)
return false;
if (!it1->second->equivalente(it2->second))
return false;
}
return true;
}
Como hemos mencionado previamente, esta comparacin recursiva de la estructura
de las expresiones de tipo es necesaria por existir la duplicidad de objetos que representan
una misma expresin de tipo estructura de rbol. Si se implementa mediante un DAG, la
equivalencia estructural se reduce a una comparacin de identidad de los nodos del DAG.

En la implementacin del algoritmo anterior hay que tener cuidado a la hora de tratar defi-
niciones de tipos mutuamente recursivas, ya se pueden producir bucles infinitos [Lou-
den97]. Este es el caso de la siguiente declaracin en Pascal:

Type enlace = ^nodo;
nodo = record
dato : integer;
siguiente: enlace
end;
Supngase, el siguiente programa en Pascal:

PROGRAM EquivalenciaTipos;
TYPE Estudiante = Record
nombre,direccion: array[1.255] of char;
edad: integer;
end;
Colegio = Record
nombre,direccion: array[1.255] of char;
edad: integer;
end;
VAR e:Estudiante;
c:Colegio;
BEGIN
e:=c;
END.
Comprobacin de Tipos
101
Un programador probablemente desear que la anterior asignacin sea catalogada
por el analizador semntico como un error de compilacin, ya que se ha asignado acciden-
talmente un registro colegio a un estudiante. Sin embargo, un sistema de tipos con equiva-
lencia estructural de tipos (Algol-68 y Mdula-3) no producira un error semntico. As, la
equivalencia de nombres establece que todo tipo ha de tener un nombre nico, conside-
rando dos tipos equivalentes slo si tienen el mismo nombre. Lenguajes con equivalencia
de nombres son Java y Ada.
Ejemplo 55. El siguiente programa en Java demuestra que su sistema de tipos posee equivalencia
de nombres:

interface IA {
public void mA();
}

interface IB {
public void mB();
}

class A implements IA, IB {
public void mA() { System.out.println("A.mA"); }
public void mB() { System.out.println("A.mB"); }
};

class B implements IA, IB {
public void mA() { System.out.println("B.mA"); }
public void mB() { System.out.println("B.mB"); }
};

public class EquivalenciaNombres {
public static void main(String[] args) {
A a=new B();
}
}
La nica sentencia del programa principal resulta un error semntico. Aunque la es-
tructura de las dos clases A y B coincida, y ambas implementen los dos tipos IA e IB, no
existe equivalencia al ser los nombres de los tipos distintos.

Existe un caso particular que se puede dar en aquellos lenguajes que permitan defi-
nir nuevos identificadores de tipo como type en Pascal, Ada y Haskell, o typedef en
C++. Dicho caso se da cuando un tipo se define igual que el nombre de otro tipo, estable-
cindose as como un alias del segundo. De este modo, si un lenguaje con equivalencia de
tipos basada en nombres identifica un alias de un tipo como equivalente al tipo al que hace
referencia, se dice que posee equivalencia de declaracin.
Ejemplo 56. En el siguiente programa en Modula-2:

MODULE EquivalenciaDeclaracion.
TYPE celsius = REAL;
fahrenheit = REAL;
centigrados = celsius;
VAR
c: celsius;
f: fahrenheit;
m: centigrados;
BEGIN
f := c; (* Error *)
m := c; (* OK *)
END EquivalenciaDeclaracion.
Define tres tipos: celsius y fahrenheit (alias de real) y centigrados (alias
de celsius). Modula-2 posee un sistema de tipos con equivalencia por declaracin. As, la
Anlisis Semntico en Procesadores de Lenguaje
102
primera asignacin no es correcta puesto celsius y fahrenheit no poseen igual nom-
bre de tipo y, adems, tampoco son alias. De forma contraria, la segunda asignacin es co-
rrecta ya que el tipo de m (centigrados) es un alias del tipo de c (celsius). En el caso
de que tuviese equivalencia de nombres estricta, la ltima asignacin tambin sera descar-
tada por el analizador semntico.

El lenguaje C++ (as como muchas implementaciones de Pascal) emplean equiva-
lencia de declaracin para clases, registros y uniones. ML posee equivalencia estructural,
pero ofrece al programador la posibilidad de utilizar equivalencia por nombre. Si deseamos
definir un tipo autor, como producto cartesiano de una cadena de caracteres (nombre) y un
entero (cdigo), podemos hacerlo de la siguiente forma:

type autor = string * int;
Este tipo es equivalente con cualquier producto de una cadena y un entero. Si lo
que el programador desea es evitar dicha compatibilidad, podr crear el nuevo tipo con
equivalencia de nombres, mediante la siguiente sintaxis:

datatype autor = au of string * int;
6.7. Conversin y Coercin de Tipos
Considrese la expresin x+i donde x es una variable de tipo real e i es de tipo en-
tero. Como un ordenador representa los reales y los enteros de forma distinta, la operacin
de suma es distinta en funcin de si sta es de nmeros reales o enteros. En el ejemplo ex-
puesto, deber convertirse el valor de i a su correspondiente valor real, previamente a lle-
var a cabo la operacin suma.
La conversin de tipos entre valores puede ser llevada a cabo implcitamente si el
compilador la realiza de un modo automtico. Este tipo de conversin implcita recibe el
nombre de coercin
63
. La mayora de los lenguajes ofrecen coercin de tipos en aquellos
contextos donde la conversin no supone prdida alguna de informacin. La conversin
implcita de nmeros enteros donde se requiera un nmero real se da, por ejemplo, en
C++, Pascal, Java y C#. En Ada
64
y Modula-2 no existe coercin alguna.
La coercin de tipos suele requerir que el generador de cdigo produzca rutinas de
conversin (e incluso de validacin semntica) que sern ejecutadas por el programa en
tiempo de ejecucin. Esto har que la ejecucin del programa se vea ralentizada por la con-
versin dinmica de tipos. Si el lenguaje no posee un comprobador de tipos esttico, la
coercin supondr un mayor coste computacional puesto que, adems de la conversin,
ser necesario inferir el tipo en tiempo de ejecucin. Por este motivo, la seleccin correcta
de los tipos de las expresiones en un programa, puede mejorar sustancialmente el rendi-
miento de ste.
Ejemplo 57. La generacin del siguiente programa en Microsoft Visual C++.NET 2003, sin
optimizacin de cdigo:

#include <iostream>
#include <ctime>
using namespace std;

int main() {

63
En los lenguajes basados en C, es comn denominar a la coercin de tipos promocin.
64
A excepcin de los subtipos que se puedan definir en el lenguaje.
Comprobacin de Tipos
103
const unsigned max=30000;
short in;
cin>>in;
long double id=in;
long double v[max];
long antes,despues;
antes=clock();
for (unsigned j=0;j<max;j++)
for (unsigned i=0;i<max;i++) v[i]=in;
despues=clock();
cout<<(despues-antes)/(double)CLOCKS_PER_SEC*1000<<endl;
antes=clock();
for (unsigned j=0;j<max;j++)
for (unsigned i=0;i<max;i++) v[i]=id;
despues=clock();
cout<<(despues-antes)/(double)CLOCKS_PER_SEC*1000<<endl;
return 0;
}
Hace que el segundo bucle sea un 4,33% ms rpido que el primero, gracias a que no tiene
que efectuar conversiones (implcitas) de tipo en tiempo de ejecucin.

La mayor parte de los lenguajes compilados tienen en la actualidad a reducir la
coercin de tipos. Un ejemplo es la eliminacin, por parte del lenguaje Java, de conversio-
nes implcitas del C tales como la interpretacin de enteros como valores lgicos. En Java
existe el tipo boolean (bool en C++) que es incompatible (implcita y explcitamente)
con cualquier otro tipo.
Por otro lado, existen diseadores de lenguajes que opinan que la coercin de tipos
ofrece un modo natural de ofrecer extensibilidad de las abstracciones, haciendo ms senci-
lla la utilizacin de nuevos tipos en conjuncin con los que ofrece el lenguaje. C++, en
particular, ofrece mecanismos extremadamente ricos para establecer coercin de tipos. Al
procurar un mecanismo tan rico de coercin, las reglas del sistema de tipos de este lenguaje
hacen que dichos mecanismos sean complejos de entender y emplear correctamente.
Ejemplo 58. El siguiente programa en C++ define una clase Entero, con conversin implcita a
dos tipos bsicos del lenguaje: int y double.

#include <iostream>
using namespace std;

class Entero {
int entero;
public:
Entero(int n) { entero=n; }
operator double() const { return entero; }
int getEntero() const { return entero; }
Entero operator+(const Entero &e) const {
return Entero(entero+e.entero);
}
};

void fe(Entero n) { cout<<n.getEntero()<<endl; }
void fd(double n) { cout<<n<<endl; }
int main() {
fe(3);
Entero e(3);
fd(e);
return 0;
}
La primera conversin es por va de su constructor. Al poseer ste un nico parmetro,
C++ reconoce una coercin del tipo de su parmetro (int) a los objetos de la clase. De
este modo, la invocacin fe(3) es totalmente correcta, ya que el int 3 se convierte, por
Anlisis Semntico en Procesadores de Lenguaje
104
medio del constructor, a un objeto Entero. La segunda conversin se especifica con la
redefinicin del operador de ahormado (cast) a double. La invocacin fd(e) produce una
conversin implcita empleando el mtodo que define la conversin.
Este mecanismo tan sofisticado puede, no obstante, dar lugar a ambigedades. Si se evala
la expresin e+1, sta poseer un error de compilacin por ambigedad:
Podra tratarse de una conversin por medio del constructor: e+Entero(1)
Podra significar una conversin a real: (double)e+1

En los lenguajes orientados a objetos la coercin por excelencia se da mediante el
empleo de la herencia. Puesto que el conjunto de mensajes que se le puede lanzar a una
superclase es tambin vlido para cualquier subclase, se establece un mecanismo de conver-
sin implcita en el sentido ascendente de la jerarqua. La herencia supone, as, un meca-
nismo de coercin de tipos.
Ejemplo 59. El lenguaje de programacin Java, como todo lenguaje orientado a objetos, ofrece
conversin implcita de tipos en sentido ascendente de una jerarqua de herencia. No posee
herencia mltiple, pero identifica los tipos con el concepto de interface: una coleccin de
mensajes que un objeto acepta. Ntese como el concepto de interface de Java es precisamen-
te el punto de vista basado en la abstraccin de tipo que introdujimos en 6.2: el conjun-
to de operaciones que se puede realizar sobre el objeto. Una clase puede implementar (y
por tanto existir coercin a) mltiples interfaces (tipos).

class Persona implements Cloneable {
protected String nombre;
public String getNombre() { return nombre; }
public Persona(String n) { nombre=n; }
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

class Empleado extends Persona implements Comparable {
private String departamento;
public String getDepartamento() { return departamento; }
public Empleado(String n,String d) {
super (n);
departamento=d;
}
public int compareTo(Object o) {
if (o instanceof Empleado)
return ((Empleado)o).nombre.compareTo(nombre);
return -1;
}
}

public class Coercion {
public static void main(String[] args) throws Exception {
Empleado e=new Empleado("Juan","Informtica");
System.out.println(e.getDepartamento());
Persona p=e;
System.out.println(e.getNombre());
Cloneable c=e;
Object o=e.clone();
Comparable comp=e;
System.out.println(comp.compareTo(c));
}
}
Comprobacin de Tipos
105
En el programa anterior, el objeto accesible desde la referencia e, posee adems de
Object
65
la unin de los tipos Empleado (es una instancia de esta clase), Persona
(hereda de ella), Comparable y Clonable (implementa ambos tipos). De este modo, las
conversiones llevadas a cabo en el programa principal son todas implcitas.

En los casos en los que la conversin de una expresin a otro tipo distinto al que
posee conlleve una posible prdida de informacin, la conversin suele requerirse de un
modo explcito. Una conversin explcita es aqulla que requiere que el programador
escriba algo para motivar la conversin. En Modula-2 todas las conversiones son explcitas.
La asignacin:

r=i;
siendo r una variable real e i entera, genera en Java un error de compilacin indi-
cando que puede haber una prdida de precisin. Para asumir la posible prdida, el pro-
gramador deber realizar la conversin de un modo explcito, mediante el operador de
ahormado (cast):

i=(int)r;
Ahora el programador asume el riesgo de perder la parte decimal del real y el com-
pilador aceptar la conversin.
Las conversiones explcitas (al igual que las implcitas) suelen generar cdigo adi-
cional que ser ejecutado en tiempo de ejecucin. En algunos lenguajes con sistemas de
tipos fuertes suelen ejecutar, adicionalmente, un cdigo de validacin de la conversin,
verificando que no se produzca desbordamiento. Este tipo de conversiones explcitas
siempre llevan a cabo una modificacin de la representacin interna de la informacin.
Sin embargo, existe en ocasiones la necesidad de convertir el tipo de una expresin,
sin que se produzca una modificacin de la representacin interna de la informacin. Lo
que se busca es poder aplicar operaciones de un tipo distinto al real, sin que se cambie la
representacin interna de la informacin. Esta posibilidad hace que un lenguaje, por defini-
cin, no sea seguro. Un posible escenario en el que puede interesar hacer conversiones
explcitas, sin convertir la representacin de la informacin, es en la implementacin de un
nuevo algoritmo de gestin de memoria.
Ejemplo 60. En el lenguaje C, el modo de hacer conversiones explcitas sin modificar la
representacin de los datos, es mediante los ahormados a punteros. Puesto que se permite
la conversin entre punteros de distintos tipos, lo buscado se puede obtener del siguiente
modo:

#include <iostream>

template<typename Tipo>
Tipo *nuevo() {
const unsigned numeroBytes=100;
static unsigned ultimo=0;
static char memoria[numeroBytes];
if (ultimo+sizeof(Tipo)<=numeroBytes) {
ultimo+=sizeof(Tipo);
return (Tipo*)(memoria+ultimo-sizeof(Tipo));
}
return 0;
}

int main() {

65
En Java toda clase deriva de Object.
Anlisis Semntico en Procesadores de Lenguaje
106
unsigned max=0;
int *p=nuevo<int>();
*p=3;
long double*q=nuevo<long double>();
*q=34.65;
std::cout<<*p<<'\t'<<*q<<std::endl;
return 0;
}
El programa anterior implementa una funcin nuevo capaz de ofrecer memoria para cual-
quier tipo, con un tamao limitado a 100 bytes
66
. Primero se aplica, en la funcin nuevo, la
conversin de un puntero de caracteres a un puntero a cualquier tipo. Posteriormente, en el
programa principal, se aplica el operador * para convertir el tipo sin modificar la represen-
tacin interna de la informacin en este caso, un array de caracteres.

Ejemplo 61. El lenguaje C++ sigue empleando el operador cast heredado del C. Sin embargo,
para distinguir las distintas semnticas de dicho operador, ha creado distintas versiones del
mismo. En este ejemplo compararemos su operador static_cast que efecta una con-
versin de la representacin interna del dato a convertir, con el reinterpret_cast que
no realiza tal modificacin.

#include <iostream>
using namespace std;

int main() {
double d=33.44;
int i1,i2;
i1=static_cast<int>(d);
i2=reinterpret_cast<int&>(d);
cout<<i1<<'\t'<<i2<<endl;
return 0;
}
El programa anterior hace que el entero i1 posea el valor 33, ejecutndose el cdigo de
conversin en tiempo de ejecucin. De forma contraria, el valor i2 depende de la plata-
forma empleada, puesto que no modifica la representacin interna del double, mostrando
su valor como si de un entero se tratase.

Como hemos mencionado, en los lenguajes orientados a objetos es comn tener un
tipo general para cualquier objeto. En C++ es void*; en Modula-2, address; en Modu-
la-3 refany; en Eiffel y Clu, any; en Java y C#, Object. Este tipo genrico es utilizado a
la hora de implementar clases contenedoras de cualquier objeto (vectores, listas, pilas, co-
las...). La conversin de cualquier objeto es implcita, puesto que estas referencias, o bien
no poseen ninguna operacin (void*), o bien su conjunto de operaciones es tan reducido
que lo posee todo objeto. De este modo, facilitan la implementacin de colecciones de
cualquier tipo.
Una vez empleemos una coleccin de referencias genricas, surgir la necesidad de
recuperar el tipo del objeto, a la hora de extraer el elemento del contenedor. El problema es
que el contenedor nos devolver una referencia con el tipo general, en lugar de ser del tipo
especfico que habamos insertado. Las operaciones que podremos solicitar a dicha referen-
cia sern siempre menores a las que deseemos: necesitamos obtener su tipo original.
La cuestin de cmo recuperar el tipo de un objeto a partir de una referencia gen-
rica, pasa por intentar ampliar el nmero de operaciones que sobre ste se puedan aplicar.

66
En ISO/ANSI C++, el tamao de un char es un byte. Los caracteres extendidos se obtienen con el
tipo wchar_t.
Comprobacin de Tipos
107
As, puesto que estamos tratando de ampliar el conjunto de operaciones permitidas, debe-
mos efectuar la conversin de un modo explcito. Esta operacin puede dar lugar a un
error en tiempo de ejecucin: al convertir una referencia genrica a una referencia concreta,
puede suceder que sta apunte a un objeto de otro tipo distinto al que se quiere convertir
ya que, al ser genrica, puede estar apuntando a cualquier objeto.
Ejemplo 62. En el lenguaje de programacin Java, la sintaxis para llevar a cabo la recuperacin
de un tipo, de un modo descendente en la jerarqua, es mediante el operador cast.

import java.util.ArrayList;
public class DownCast {
public static ArrayList crearVector() {
ArrayList v=new ArrayList();
for (int i=-10;i<=10;i++)
v.add( new Integer(i) );
return v;
}
public static void main(String[] a) {
ArrayList v=crearVector();
System.out.println(((Integer)v.get(0)).intValue());
System.out.println(((Character)v.get(1)).charValue());
}
}
En el programa anterior la primera recuperacin del tipo es correcta, mostrndose el valor
del primer entero metido en el contenedor: -1. La segunda genera una excepcin
(ClassCastException) en tiempo de ejecucin, finalizando el programa. No es posible
convertir el tipo del segundo objeto a un tipo Character. Ntese cmo el mtodo
charValue no es una operacin vlida del objeto en cuestin.
Un programador de Java tiene mltiples mecanismos para conocer el tipo de un objeto en
tiempo de ejecucin. Distintos ejemplos son el operador instanceof, el mtodo
getClass o manejar la excepcin ClassCastException en el ahormado al tipo de-
seado.

El modo en el que los lenguajes orientados a objetos permiten realizar esta conver-
sin
67
de supertipo a subtipo (de general a especfico) es haciendo que los objetos posean
informacin de su tipo en tiempo de ejecucin. De este modo, se podr comprobar din-
micamente si el tipo a ahormar es correcto y, por tanto, si se pueden aplicar al objeto las
operaciones propias de ese tipo.
Ejemplo 63. El lenguaje ISO/ANSI C++ introdujo informacin dinmica de tipos (RTTI,
RunTime Type Information), para poder llevar a cabo la recuperacin segura del tipo de un
objeto.

#include <iostream>
#include <string>
using namespace std;

class Base {
public:
~Base() {}
virtual const char *quienSoy() {return "Base";}
};
class Derivada: public Base {
public:
virtual const char *quienSoy() {return "Derivada";}
};


67
Esta conversin suele denominarse downcast, por el sentido descendente que sigue en la jerarqua de
herencia.
Anlisis Semntico en Procesadores de Lenguaje
108
int main() {
Base *base=new Derivada;
Derivada *derivada=dynamic_cast<Derivada*>(base);
if (derivada)
cout<<"Conversin correcta.\n"<<
"Quien es: "<<derivada->quienSoy()<<".\n"<<
"Tipo: "<<typeid(*derivada).name()<<'.'<<endl;
else
cout<<"Conversin errnea.\n";
return 0;
}
El operador dynamic_cast lleva a cabo la comprobacin dinmica del tipo del objeto
apuntado. Si ste posee un tipo compatible con el solicitado, devuelve un puntero al tipo
demandado apuntando al objeto. En caso contrario devuelve 0. Otra caracterstica de RTTI
es el operador typeid que devuelve un objeto type_info con informacin respecto a su
tipo. En el ejemplo anterior, la conversin es correcta y se muestra que el tipo es
Derivada
68
.

6.8. Sobrecarga y Polimorfismo
Un smbolo est sobrecargado cuando tiene distintos significados dependiendo de
su contexto. Esta definicin suele aplicarse a operadores y funciones. Un ejemplo comn es
el operador +, ya que puede representar suma de enteros, de reales o incluso de cadena de
caracteres. Aunque para un humano la suma de enteros y reales puede tener el mismo signi-
ficado, para una mquina no es as, ya que ejecuta operaciones distintas en cada caso. En
los lenguajes Ada y Basic, el operador parntesis est sobrecargado; A(i) puede significar
tanto el acceso al elemento i-simo de un array, como la invocacin a la funcin A con el
parmetro i.
El proceso de conocer, de entre todos los posibles significados, el significado con-
creto de un smbolo sobrecargado se denomina resolucin de la sobrecarga. La sobrecarga
se resuelve en funcin del contexto en el que aparezca el smbolo sobrecargado, basndose
en las reglas que especifica el sistema de tipos.
Ejemplo 64. La siguiente gramtica libre de contexto representa un subconjunto de expresiones
en la que el operador suma representa la adicin de nmeros enteros y reales:

(1) expresion

cte_entera
(2) expresion

cte_real
(3) expresion
1


expresion
2
+ expresion
3

Se define una gramtica atribuida para calcular el cdigo a generar, en la que se infiere el
tipo de cada expresin:

P R
(1) expresion.tipo = I
expresion.codigo = PUSH_INT + cte_entera.valor
(2) expresion.tipo = F
expresion.codigo = PUSH_FLT + cte_real.valor

68
El modo de representar el tipo de un objeto en RTTI es dependiente de la implementacin. Lo nico
que se ha de cumplir es que si dos objetos poseen el mismo tipo, la comparacin de ambas devoluciones
del operador typeid ha de ser cierta.
Comprobacin de Tipos
109
P R
(3) expresion
1
.tipo=mayorTipo(expresion
2
.tipo,expresion
3
.tipo)
expresion
1
.codigo=expresion
2
.codigo +
coercion(expresion
1
.tipo,expresion
2
.tipo) +
expresion
3
.codigo +
coercion(expresion
1
.tipo,expresion
3
.tipo) +
suma(expresion
1
.tipo)

char mayorTipo(char t1,char t2) {
if ( t1 == F || t2 == F ) return F;
return I;
}

String coercion(char t1,char t2) {
if ( t1 == F && t2 == I ) return INT2FLT;
return ;
}

String suma(char tipo) {
if ( t1 == F) return ADD_FLT;
return ADD_INT;
}
Para la expresin 3+4+3.4, el cdigo a generar (el valor del atributo expre-
sion.codigo) es la consecucin de las sentencias PUSH_INT 3 (apilar un entero),
PUSH_INT 4, ADD_INT (apilar los dos enteros en el tope de la pila), INT2FLT (conver-
tir el entero en la pila a un real), PUSH_FLT 3.4 (apilar un real), ADD_FLT (sumar los
dos reales de la pila). Vemos cmo la gramtica atribuida infiere el tipo y, basndose en
ste, resuelve la sobrecarga: la primera suma se resuelve como una operacin entera, cuan-
do la segunda es real.
En este ejemplo se muestra la relacin entre el concepto de sobrecarga y el de coercin de
tipos, existente en la mayora de lenguajes. Si un operador est sobrecargado es porque se
define para distintos tipos de operandos. Si el operador es binario (como el caso de la su-
ma), puede ser que los dos operandos posean el mismo tipo, pero tambin es comn que
no sea as. Es en ese caso (en nuestro ejemplo, la suma de un entero y un real y viceversa)
es cuando, adems de resolver la sobrecarga, es necesario que el compilador realice una
conversin implcita de tipos.

El concepto de sobrecarga se emplea tambin para funciones y mtodos, donde el
mismo identificador se puede utilizar para implementaciones distintas. Comnmente, el
criterio necesario para sobrecargar un mtodo o funcin implementado previamente es
modificar el nmero de parmetros, o el tipo de alguno de ellos. En este contexto, la reso-
lucin de la sobrecarga supone conocer a qu mtodo o funcin invocar. La resolucin se
llevar cabo a partir del nmero y tipo de los parmetros reales. El comprobador de tipos
deber tomar todos los tipos asociados a un identificador y resolver cul es el correcto ante
una invocacin dada.
La sobrecarga aade complejidad a un sistema de tipos de un compilador. Si uni-
mos a sta otras caractersticas ya mencionadas, como la coercin de tipos, pueden darse
contextos ambiguos en los que la sobrecarga no pueda ser resuelta, y se tenga que generar
un error de compilacin.
Ejemplo 65. El siguiente programa en C++:

#include <iostream>
Anlisis Semntico en Procesadores de Lenguaje
110
using namespace std;

void f(double,float) { cout<<"f(double,float)\n"; }
void f(float,double) { cout<<"f(float,double)\n"; }
int main() {
f(1.0,2.0); // Error
f(1.0f,2.0); // f(float,double)
f(1.0,2.0f); // f(double,float)
f(1.0f,2.0f); // Error
return 0;
}
Posee dos lneas en las que las invocaciones a una de las funciones f son ambiguas: la pri-
mera y la ltima. Puesto que los parmetros han de ser double y float, o viceversa, la
invocacin con dos double o dos float es ambigua
69
.

Un smbolo (operador o funcin) es polimrfico respecto al tipo si define una se-
mntica independiente de los tipos a los que se aplique. Para que un smbolo sea polimrfi-
co, deber definir un nico comportamiento para todos los tipos del sistema.
Un operador polimrfico del lenguaje C es el operador de direccin &. Este opera-
dor se puede aplicar a cualquier lvalue de un tipo T, devolviendo un puntero a T. La semn-
tica no vara y se puede aplicar a determinados elementos, indistintamente del tipo que po-
sean. En el lenguaje de programacin ML, el operador de comparacin de igualdad (=) es
polimrfico, puesto que acepta dos expresiones del mismo tipo y devuelve un valor lgico.
Sin embargo, los operadores de comparacin <, <=, >= y > no son polimrficos puesto
que, de forma contraria al operador de igualdad, no se pueden aplicar a cualquier tipo.
El modo de representar las expresiones de tipos polimrficas mediante la notacin
introducida por Alfred Aho ( 6.3) es empleando variables de tipo: variables que represen-
tan, dentro de una expresin de tipo, cualquier aparicin de otra expresin de tipo. Una
variable de tipo sirve para representar cualquier tipo dentro de otra expresin de tipo. Para
indicar que una variable de tipo podr ser sustituida por cualquier tipo, se utiliza el cuantifi-
cador universal . As, la expresin de tipo del operador & del lenguaje C tendr la siguien-
te expresin de tipo:
. pointer()
La expresin de tipos anterior indica que el operador & se puede aplicar sobre cual-
quier tipo del lenguaje y devuelve un puntero al tipo al que haya sido aplicado.
Ejemplo 66. En los entornos interactivos del lenguaje de programacin ML
70
, dado un smbolo
el entorno nos muestra su expresin de tipo asociada. As, podemos preguntarle a sistema
por la expresin de tipo del operador =. El entorno mostrar la siguiente expresin de tipo:
a * a -> bool
En ML, la expresin de tipo a indica que a es una variable de tipo al que se le aplica el
cuantificador universal el apstrofo. De este modo, el operador de igualdad en ML es
polimrfico, y su tipo asociado es una funcin que recibe un producto de cualquier par de
tipos, iguales entre s, devolviendo un valor lgico. El tipo de datos al que se le puede apli-
car el operador binario = puede variar en cada invocacin; sin embargo, ambos operandos
han de ser del mismo tipo ya que en la expresin de tipo aparece la misma variable de
tipo, a.


69
En el lenguaje de programacin C++, las constantes 1.0 y 2.0 son ambas double. Para que sean
float hay que aadirles el sufijo f (o F); para que sean long double, el sufijo es l (o L).
70
Como el sistema interactivo Standard ML of New Jersey (SML/NJ).
Comprobacin de Tipos
111
El polimorfismo de tipos tambin se aplica al concepto de funcin y mtodo. Una
funcin o mtodo polimrfico es aqulla que es capaz de recibir y devolver parmetros de
cualquier tipo. En muchos lenguajes de programacin como C++, Eiffel o Ada esta
caracterstica es definida como genericidad
71
. El polimorfismo tambin es implementado
por lenguajes funcionales como ML, Haskell o Miranda.
El principal beneficio de la utilizacin de funciones polimrficas es que permiten
implementar algoritmos que manipulan estructuras de datos, independientemente del tipo
de elemento que contengan. De este modo, las implementaciones pueden desarrollarse de
un modo independiente al tipo, dejando al procesador de lenguaje la tarea de sustituir, en
cada invocacin, las variables de tipo por los tipos concretos.
Ejemplo 67. Supngase que, en el lenguaje de programacin C, se desea crear una funcin que
nos devuelva la longitud de una lista enlazada de enteros. Una implementacin sera la si-
guiente:

typedef struct s_l {
int informacion;
struct s_l *siguiente;
} lista;

unsigned longitud(lista *l) {
unsigned lon=0;
while (l) {
lon++;
l=l->siguiente;
}
return lon;
}
El principal problema de la funcin longitud es que depende del tipo a contener. En el
caso de que quisisemos calcular la longitud de una lista de reales, deberamos implementar
una nueva funcin puesto que el parmetro de la funcin posee por tipo una lista de ente-
ros. Es fcil apreciar cmo esta limitacin es demasiado fuerte, ya que el algoritmo en
ningn caso hace uso del tipo contenido por la lista; sin embargo, si variamos ste, el algo-
ritmo deja de ser vlido.
Una alternativa para el problema planteado es utilizar el tipo ms general que posee C:
void*. Recordemos que podemos convertir la direccin de cualquier tipo a void*. Sin
embargo, puesto que este tipo es tan genrico que no posee ninguna operacin, la recupe-
racin del tipo es una tarea no segura ( 6.7). Esto, adems del cdigo de conversin a aa-
dir, hace que los programas puedan tener errores en tiempo de ejecucin.
El lenguaje C++ aade la posibilidad de implementar estructuras y funciones polimrficas
(genricas), independientes del tipo. As, la siguiente versin del programa en C++ es segu-
ra respecto al tipo:

template<typename Tipo>
struct Lista {
int informacion;
Lista<Tipo> *siguiente;
};

template<typename Tipo>
unsigned longitud(Lista<Tipo> *l) {
unsigned lon=0;
while (l) {
lon++;
l=l->siguiente;

71
El trmino polimorfismo en estos lenguajes, como veremos, es aplicado a un polimorfismo restringido
basado en la herencia.
Anlisis Semntico en Procesadores de Lenguaje
112
}
return lon;
}
El cuantificador universal en C++ se define mediante identificadores dentro de una planti-
lla (template). Ntese cmo, en esta versin, la funcin longitud es vlida para cual-
quier lista, independientemente del tipo que contenga.

Un procesador de un lenguaje que ofrezca polimorfismo, deber implementar un
mecanismo de unificacin: proceso de encontrar una sustitucin para cada variable de tipo
con otra expresin de tipo, acorde al empleo del operador o funcin polimrfico. Puesto
que el operador & del lenguaje C posee la expresin de tipo:
. pointer()
Al utilizar este operador en la expresin &i, donde i es una variable entera, el algo-
ritmo de unificacin encontrar la sustitucin de por integer como vlida y, por tanto,
se determinar el tipo de la expresin &i como pointer(integer). Las variables de
tipo que reciben un valor tras aplicar una sustitucin, se dice que son instanciadas.
Un mecanismo de unificacin es una tcnica potente. Adems de su utilizacin en
la inferencia de tipos polimrficos [Milner78], desarrolla un papel fundamental en el mode-
lo computacional del lenguaje Prolog. Los algoritmos de unificacin tambin se emplean
para tcnicas de reconocimiento o emparejamiento de patrones
72
, ampliamente utilizados
en lenguajes como Perl o awk.
Cabe mencionar que en los lenguajes orientados a objetos, el trmino polimorfismo
suele emplearse para lo que realmente es polimorfismo de subtipos (herencia): poder refe-
rirse, mediante un supertipo, a cualquier tipo derivado. Puesto que todos los tipos deriva-
dos ofrecen las operaciones de un tipo base, el compilador acepta el tratamiento polimrfi-
co de objetos derivados mediante referencias a su supertipo. Ntese cmo esto es un sub-
conjunto de la amplitud del concepto de polimorfismo, que implica un tratamiento genri-
co para cualquier tipo no slo para las clases que heredan de una dada.
6.9. Inferencia de Tipos
Aunque ya hemos utilizado el concepto de inferencia de tipos a lo largo de todo es-
te libro, este trmino puede definirse como el problema de determinar el tipo de una cons-
truccin del lenguaje. sta es la tarea fundamental de un sistema de tipos. Hemos visto
cmo existen lenguajes con inferencia esttica de tipos (tiempo de compilacin) e inferencia
dinmica (tiempo de ejecucin).
Como hemos descrito a lo largo del punto 1 de este libro, la inferencia de tipos es
un problema de naturaleza compleja en el que se ha de tener en cuenta:
La representacin interna de las expresiones de tipo del lenguaje.
La de equivalencia de tipos que posea el lenguaje de programacin nominal,
declarativa, estructural, o una combinacin de stas.
Las distintas coerciones de tipos existentes en el lenguaje.

72
Pattern matching.
Comprobacin de Tipos
113
Las reglas que definen las conversiones explcitas no factibles
73
, de modifica-
cin de representacin interna, o de reinterpretacin de la informacin.
La especificacin de las distintas semnticas de los operadores sobrecargados,
as como el modo de resolver la sobrecarga de funciones y mtodos.
El modo en el que se pueden construir estructuras de datos y funciones poli-
mrficas, as como la especificacin del algoritmo de unificacin capaz de infe-
rir los tipos.
Adicionalmente a estas caractersticas, existen otras ms especficas de determina-
dos lenguajes, como las listas variables de argumentos o los parmetros por omisin
74
.


73
Por ejemplo, en el lenguaje de programacin Java, aunque se ahorme una expresin entera a otra lgi-
ca, esta conversin no se permite.
74
Ambos presentes en el lenguaje C++.
115
CUESTIONES DE REVISIN
1. Qu determina si una regla de un lenguaje es parte del anlisis semntico o
anlisis sintctico? Ponga un par de ejemplos para cada caso.
2. Defina semntica de un lenguaje de programacin. Enumere los distintos tipos
de lenguajes de especificacin semntica que conozca.
3. Por qu es imposible detectar ciertos errores en tiempo de compilacin? Ponga
tres ejemplos.
4. Qu significa anotar o decorar un rbol sintctico?
5. Defina y explique los conceptos de rbol sintctico, rbol sintctico abstracto,
sintaxis concreta y sintaxis abstracta de un lenguaje de programacin.
6. Qu es un atributo de un smbolo gramatical? Qu es una regla semntica?
7. Qu es una gramtica atribuida?
8. Indique las diferencias entre una gramtica atribuida y una definicin dirigida
por sintaxis.
9. Qu un atributo calculado en una produccin?
10. Cul es la diferencia entre un atributo heredado y uno sintetizado? Cmo se
representa dicha diferencia en un rbol sintctico?
11. Qu es un compilador de una pasada? Qu ventajas e inconvenientes ofrece
respecto a los compiladores de varias pasadas?
12. Indique qu es un grafo de dependencias de una gramtica atribuida.
13. Qu es un ordenamiento topolgico de un grafo de dependencias de una gra-
mtica atribuida? Cul es su principal utilidad?
14. Con qu tipo de recorrido de un AST puede evaluarse una gramtica S-
atribuida? Y una L-atribuida?
15. Indique las diferencias entre una definicin dirigida por sintaxis y un esquema
de traduccin.
16. Qu significa que una gramtica sea S-atribuida y L-atribuida? Qu implica y
por qu es una faceta importante?
17. Qu es una gramtica atribuida completa? Qu relacin existe con el concepto
de gramtica atribuida bien definida? Por qu es un concepto importante?
18. Cul es el significado de evaluar una gramtica?
19. Indique, adems las gramticas atribuidas S y L-atribuidas, cules pueden ser
evaluadas, a partir de su rbol sintctico, con una nica visita de cada uno de sus
nodos.
20. Indique qu tipo de recorrido ha de efectuarse sobre un AST para poder evaluar
una gramtica L-atribuida visitando una sola vez cada nodo del rbol.
21. Es posible convertir una gramtica L-atribuida a S-atribuida? Y en el sentido
contrario? Razone ambas respuestas.
Anlisis Semntico en Procesadores de Lenguaje
116
22. Indique cuales son los dos mtodos principales empleados para evaluar una
gramtica atribuida. Explique ambos.
23. Cul es la diferencia entre una regla semntica y una rutina semntica?
24. Algunos compiladores realizan el anlisis semntico al mismo tiempo que el sin-
tctico. Otros, en fase de anlisis sintctico, crean un AST y posteriormente rea-
lizan las comprobaciones semnticas. Indique las ventajas e inconvenientes de
ambas alternativas.
25. Bajo qu circunstancias puede calcularse un atributo heredado en una herra-
mienta de anlisis sintctico ascendente?
26. Cules son los principales objetivos de la existencia de tipos en los lenguajes de
programacin?
27. De cuntas formas distintas podra definirse el concepto de tipo?
28. Defina comprobacin de tipos esttica y dinmica. Indique dos lenguajes que
posea cada una de ellas.
29. Ponga un ejemplo de dos comprobaciones dinmicas de tipo que realice un len-
guaje que usted conozca.
30. Cul es la diferencia entre equivalencia de tipos y compatibilidad de tipos?
Ponga dos ejemplos.
31. Defina y relacione expresin de tipo, sistema de tipos y comprobador de tipos.
32. Explique las diferencias surgidas a la hora de representar expresiones de tipo
mediante estructuras de rboles o grafos acclicos dirigidos.
33. Qu significa que un lenguaje sea fuerte respecto al tipo? Y que sea seguro?
Cite caractersticas del lenguaje C que hacen que no sea seguro.
34. Qu relacin existe entre el concepto de lenguaje seguro, comprobacin estti-
ca de tipos y comprobacin dinmica de tipos?
35. Cmo suelen gestionar en tiempo de ejecucin los errores de tipo los lenguajes
seguros?
36. Qu es la coercin y conversin de tipos?
37. Qu significa que un smbolo est sobrecargado?
38. Defina polimorfismo de tipos. Dnde se suele dar en los lenguajes de progra-
macin. Ponga dos ejemplos de cada caso.
39. Defina y explique los distintos tipos de equivalencia de tipos que conoce.
40. Qu relacin existe entre la sobrecarga de operadores y las coerciones de un
lenguaje de programacin?
41. Indique las diferencias existentes entre un operador polimrfico y un sobrecar-
gado. Ejemplifquelo con dos casos.
42. Cite complejidades del problema de la inferencia de tipos en un lenguaje como
el C++?
43. Qu es un algoritmo de unificacin? En qu lenguajes se emplea?
44. Cite las caractersticas polimrficas del lenguaje C++.

117
EJERCICIOS PROPUESTOS
1. Dada la sentencia 3*(61+-3), vlida para la gramtica atribuida del Ejemplo 11,
muestre el rbol sintctico decorado y un AST alternativo. Cul sera su sintaxis
abstracta?
2. Ample la gramtica atribuida del Ejemplo 14 para que sea capaz de utilizar todas
las bases de 2 a 16. La base se pospondr al nmero entre llaves. Por defecto ser
base 10. Ejemplos 0110{2}, 123{10}, af45{16}, 981.
3. Respecto a la gramtica atribuida del Ejemplo 14. Puede evaluarse con una nica
pasada, visitando una nica vez cada nodo del rbol sintctico? Si optamos por
hacer un compilador de varias pasadas, se podra decorar su AST con una sola visi-
ta de sus nodos?
4. Implemntese, mediante el patrn de diseo Visitor, un evaluador de la gramtica
del Ejemplo 14.
5. Implemente una gramtica atribuida capaz de traducir expresiones infijas a expre-
siones prefijas. Por ejemplo, traducir de a/2+10*c a + / a 2 * 10 c.
6. Escriba una gramtica atribuida para calcula el valor real del nmero descrito por el
no terminal real de la siguiente gramtica:
real num . num
num num digito
| digito
digito 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
7. Dada la siguiente gramtica:
arbolbin ( cte_entera arbolbin arbolbin )
| nil
Escriba una gramtica atribuida para comprobar si el rbol binario es de bs-
queda. Como ejemplo, (2 (1 nil nil) (3 nil nil)) es un rbol de
bsqueda, pero no lo es (2 (0 nil nil) (1 nil nil)).
8. Dada la siguiente gramtica atribuida GA={G,A,R,B} siendo G y R:

P G
(1)
S
Logico MasLogico
(2)
MasLogico
1

AND Logico MasLogico
2

(3)
MasLogico
1

OR Logico MasLogico
2

(4)
MasLogico
(5)
Logico
Termino
1
+ Termino
2

(6)
Logico
Termino
1
Termino
2

(7)
Logico
Termino
(8)
Termino
CTE_ENTERA
(9)
Termino
CTE_REAL

P R
(1) S.tipo = MasLogico.tipo
MasLogico.inicial = Logico.tipo
(2) MasLogico
2
.inicial = Logico.tipo
MasLogico
1
.tipo = ENTERO
Anlisis Semntico en Procesadores de Lenguaje
118
P R
(3) MasLogico
2
.inicial = Logico.tipo
MasLogico
1
.tipo = ENTERO
(4) MasLogico.tipo = MasLogico.inicial
(5) Logico.tipo = MayorTipo(Termino
1
.tipo,Termino
2
.tipo)
(6) Logico.tipo = MayorTipo(Termino
1
.tipo,Termino
1
.tipo)
(7)
(8) Termino.tipo = ENTERO
(9) Termino.tipo = REAL


Tipo MayorTipo(Tipo t1,Tipo t2){
if (t1==REAL) || (t2===REAL) return REAL;
return ENTERO;
}
y siendo B:

P B
(2) MasLogico
1
.inicial==ENTERO
Logico.tipo==ENTERO
(3) MasLogico
1
.inicial==ENTERO
Logico.tipo==ENTERO
Es la gramtica GA una gramtica S-atribuida? Es la gramtica GA una gram-
tica L-atribuida? Pertenece la sentencia 3+4.5 al lenguaje definido por la gra-
mtica GA? Pertenece la sentencia 3-2 OR 3+2.1 al lenguaje definido por la
gramtica GA? Es la gramtica GA una gramtica atribuida completa? Est
GA bien definida?
9. Muestre el grafo de dependencias del ejercicio anterior.
10. Dada la siguiente gramtica G libre de contexto:

P G
(1)
S
logico masLogico
(2)
logico
implica masImplica
(3)
implica
true
(4)
implica
false
(5)
masLogico
1

AND logico masLogico
2

(6)
masLogico
1

OR logico masLogico
2

(7)
masLogico
(8)
masImplica
1

=> implica masImplica
2

(9)
masImplica
Siendo la asociatividad de los tres operadores a izquierdas, defina una gramtica
atribuida que reconozca como sentencias pertenecientes a su lenguaje nicamen-
te aquellas expresiones que sean evaluadas como ciertas. Evale la gramtica an-
te la entrada:
false OR true AND false OR true => false
Pertenece el programa anterior al lenguaje definido por la gramtica atribuida?
11. Indique un ordenamiento topolgico del grafo de dependencias del Ejemplo 15 y
un orden de ejecucin de sus reglas semnticas.
12. Considere la siguiente gramtica
[1] <L> <M> <L> b
[2] | a
[3] <M> |
Ejercicios Propuestos
119
En qu orden se podran evaluar sus reglas semnticas asociadas en el caso de
tenerlas en una nica pasada con un analizador sintctico ascendente?
13. Dada la siguiente gramtica libre de contexto:
[1] <S> <Elemento> <Elementos>
[2] |
[3] <Elemento> A
[4] | B
[5] <Elementos> <Elemento> <Elementos>
[6] |
Especifquese sobre ella una gramtica atribuida bien definida que restrinja el
lenguaje reconocido a aquellos programas que posean igual nmero de tokens A
y B. Demustrese su condicin de gramtica completa.
14. Indique una representacin de las expresiones de tipo de las clases del Pascal para
los cuatro puntos que hemos identificado en el Ejemplo 41.
15. Dado el siguiente cdigo C:

typedef struct {
int a, b;
} nodo, *ptr_nodo;
nodo aa[100];
ptr_nodo bb(int x, nodo y);
Escriba las expresiones de tipo para aa y bb.
16. Muestre la expresin de tipo de una funcin que tome como argumento un array
indexado por enteros, con elementos de cualquier tipo, y devuelva un array cuyos
elementos sean los elementos apuntados por los elementos del array dado.
17. Indique cmo los mtodos de clase (static) y los mtodos abstractos deberan
ser tenidos en cuenta en la fase de anlisis semntico de un procesador de lenguaje.
18. Identifique una expresin de tipo necesaria para llevar las comprobaciones de tipo
del identificador miFuncion, para el siguiente cdigo C++:
template <class T> T *miFuncion(T *t) { return *t; }
19. Implemente el Ejemplo 41, creando las expresiones de tipo mediante un DAG en
lugar de empleando un rbol.
20. Identifique las expresiones de tipo necesarias y escriba una definicin dirigida por
sintaxis para realizar un comprobador de tipos de comparaciones de igualdad de un
pseudopascal con las siguientes caractersticas:
Tipos bsicos char e integer y sus correspondientes constantes.
Constructores de tipo array y .
Se permiten comparaciones de punteros si son de igual tipo.
Se permiten comparaciones de arrays siempre que tengan igual rango y tipo.
Los subrangos de los arrays sern nmeros enteros (no caracteres).
Utilice para ello un objeto tablaSimbolos con los mtodos insertar y
buscar. El siguiente es un programa correcto:

VAR
vector:array[1..10] of integer;
puntero:^integer;
pDoble:^^integer;
v:^array[1..10] of ^char;
Anlisis Semntico en Procesadores de Lenguaje
120
w:array[1..10] of ^char;
BEGIN
45=vector[3];
pDoble^^=puntero^;
puntero=pDoble^;
puntero^=vector[puntero^];
v^[puntero^]^='3';
v^=w;
END.
Los siguientes son programas semnticamente incorrectos:

VAR
BEGIN
'3'=3;
END.
VAR
pChar:^char;
pInt:^integer;
BEGIN
pChar=pInt;
END.
VAR
puntero:^integer;
pDoble:^^integer;
BEGIN
puntero=pDoble;
END.
VAR
v:ARRAY[1..10] OF char;
BEGIN
v['a']='a';
END.
VAR
v:ARRAY[1..10] OF char;
BEGIN
v[3]=3;
END.
VAR
v:ARRAY[1..10] OF char;
w:ARRAY[1..10] OF integer;
BEGIN
v=w;
END.
VAR
v:ARRAY[1..10] OF char;
w:ARRAY[1..11] OF char;
BEGIN
v=w;
END.
21. Implemente, mediante el patrn de diseo Composite y comprobador de tipos equi-
valente a la definicin dirigida por sintaxis del ejercicio anterior.
22. Dada la siguiente gramtica:
<S> <var> = <exp>
<var> ID
<exp> <term> <masTerm>
<term> <fact> <masFact>
<masTerm> + <term> <masTerm>
| - <term> <masTerm>
|
<fact> CTE_ENTERA
| CTE_REAL
| ( <exp> )
<masFact> * <fact> <masFact>
| / <fact> <masFact>
|
Disee sobre ella una definicin dirigida por sintaxis para que el atributo
var.tipo infiera el tipo adecuado, entero o real, en la asignacin. Adems, el
atributo var.valor ha de poseer la evaluacin de la expresin asignada, si-
guiendo las precedencias tpicas en los lenguajes de programacin.
23. Supngase las declaraciones generadas por la siguiente gramtica:

(1) D

id L
(2) L

, id L
(3) L

: T
(4) T

integer
(5) T

real
Ejercicios Propuestos
121
Construya un esquema de traduccin para introducir el tipo de cada identifica-
dor en una tabla de smbolos.
24. La siguiente expresin en el lenguaje C:
(t)-x
Puede significar dos cosas. Si t es un tipo (definido mediante typedef), se est
ahormando el valor de la expresin -x al tipo. Tambin puede tratarse de que t
sea otra variable y, simplemente, tengamos una resta.
Describa cmo un analizador sintctico puede resolver estas dos posibles inter-
pretaciones. Tiene sentido que el analizador semntico emplee en analizador
lxico para resolver este problema?

123
A Evaluacin de un AST
En este apndice se muestra el cdigo fuente del Ejemplo 7, en el que se creaba un
AST de una expresin aritmtica. El rbol se construa por medio de una especificacin
yacc/bison. Puesto que en el Ejemplo 29 se ampliaba el AST para implementar un proce-
sador de lenguaje de mltiples pasadas, nos limitaremos a mostrar la segunda versin algo
mayor que la presentada en el Ejemplo 7.
El lenguaje de programacin es C++.
A.1 Implementacin del AST
ast.h
#ifndef _ast_h
#define _ast_h

#include "visitor.h"
#include <iostream>
#include <sstream>
using namespace std;

class Expresion {
public:
virtual ~Expresion() {}
virtual void aceptar(Visitor*) = 0;
// * Atributos
double valor;
char tipo;
ostringstream codigo;
};

class ExpresionUnaria: public Expresion {
Expresion *operando;
char operador;
ExpresionUnaria(ExpresionUnaria&) {}
ExpresionUnaria &operator=(ExpresionUnaria&) {return *this;}
public:
ExpresionUnaria(char operador,Expresion *operando) {
this->operador=operador;
this->operando=operando;
}
~ExpresionUnaria() { delete operando; }
virtual void aceptar(Visitor *v) {v->visitar(this);}
char getOperador() const { return operador; }
Expresion *getOperando() { return operando; }
};


class ExpresionBinaria: public Expresion {
Expresion *operando1,*operando2;
char operador;
ExpresionBinaria(ExpresionBinaria&) {}
ExpresionBinaria &operator=(ExpresionBinaria&) {return *this;}
public:
ExpresionBinaria(char operador,Expresion *operando1,Expresion *operando2) {
this->operador=operador;
this->operando1=operando1;
this->operando2=operando2;
}
~ExpresionBinaria() { delete operando1; delete operando2; }
virtual void aceptar(Visitor *v) {v->visitar(this);}
char getOperador() const { return operador; }
Expresion *getOperando1() { return operando1; }
Expresion *getOperando2() { return operando2; }
};

Anlisis Semntico en Procesadores de Lenguaje
124
class ConstanteEntera: public Expresion {
public:
ConstanteEntera(int v) {valor=v;}
virtual void aceptar(Visitor *v) {v->visitar(this);}
};

class ConstanteReal: public Expresion {
public:
ConstanteReal(double v) {valor=v;}
virtual void aceptar(Visitor *v) {v->visitar(this);}
};

#endif
A.2 Visitas del AST
En este punto mostraremos cmo, mediante la utilizacin del patrn de diseo Vi-
sitor, es posible separar cada una de las visitas del AST de la representacin del rbol. Las
distintas visitas sern presentadas siguiendo el siguiente orden: semntico, generacin de
cdigo, clculo y mostrar (traza). Para cada una de ellas se mostrar la declaracin e imple-
mentacin de la clase. Comenzaremos con la clase abstracta Visitor.
visitor.h
#ifndef _visitor_h
#define _visitor_h

class ExpresionUnaria;
class ExpresionBinaria;
class ConstanteEntera;
class ConstanteReal;
class Visitor {
public:
virtual void visitar(ExpresionUnaria *) = 0;
virtual void visitar(ExpresionBinaria *) = 0;
virtual void visitar(ConstanteEntera *) = 0;
virtual void visitar(ConstanteReal *) = 0;
};

#endif
visitorsemantico.h
#ifndef _visitorsemantico_h
#define _visitorsemantico_h

#include "ast.h"

class VisitorSemantico: public Visitor{
static char tipoMayor(char tipo1,char tipo2);
public:
void visitar(ExpresionUnaria *) ;
void visitar(ExpresionBinaria *);
void visitar(ConstanteEntera *) ;
void visitar(ConstanteReal *) ;
};

#endif
visitorsemantico.cpp
#include "visitorsemantico.h"

char VisitorSemantico::tipoMayor(char tipo1,char tipo2) {
// 'E' -> error
// 'I' -> entero
// 'F' -> real
if (tipo1=='E'||tipo2=='E') return 'E';
if (tipo1=='F'||tipo2=='F') return 'F';
return 'I';
}

void VisitorSemantico::visitar(ExpresionUnaria *e) {
// * Calculo el tipo del operando
e->getOperando()->aceptar(this);
// * Calculo el tipo del nodo
e->tipo=e->getOperando()->tipo;
}

Evaluacin de un AST
125
void VisitorSemantico::visitar(ExpresionBinaria *e) {
// * Calculo el tipo de los operandos
e->getOperando1()->aceptar(this);
e->getOperando2()->aceptar(this);
// * Calculo el tipo del nodo
e->tipo=tipoMayor(e->getOperando1()->tipo,e->getOperando2()->tipo);
// * Error semntico
if (e->getOperador()=='=' &&
e->getOperando1()->tipo=='I' && e->getOperando2()->tipo=='F')
e->tipo='E';
}

void VisitorSemantico::visitar(ConstanteEntera *c) {
c->tipo='I';
}

void VisitorSemantico::visitar(ConstanteReal *c) {
c->tipo='F';
}
visitorgc.h
#ifndef _visitorgc_h
#define _visitorgc_h

#include "ast.h"

class VisitorGC: public Visitor{
public:
void visitar(ExpresionUnaria *) ;
void visitar(ExpresionBinaria *);
void visitar(ConstanteEntera *) ;
void visitar(ConstanteReal *) ;
};

#endif
visitorgc.cpp
#include "visitorgc.h"
#include <cassert>

void VisitorGC::visitar(ExpresionUnaria *e) {
assert(e->getOperador()=='-');
// * Genero el cdigo del operando
e->getOperando()->aceptar(this);
e->codigo<<e->getOperando()->codigo.str();
// * Genero el cdigo de negacin
e->codigo<<"\t"<<e->tipo<<"NEG\n"; // * Instruccin INEG o FNEG
}

void VisitorGC::visitar(ExpresionBinaria *e) {
// * Cdigo para apilar el primer operando
e->getOperando1()->aceptar(this);
e->codigo<<e->getOperando1()->codigo.str();
// * Es necesario convertir el op1 de entero a real?
if (e->getOperando1()->tipo=='I' && e->tipo=='F' )
e->codigo<<"\tITOF\n"; // * Integer to Float
// * Cdigo para apilar el segundo operando
e->getOperando2()->aceptar(this);
e->codigo<<e->getOperando2()->codigo.str();
// * Es necesario convertir el op2 de entero a real?
if (e->getOperando2()->tipo=='I' && e->tipo=='F' )
e->codigo<<"\tITOF\n"; // * Integer to Float
// * Tipo del operador
e->codigo<<'\t'<<e->tipo; // I o F
// * Operador
switch (e->getOperador()){
case '+': e->codigo<<"ADD\n"; break;
case '-': e->codigo<<"SUB\n"; break;
case '*': e->codigo<<"MUL\n"; break;
case '/': e->codigo<<"DIV\n"; break;
case '=': e->codigo<<"STORE\n"; break;
default: assert(0);
}
if (e->getOperando1()->tipo=='I' && e->getOperando2()->tipo=='I')
e->valor=(int)e->valor;
}

void VisitorGC::visitar(ConstanteEntera *c) {
// * Apila un entero
c->codigo<<"\tPUSHI\t"<<(int)(c->valor)<<"\n";
}

void VisitorGC::visitar(ConstanteReal *c) {
// * Apila un real
Anlisis Semntico en Procesadores de Lenguaje
126
c->codigo<<"\tPUSHF\t"<<c->valor<<"\n";
}
visitorcalculo.h
#ifndef _visitorcalculo_h
#define _visitorcalculo_h

#include "ast.h"

class VisitorCalculo: public Visitor{
public:
void visitar(ExpresionUnaria *) ;
void visitar(ExpresionBinaria *);
void visitar(ConstanteEntera *) ;
void visitar(ConstanteReal *) ;
};

#endif
visitorcalculo.cpp
#include "visitorcalculo.h"
#include <cassert>

void VisitorCalculo::visitar(ExpresionUnaria *e) {
assert(e->getOperador()=='-');
// * Calculo el valor del operando
e->getOperando()->aceptar(this);
e->valor= - e->getOperando()->valor;
}

void VisitorCalculo::visitar(ExpresionBinaria *e) {
// * Calculo el valor de los operandos
e->getOperando1()->aceptar(this);
e->getOperando2()->aceptar(this);
// * Calculo el valor del nodo
switch (e->getOperador()){
case '+': e->valor=e->getOperando1()->valor+e->getOperando2()->valor; break;
case '-': e->valor=e->getOperando1()->valor-e->getOperando2()->valor; break;
case '*': e->valor=e->getOperando1()->valor*e->getOperando2()->valor; break;
case '/': e->valor=e->getOperando1()->valor/e->getOperando2()->valor; break;
case '=': e->valor=e->getOperando2()->valor; break;
default: assert(0);
}
if (e->getOperando1()->tipo=='I' && e->getOperando2()->tipo=='I')
e->valor=(int)e->valor;
}

void VisitorCalculo::visitar(ConstanteEntera *c) {}

void VisitorCalculo::visitar(ConstanteReal *c) {}
visitormostrar.h
#ifndef _visitormostrar_h
#define _visitormostrar_h

#include "ast.h"
#include <iostream>

class VisitorMostrar: public Visitor{
std::ostream &flujo;
public:
VisitorMostrar(std::ostream &f):flujo(f) {}
void visitar(ExpresionUnaria *) ;
void visitar(ExpresionBinaria *);
void visitar(ConstanteEntera *) ;
void visitar(ConstanteReal *) ;
};

#endif
visitormostrar.cpp
#include "visitormostrar.h"
#include <iostream>

using namespace std;

void VisitorMostrar::visitar(ExpresionUnaria *e) {
flujo<<"( "<<e->getOperador()<<' ';
e->getOperando()->aceptar(this);
flujo<<" )";
}
Evaluacin de un AST
127

void VisitorMostrar::visitar(ExpresionBinaria *e){
flujo<<"( "<<e->getOperador()<<' ';
e->getOperando1()->aceptar(this);
flujo<<' ';
e->getOperando2()->aceptar(this);
flujo<<" )";
}

void VisitorMostrar::visitar(ConstanteEntera *c) {
flujo<<(int)(c->valor);
}

void VisitorMostrar::visitar(ConstanteReal *c) {
flujo<<c->valor;
}
A.3 Especificacin Lxica y Sintctica del Lenguaje
Para finalizar el ejemplo, se muestra la especificacin lxica y sintctica realizada
con las herramientas plclex y pcyacc.
sintac.y
%{
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ast.h"
int yyparse();
int yylex();
Expresion *ast;
%}
%union {
int entero;
double real;
Expresion *expresion;
}
%right '='
%left '+' '-'
%left '*' '/'
%right MENOS_UNARIO
%nonassoc '(' ')'

%token <entero> CTE_ENTERA
%token <real> CTE_REAL
%token MENOS_UNARIO
%type <expresion> expresion
%%
programa: expresion { ast=$1;}
;
expresion: expresion '+' expresion {$$=new ExpresionBinaria('+',$1,$3); }
| expresion '-' expresion {$$=new ExpresionBinaria('-',$1,$3); }
| expresion '*' expresion {$$=new ExpresionBinaria('*',$1,$3); }
| expresion '/' expresion {$$=new ExpresionBinaria('/',$1,$3); }
| expresion '=' expresion {$$=new ExpresionBinaria('=',$1,$3); }
| '-' expresion %prec MENOS_UNARIO {$$=new ExpresionUnaria('-',$2); }
| '(' expresion ')' { $$=$2; }
| CTE_ENTERA { $$=new ConstanteEntera($1); }
| CTE_REAL { $$=new ConstanteReal($1); }
;
%%
void yyerror(char *s) {
printf(s);
}

#include "visitorsemantico.h"
#include "visitorgc.h"
#include "visitorcalculo.h"
#include "visitormostrar.h"
#include <iostream>
using namespace std;

int main() {
yyparse();
VisitorSemantico semantico;
ast->aceptar(&semantico);
if (ast->tipo=='E')
cerr<<"El programa no es semnticamente vlido."<<endl;
else {
cout<<"\nAnlisis semntico finalizado correctamente.\n";
VisitorCalculo calculo;
Anlisis Semntico en Procesadores de Lenguaje
128
ast->aceptar(&calculo);
cout<<"\nValor de la expresin: "<<ast->valor<<endl;
VisitorGC gc;
ast->aceptar(&gc);
cout<<"\nCdigo generado:\n"<<ast->codigo.str()<<endl;
}
VisitorMostrar traza(cout);
cout<<"\nAST generado:\n";
ast->aceptar(&traza);
cout<<endl;
delete ast;
}
lexico.l
%{
#include <string.h>
#include <stdlib.h>
#include "ast.h"
#include "yytab.h"

unsigned yylineno=1;
void errorLexico(char);
void inicializarLexico(char *,char *);
%}
comentario \/\/.*\n
linea \n
letra [a-zA-Z]
numero [0-9]
real {numero}+(\.{numero}+)?
alfa [{numero}{letra}]
read [Rr][Ee][Aa][Dd]
write [Ww][Rr][Ii][Tt][Ee]
main [Mm][Aa][Ii][Nn]
integer [Ii][Nn][Tt][Ee][Gg][Ee][Rr]
%%
[ \t]+ ; // * Se ignoran los espacions y tabuladores
{comentario} { yylineno++; }
{linea} { yylineno++; }
{numero}+ { yylval.entero=atoi(yytext); return CTE_ENTERA; }
{real}([Ee][\+\-]?{numero}+)? { yylval.real=atof(yytext); return CTE_REAL; }
\+ |
\- |
\* |
\/ |
\( |
\) |
% |
= |
\} |
\{ |
, |
; { return yytext[0]; } // Token=Caracter ASCII
. { errorLexico(yytext[0]); } // Caracter no perteneciente al lenguaje
%%

void errorLexico(char c) {
printf("Error lexico en linea %d. Caracter %c no perteneciente al lenguaje.\n",
yylineno,c);
exit(-1);
}

void inicializarLexico(char *fOrigen,char *fDestino) {
yyin=fopen(fOrigen,"r");
if (yyin==NULL) {
printf("Error abriendo el fichero %s.\n",fOrigen);
exit(-1);
}
yyout=fopen(fDestino,"w");
if (yyout==NULL) {
printf("Error abriendo el fichero %s.\n",fDestino);
exit(-1);
}
}
129
B Evaluacin de una Gramtica L-Atribuida
mediante un Analizador Descendente Recursivo
En el Ejemplo 28 se present un mecanismo para poder evaluar una gramtica L-
atribuida en una nica pasada. Para ello, se estableca un esquema de traduccin de las re-
glas semnticas a cdigo. La ejecucin de las reglas se procesaba al mismo tiempo que la
tarea de reconocer sintcticamente el lenguaje, mediante un analizador descendente recursi-
vo predictivo. La gramtica del ejemplo es LL1, resultado muy sencillo el desarrollo del
analizador sintctico.
La implementacin se ha realizado en el lenguaje de programacin Java, distribu-
yndose en tres paquetes: sintctico, lxico y errores. ste ser el orden en el que se presen-
tar el cdigo fuente de cada uno de los mdulos.
B.1 Mdulo Sintctico
Sintactico.java
package latribuida.sintactico;

/**
* <p>Title: Implementancin LAtribuida</p>
* <p>Description: Ejemplo de Implementacion de un Evaluador Descendente Recursivo
* LL1, sobre una gramtica LAtribuida</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: Universidad de Oviedo</p>
* @author Francisco Ortn
* @version 1.0
*/

import latribuida.lexico.*;

/** Clase que ofrece toda la funcionalidad del analizador sintctico */
public class Sintactico {
/** Analizador lxico */
private Lexico lexico;

/** Constructor con el archivo de entrada */
public Sintactico(String nombreFichero) {
lexico=new Lexico(nombreFichero);
}

/** Constructor con entrada estndar */
public Sintactico() { lexico=new Lexico(); }

/** Mtodo que reconoce sintcticamente el no terminal "expresion".<br/>
* Produccin: expresion -> terminos masTerminos<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (ninguno) y
* devuelve los sintetizados (expresion.valor)<br/>
*/
public int expresion() {
// * Regla: masTerminos.operando1 = termino.valor
int masTerminosOperando1=termino();
// expresion.valor = masTerminos.valor
return masTerminos(masTerminosOperando1);
}

/** Mtodo que reconoce sintcticamente el no terminal "masTerminos".<br/>
* Produccion: masTerminos1 -> '+' termino masTerminos2<br/>
* Produccion: masTerminos1 -> '-' termino masTerminos2<br/>
* Produccion: masTerminos1 -> lambda<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (masTerminos.operando1)
Anlisis Semntico en Procesadores de Lenguaje
130
* y devuelve los sintetizados (masTerminos.valor)<br/>
*/
private int masTerminos(int operando1) {
int token=lexico.getToken();
switch (token) {
case '+': lexico.match('+');
// * Regla: masTerminos2.operando1=masTerminos1.operando1+termino.valor
int masTerminos2Operando1=masTerminos(operando1+termino());
// * Regla: masTerminos1.valor = masTerminos2.valor
return masTerminos2Operando1;
case '-': lexico.match('-');
// * Regla: masTerminos2.operando1=masTerminos1.operando1-termino.valor
masTerminos2Operando1=masTerminos(operando1-termino());
// * Regla: masTerminos1.valor = masTerminos2.valor
return masTerminos2Operando1;
default: // * lambda
// * Regla: masTerminos1.valor = masTerminos1.operando1
return operando1;
}
}

/** Mtodo que reconoce sintcticamente el no terminal "termino".<br/>
* Produccion: termino -> factor masFactores<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (ninguno)
* y devuelve los sintetizados (termino.valor)<br/>
*/
private int termino() {
// * Regla: masFactores.operando1 = factor.valor
int masFactoresOperando1=factor();
// * Regla: termino.valor = masFactores.valor
return masFactores(masFactoresOperando1);
}

/** Mtodo que reconoce sintcticamente el no terminal "masFactores".<br/>
* Produccion: masFactores1 -> '*' factor masFactores2<br/>
* Produccion: masFactores1 -> '/' factor masFactores2<br/>
* Produccion: masFactores1 -> lambda<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (masFactores.operando1)
* y devuelve los sintetizados (masFactores.valor)<br/>
*/
private int masFactores(int operando1) {
int token=lexico.getToken();
switch (token) {
case '*': lexico.match('*');
// * Regla: masFactores2.operando1=masmasFactores1.operando1+factor.valor
int masFactores2Operando1=masFactores(operando1*factor());
// * Regla: masFactores1.valor = masFactores2.valor
return masFactores2Operando1;
case '/': lexico.match('/');
// * Regla: masFactores2.operando1=masFactores1.operando1-factor.valor
masFactores2Operando1=masFactores(operando1/factor());
// * Regla: masFactores1.valor = masFactores2.valor
return masFactores2Operando1;
default: // * lambda
// * Regla: masFactores1.valor = masFactores1.operando1
return operando1;
}
}

/** Mtodo que reconoce sintcticamente el no terminal "factor".<br/>
* Produccion: factor -> CTE_ENTERA<br/>
* Adems evala los atributos calculados en las reglas semnticas
* de esta produccin. Recibe los atributos heredados (ninguno)
* y devuelve los sintetizados (factor.valor)<br/>
*/
private int factor() {
int cteEnteraValor=lexico.getYYlval().entero;
lexico.match(lexico.CTE_ENTERA);
// * Regla: factor.valor = CTE_ENTERA.valor
return cteEnteraValor;
}

/** Entrada del programa.<br/>
* Sin argumentos: recibe la sentencia por la entrada estndar.
* Un argumento: archivo de entrada del evaluador.
*/

public static void main(String[] args) {
Sintactico sintactico=null;
if (args.length>=1) sintactico=new Sintactico(args[0]);
else sintactico=new Sintactico();
// * Nos limitamos a llamar al smbolo inical
System.out.println("Valor de la expresin: "+
sintactico.expresion() );
}
Evaluacin Descendente de una Gramtica L-Atribuida
131
}
B.2 Mdulo Lxico
Atributo.java
package latribuida.lexico;

/**
* <p>Title: Implementancin LAtribuida</p>
* <p>Description: Ejemplo de Implementacion de un Evaluador Descendente Recursivo LL1,
sobre una gramtica LAtribuida</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: Universidad de Oviedo</p>
* @author Francisco Ortn
* @version 1.0
*/

/** Distintos atributos de los smbolos terminales */
public class Atributo {
public final int entero;
public final char carcter;
public Atributo(int entero) { this.entero=entero; carcter='?'; }
public Atributo(char carcter) { this.carcter=carcter; entero=-1; }
}
Lexico.java
package latribuida.lexico;

/**
* <p>Title: Implementancin LAtribuida</p>
* <p>Description: Ejemplo de Implementacion de un Evaluador Descendente Recursivo LL1,
sobre una gramtica LAtribuida</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: Universidad de Oviedo</p>
* @author Francisco Ortn
* @version 1.0
*/

import java.io.*;

/** Clase que ofrece toda la funcionalidad del analizador lxico */
public class Lexico {
/** Flujo de entrada */
private PushbackReader in;
/** Nmero de lnea */
private int yylineno=1;
/** Nmero de lnea */
public int getYYlineno() {return yylineno; }
/** Tokens */
public static final int CTE_ENTERA=256;
/** Valor semntico del token */
private Atributo yylval;
/** Atributo del token */
public Atributo getYYlval() { return yylval; }

/** Atributo que indica el token actual */
private int token;
/** Mtodo que devuelve el token actual */
public int getToken() { return token; }
/** Mtodo que comprueba que el siguiente token es el pasado. Si as
* es, avanza el token actual al siguiente. */
public void match(int token) {
if (this.token==token) {
// * Avanzamos
this.token=yylex();
return;
}
latribuida.errores.Error.esperaba(yylineno,token,this.token);
}

/** Construye un analizador lxico sobre la entrada estndar */
public Lexico() {
in=new PushbackReader(new InputStreamReader(System.in));
token=yylex(); // * Leemos el primer token
}

/** Construye un analizador lxico sobre el nombre del fichero pasado */
public Lexico(String fichero) {
try {
this.in=new PushbackReader(new FileReader(fichero));
token=yylex(); // * Leemos el primer token
Anlisis Semntico en Procesadores de Lenguaje
132
} catch(Exception e) {
System.err.println("El fichero "+fichero+" no se encuentra.");
e.printStackTrace();
System.exit(-1);
}
}

private int getCar() {
int ch=0;
try { ch=in.read(); }
catch (Exception e) {
System.err.println("No se puede leer de disco.\n");
e.printStackTrace();
System.exit(-2);
}
if (ch=='\n') yylineno++;
return ch;
}

private void putCar(int c) {
try { in.unread(c); }
catch (Exception e) {
System.err.println("Buffer lleno.\n");
e.printStackTrace();
System.exit(-3);
}
if (c=='\n') yylineno--;
}

/** Mtodo que devuelve el siguiente token.<br/>
* Este mtodo es privado. El analizador sintctico deber utilizar los
* mtodos getToken y match. */
private int yylex() {
int ch=0;
ch=getCar();
// * Basura
while (ch==' '||ch=='\t'||ch=='\n'||ch=='\r') ch=getCar();
// * Nmero
if (ch>='0'&&ch<='9') {
StringBuffer s=new StringBuffer();
while (ch>='0'&&ch<='9') {
s.append((char)ch);
ch=getCar();
}
putCar(ch);
yylval=new Atributo( Integer.parseInt(s.toString()) );
return CTE_ENTERA;
}
// * Cualquier otro carcter
yylval=new Atributo((char)ch);
return ch;
}


/** Mtodo de prueba de la clase */
public static void main(String[] args) {
Lexico lexico=null;
if (args.length>=1) lexico = new Lexico();
else lexico=new Lexico();
int token;
while ((token=lexico.yylex()) != -1 ) {
System.out.println("Nmero de lnea: "+lexico.getYYlineno());
System.out.println("\tNmero de token: "+token);
System.out.print("\tAtributo:");
if (token==CTE_ENTERA)
System.out.println(lexico.getYYlval().entero);
else System.out.println(lexico.getYYlval().carcter);
}
}
}
B.3 Mdulo Errores
Error.java
package latribuida.errores;

/**
* <p>Title: Implementancin LAtribuida</p>
* <p>Description: Ejemplo de Implementacion de un Evaluador Descendente Recursivo LL1,
sobre una gramtica LAtribuida</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: Universidad de Oviedo</p>
Evaluacin Descendente de una Gramtica L-Atribuida
133
* @author Francisco Ortn
* @version 1.0
*/

import latribuida.lexico.Lexico;
/** Clase que gestiona el mdulo gestor de errores */
public class Error {

/** Mensaje que muestra que esperaba otro terminal en lugar de otro recibido */
static public void esperaba(int nLnea, int tokenEsperado, int tokenRecibido) {
System.err.println("Error en lnea:" +nLnea);
System.err.println("\tEsperaba un "+aCadena(tokenEsperado)+
", y encontr un "+aCadena(tokenRecibido));
System.exit(-2);
}

/** Traduce el cdigo numrico de un token a una cadena para que
* el usuario la pueda entender.
*/
private static String aCadena(int token) {
if (token==Lexico.CTE_ENTERA) return "constante entera";
// * Es un carcter
else return "\'"+(char)token+"\'";
}
}

135
C Comprobador de Tipos
En el Ejemplo 42 se introdujo cmo, empleando el patrn de diseo Composite,
pueden ser modeladas las expresiones de tipo de un lenguaje de programacin. La primera
parte de este apndice muestra la implementacin de stas en el lenguaje C++.
Las expresiones de tipo eran, posteriormente, empleadas en el Ejemplo 44 para im-
plementar un sistema y comprobador de tipos de un subconjunto del lenguaje Pascal. El
segundo punto de este apndice muestra la implementacin del sistema de tipos implemen-
tado en una nica pasada, mediante la utilizacin de pclex y pcyacc.
C.1 Expresiones de Tipo
tipos.h
#ifndef _tipos_h
#define _tipos_h

#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <cassert>
using namespace std;

// * Clase componente del patrn Composite
class ExpresionTipo {
public:
virtual ~ExpresionTipo() {}
// * Para el generador de cdigo
virtual unsigned getBytes() const = 0;
// * Por depuracin
virtual string expresionTipo() const = 0;
// * Mtodos de anlisis semntico, con comportamiento predefinido
virtual ExpresionTipo *flecha();
virtual ExpresionTipo *corchete(const ExpresionTipo*);
virtual ExpresionTipo *parentesis(const vector<ExpresionTipo*>&);
virtual ExpresionTipo *punto(const string&);
// * Equivalencia de expresiones de tipo
virtual bool equivalente(const ExpresionTipo *) const;
};

// * Clase hoja del patrn Composite: tipo simple Integer
class Integer: public ExpresionTipo {
Integer(Integer&) {}
Integer &operator=(Integer&) { return *this; }
public:
Integer() {}
// * Para el generador de cdigo
unsigned getBytes() const { return 4; }
// * Por depuracin
string expresionTipo() const { return "integer"; }
};


// * Clase hoja del patrn Composite: tipo simple Char
class Char: public ExpresionTipo {
Char(Char&) {}
Char&operator=(Char&) { return *this; }
public:
Char() {}
// * Para el generador de cdigo
unsigned getBytes() const { return 1; }
// * Por depuracin
string expresionTipo() const { return "char"; }
};

Anlisis Semntico en Procesadores de Lenguaje
136
// * Clase hoja del patrn Composite: tipo simple Void
class Void: public ExpresionTipo {
Void(Void&) {}
Void&operator=(Void&) { return *this; }
public:
Void() {}
// * Para el generador de cdigo
unsigned getBytes() const { return 0; }
// * Por depuracin
string expresionTipo() const { return "void"; }
};

// * Clase hoja del patrn Composite: tipo simple Error
class Error: public ExpresionTipo {
Error(Error&) {}
Error&operator=(Error&) { return *this; }
string mensaje;
unsigned numeroLinea;
public:
Error(const string &s) {
mensaje=s; extern unsigned yylineno; numeroLinea=yylineno; }
// * Para el generador de cdigo
unsigned getBytes() const { assert(0); return 0; }
// * Por depuracin
string expresionTipo() const { return "error"; }
friend ostream &operator<<(ostream &,const Error &);
};

inline ostream &operator<<(ostream &o,const Error &e) {
return o<<"Error en la lnea "<<e.numeroLinea<<". "<<e.mensaje;
}

// * Clase compuesta del patrn Composite: tipo compuesto Pointer
class Pointer: public ExpresionTipo {
Pointer(Pointer&) {}
Pointer &operator=(Pointer&) { return *this; }
ExpresionTipo *a;
public:
Pointer(ExpresionTipo *a) { this->a=a; }
~Pointer() { delete a; }
// * Para el generador de cdigo
unsigned getBytes() const { return 4; }
// * Por depuracin
string expresionTipo() const;
// * Comprobaciones semnticas
ExpresionTipo *flecha() { return a; }
// * Equivalencia de expresiones de tipo
virtual bool equivalente(const ExpresionTipo *) const;
// * Mtodos especficos
const ExpresionTipo *getA() const { return a; }
};

// * Clase compuesta del patrn Composite: tipo compuesto Array
class Array: public ExpresionTipo {
Array(Array&) {}
Array&operator=(Array&) { return *this; }
int desde,hasta;
ExpresionTipo *de;
public:
Array(int desde,int hasta,ExpresionTipo *de) {
this->desde=desde; this->hasta=hasta; this->de=de; }
~Array() { delete de; }
// * Para el generador de cdigo
unsigned getBytes() const { return (hasta-desde+1)*de->getBytes(); }
// * Por depuracin
string expresionTipo() const;
// * Comprobaciones semnticas
ExpresionTipo *corchete(const ExpresionTipo *e) {
if (!dynamic_cast<const Integer*>(e))
return new Error("El ndice ha de ser de tipo entero.");
return de;
}
// * Equivalencia de expresiones de tipo
virtual bool equivalente(const ExpresionTipo *) const;
// * Mtodos especficos
const ExpresionTipo *getDe() const { return de; }
};

// * Clase compuesta del patrn Composite: tipo compuesto funcin ->
class Function: public ExpresionTipo {
Function(Function&) {}
Function&operator=(Function&) { return *this; }
ExpresionTipo *devolucion;
vector<ExpresionTipo*> parametros;
public:
Function(ExpresionTipo *d,const vector<ExpresionTipo*> &v):
devolucion(d),parametros(v) {}
Comprobador de Tipos
137
~Function();
// * Para el generador de cdigo
unsigned getBytes() const { return 4; }
// * Por depuracin
string expresionTipo() const;
// * Comprobaciones semnticas
ExpresionTipo *parentesis(const vector<ExpresionTipo*>&) ;
// * Equivalencia de expresiones de tipo
virtual bool equivalente(const ExpresionTipo *) const;
// * Mtodos especficos
const ExpresionTipo *getDevolucion() const { return devolucion; }
const vector<ExpresionTipo*> &getParametros() const { return parametros; }

};

// * Clase compuesta del patrn Composite: tipo compuesto Record
class Record: public ExpresionTipo {
Record(Record&) {}
Record &operator=(Record&) { return *this; }
map<string,ExpresionTipo*> campos;
public:
Record(const map<string,ExpresionTipo*> &c): campos(c) {}
~Record();
// * Para el generador de cdigo
unsigned getBytes() const;
// * Por depuracin
string expresionTipo() const;
// * Comprobaciones semnticas
ExpresionTipo *punto(const string&);
// * Equivalencia de expresiones de tipo
virtual bool equivalente(const ExpresionTipo *) const;
// * Mtodos especficos
const map<string,ExpresionTipo*> &getCampos() const { return campos; }
};

#endif
tipos.cpp
#include "tipos.h"
#include <map>
#include <string>
#include <vector>
#include <sstream>
#include <cstring>
using namespace std;

/******** Expresion Tipo ********************************************/

ExpresionTipo *ExpresionTipo::flecha() {
return new Error("Operacin ^ no permitida.");
}

ExpresionTipo *ExpresionTipo::corchete(const ExpresionTipo*) {
return new Error("Operacin [] no permitida.");
}

ExpresionTipo *ExpresionTipo::punto(const string&) {
return new Error("Operacin . no permitida.");
}

ExpresionTipo *ExpresionTipo::parentesis(const vector<ExpresionTipo*>&) {
return new Error("Operacin [] no permitida.");
}

// * Funcionamiento por omisin para los tipos simples
bool ExpresionTipo::equivalente(const ExpresionTipo *et) const {
return typeid(*this)==typeid(*et); // * RTTI
}


/******** Pointer ********************************************/

string Pointer::expresionTipo() const {
ostringstream o;
o<<"pointer("<<a->expresionTipo()<<")";
return o.str();
}

bool Pointer::equivalente(const ExpresionTipo *et) const {
const Pointer *puntero=dynamic_cast<const Pointer*>(et);
if (!puntero) return false;
return a->equivalente(puntero->a);
}

/******** Array ********************************************/
Anlisis Semntico en Procesadores de Lenguaje
138

string Array::expresionTipo() const {
ostringstream o;
o<<"array("<<desde<<".."<<hasta<<","<<de->expresionTipo()<<")";
return o.str();
}

bool Array::equivalente(const ExpresionTipo *et) const {
const Array *array=dynamic_cast<const Array*>(et);
if (!array) return false;
return desde==array->desde && hasta==array->hasta &&
array->equivalente(array->de);
}
/******** Function ********************************************/

Function::~Function(){
delete devolucion;
for (unsigned i=0;i<parametros.size();i++)
delete parametros[i];
}

ExpresionTipo *Function::parentesis(const vector<ExpresionTipo*> &v) {
if (v.size()!=parametros.size()) {
ostringstream o;
o<<"La funcin posee "<<parametros.size()<<" parmetros y se le estn "<<
"pasando "<<v.size()<<".";
return new Error(o.str());
}
// * Comparamos la igualdad de los tipos
int equivalente=1;
unsigned i=0;
for (;i<v.size()&&equivalente;i++)
equivalente=parametros[i]->equivalente(v[i]);
if (!equivalente) {
ostringstream o;
o<<"La funcion est declarada con el parmetro nmero "<<i<<
" de tipo "<<parametros[i-1]->expresionTipo()<<
" y se le est pasando una expresin de tipo "<<
v[i-1]->expresionTipo()<<".";
return new Error(o.str());
}
return devolucion;
}

string Function::expresionTipo() const {
ostringstream o;
if (parametros.size())
o<<"(";
unsigned i=0;
for (;i<parametros.size()-1;i++)
o<<parametros[i]->expresionTipo()<<',';
if (parametros.size())
o<<parametros[i]->expresionTipo()<<')';
o<<"->"<<devolucion->expresionTipo();
return o.str();
}

bool Function::equivalente(const ExpresionTipo *et) const {
const Function *fun=dynamic_cast<const Function*>(et);
if (!fun) return false;
if (parametros.size()!=fun->parametros.size())
return false;
for (unsigned i=0;i<parametros.size();i++)
if (!parametros[i]->equivalente(fun->parametros[i]))
return false;
return devolucion->equivalente(fun->devolucion);
}

/******** Record ********************************************/

Record::~Record() {
map<string,ExpresionTipo*>::const_iterator it;
for (it=campos.begin();it!=campos.end();++it)
delete it->second;
}

ExpresionTipo *Record::punto(const string &campo) {
if (campos.find(campo)==campos.end()) {
ostringstream o;
o<<"El campo \""<<campo<<"\" no esta declarado en el registro.";
return new Error(o.str());
}
return campos[campo];
}

unsigned Record::getBytes() const {
unsigned suma=0;
Comprobador de Tipos
139
map<string,ExpresionTipo*>::const_iterator it;
for (it=campos.begin();it!=campos.end();++it)
suma+=it->second->getBytes();
return suma;
}

string Record::expresionTipo() const {
ostringstream o;
o<<"record(";
map<string,ExpresionTipo*>::const_iterator it;
for (it=campos.begin();it!=campos.end();) {
o<<" ("<<it->first<<" x "<<it->second->expresionTipo()<<") ";
++it;
if (it!=campos.end()) o<<"x ";
}
o<<")";
return o.str();
}

bool Record::equivalente(const ExpresionTipo *et) const {
const Record *record=dynamic_cast<const Record*>(et);
if (!record) return false;
if (campos.size()!=record->campos.size())
return false;
map<string,ExpresionTipo*>::const_iterator it1,it2;
for (it1=campos.begin(),it2=record->campos.begin();it1!=campos.end();++it1,++it2) {
if (it1->first!=it2->first)
return false;
if (!it1->second->equivalente(it2->second))
return false;
}
return true;
}
ts.h
#ifndef _ts_h
#define _ts_h

#include "tipos.h"
#include <map>
#include <string>
#include <sstream>

using namespace std;

class TablaSimbolos {
map<string,ExpresionTipo*> tabla;
public:
~TablaSimbolos() {
map<string,ExpresionTipo*>::iterator it;
for (it=tabla.begin();it!=tabla.end();++it)
delete it->second;
}

bool existe(const string &id) { return tabla.find(id)!=tabla.end();}

ExpresionTipo *buscar(const string &id) {
if (!existe(id)) {
ostringstream o;
o<<"El identificador \""<<id<<"\" no se ha declarado.";
return new Error(o.str());
}
return tabla[id];
}

void insertar(const string &id,ExpresionTipo *et) {
tabla[id]=et;
}

};

#endif
C.2 Sistema y Comprobador de Tipos
sintac.y
%{
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "tipos.h"
Anlisis Semntico en Procesadores de Lenguaje
140
#include "ts.h"
#include <map>
#include <vector>
#include <iostream>
using namespace std;
TablaSimbolos ts;
extern unsigned yylineno;
int yyparse();
int yylex();
%}
%union {
int entero;
char caracter;
char cadena[255];
ExpresionTipo *tipo;
vector<ExpresionTipo*>* Vector;
map<string,ExpresionTipo*>* Map;
}
%right MENOS_UNARIO
%right '^'
%nonassoc '(' ')'
%token <entero> CTE_ENTERA
%token <caracter> CTE_CARACTER
%token <cadena> ID
%token FUNCTION RECORD PROCEDURE VAR BEGINPR END DOS_PUNTOS ARRAY OF INTEGER CHAR
%type <tipo> expresion tipo
%type <Vector> tipos listatipos listaexpresiones comaexpresiones
%type <Map> listacampos
%%
programa: VAR declaraciones BEGINPR expresiones END '.'
;
declaraciones: declaraciones declaracion ';'
| /* vacio */
;
declaracion: ID ':' tipo { if (ts.existe($1)) { cerr<<"Error en linea "<<yylineno;
cerr<<". "<<$1<<" ya declarado.\n"; }
ts.insertar($1,$3); }
;
tipo: INTEGER { $$=new Integer; }
| CHAR { $$=new Char; }
| '^' tipo { $$=new Pointer($2); }
| ARRAY '[' CTE_ENTERA DOS_PUNTOS CTE_ENTERA ']' OF tipo
{ $$=new Array($3,$5,$8); }
| FUNCTION '(' listatipos ')' ':' tipo
{ $$=new Function($6,*$3); delete $3; }
| PROCEDURE '(' listatipos ')'
{ $$=new Function(new Void,*$3); delete $3; }
| RECORD listacampos END
{ $$=new Record(*$2); delete $2; }
;
listatipos: tipos { $$=$1; }
| /* vacio */ { $$=new vector<ExpresionTipo*>; }
;
tipos: tipos ',' tipo { $$=$1; $$->push_back($3); }
| tipo { $$=new vector<ExpresionTipo*>; $$->push_back($1); }
;
listacampos: listacampos ID ':' tipo ';' { $$=$1; (*$$)[$2]=$4; }
| /* vacio */ { $$=new map<string,ExpresionTipo*>; }
;
expresiones: expresiones expresion ';' { Error *e=dynamic_cast<Error*>($2);
if (e) cerr<<*e<<endl;
else {
cout<<"Linea "<<yylineno<<", tipo "<<
$2->expresionTipo()<<", "<<
$2->getBytes()<<" bytes.\n"; }
}
| /* vacio */
;
expresion: '(' expresion ')' { $$=$2; }
| CTE_ENTERA { $$=new Integer; }
| CTE_CARACTER { $$=new Char; }
| ID { $$=ts.buscar($1); }
| expresion '^' { $$=$1->flecha(); }
| expresion '[' expresion ']' { $$=$1->corchete($3); }
| expresion '.' ID { $$=$1->punto($3); }
| ID '(' listaexpresiones ')' { $$=ts.buscar($1)->parentesis(*$3); delete $3; }
;
listaexpresiones: comaexpresiones { $$=$1; }
| /* vacio */ { $$=new vector<ExpresionTipo*>; }
;
comaexpresiones: comaexpresiones ',' expresion { $$=$1; $$->push_back($3); }
| expresion { $$=new vector<ExpresionTipo*>; $$->push_back($1); }
;
%%
void yyerror(char *s) {
extern unsigned yylineno;
cerr<<"Error sintactico en la linea "<<yylineno;
Comprobador de Tipos
141
}

int main() {
yyparse();
}
lexico.l
%{
#include <string.h>
#include <stdlib.h>
#include "tipos.h"
#include <map>
#include <vector>
using namespace std;
#include "yytab.h"

unsigned yylineno=1;
void errorLexico(char);
void inicializarLexico(char *,char *);
%}
comentario \/\/.*\n
linea \n
letra [a-zA-Z]
numero [0-9]
real {numero}+(\.{numero}+)?
alfa ({numero}|{letra})
var [Vv][Aa][Rr]
begin [Bb][Ee][Gg][Ii][Nn]
end [Ee][Nn][Dd]
function [Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]
procedure [Pp][Rr][Oo][Cc][Ee][Dd][Uu][Rr][Ee]
record [Rr][Ee][Cc][Oo][Rr][Dd]
array [Aa][Rr][Rr][Aa][Yy]
of [Oo][Ff]
integer [Ii][Nn][Tt][Ee][Gg][Ee][Rr]
char [Cc][Hh][Aa][Rr]
%%
[ \t]+ ; // * Se ignoran los espacions y tabuladores
{comentario} { yylineno++; }
{linea} { yylineno++; }
{numero}+ { yylval.entero=atoi(yytext); return CTE_ENTERA; }
\'{alfa}\' { yylval.caracter=yytext[1]; return CTE_CARACTER; }
{var} { return VAR; }
{begin} { return BEGINPR;}
{end} { return END;}
{function} { return FUNCTION;}
{procedure} { return PROCEDURE;}
{record} { return RECORD;}
{array} { return ARRAY;}
{of} { return OF; }
{integer} { return INTEGER; }
{char} { return CHAR; }
{letra}{alfa}* { strcpy(yylval.cadena,yytext); return ID; }
".." { return DOS_PUNTOS; }
\. |
\: |
\, |
\[ |
\] |
\( |
\) |
\^ |
\; |
; { return yytext[0]; } // Token=Caracter ASCII
. { errorLexico(yytext[0]); } // Caracter no perteneciente al lenguaje
%%

void errorLexico(char c) {
printf("Error lexico en linea %d. Caracter %c no perteneciente al lenguaje.\n",
yylineno,c);
exit(-1);
}

void inicializarLexico(char *fOrigen,char *fDestino) {
yyin=fopen(fOrigen,"r");
if (yyin==NULL) {
printf("Error abriendo el fichero %s.\n",fOrigen);
exit(-1);
}
yyout=fopen(fDestino,"w");
if (yyout==NULL) {
printf("Error abriendo el fichero %s.\n",fDestino);
exit(-1);
}
}
Anlisis Semntico en Procesadores de Lenguaje
142


143
REFERENCIAS BIBLIOGRFICAS
[Aho90] Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman. Compiladores: Principios, Tcnicas
y Herramientas. Addison-Wesley Iberoamericana. 1990.
[ANSIC++] Programming Language C++. Doc No: X3J16/96-0225. ANSI (American Na-
tional Stardards Institute).1996.
[ANTLR] ANTLR, Another Tool for Language Recognition. http://www.antlr.org/
[Atkinson86] R.G. Atkinson. Hurricane: An Optimizing Compiler for Smalltalk. ACM
SIGPLAN Notices, vol. 21, no. 11. 1986.
[Bischoff92] Kurt M. Bischoff. User Manual for Ox: an Attribute Grammar Compiling System
based on Yacc, Lex and C. Technical Report 92-30. Department of Computer
Science, Iowa State University. 1992.
[Bjorner82] D. Bjorner, C.B. Jones. Formal Specification and Software Development. Prentice
Hall. 1982.
[Cardelli97] Luca Cardelli. Type Systems. The Computer Science and Engineering Handbook,
CRC Press. 1997.
[Cueva03] J.M. Cueva, R. Izquierdo, A.A. Juan, M.C. Luengo, F. Ortn, J.E. Labra. Lengua-
jes, Gramticas y Autmatas en Procesadores de Lenguaje. Servitec. 2003.
[Cueva98] Juan Manuel Cueva Lovelle. Conceptos bsicos de Procesadores de Lenguaje.
Servitec. 2003.
[Eckel00] Bruce Eckel. Thinking in C++. Volume 1: Introduction to Standard C++. 2
nd

Edition. Prentice Hall. 2000.
[Engelfriet84] J. Engelfriet. Attribute grammars: Attribute evaluation methods. In B. Lorho,
editor, Methods and Tools for Compiler Construction, pp 103-137. Cambridge University
Press, 1984.
[FNC-2] The FNC-2 attribute grammar system. http://www-
rocq.inria.fr/oscar/www/fnc2/
[GOF02] Erich Gamma, Richard Elm, Ralph Johnson, John Vlissides. Patrones de diseo:
elementos de software orientado a objetos reutilizable. Pearson Educacin. 2002.
[Goldberg83] Adele Goldberg, David Robson. Smalltalk-80: the language and its implementa-
tion. Addison-Wesley. 1983.
[Gosling00] J. Gosling, B. Joy, G. Steele, G. Bracha. The Java Language Specification. Addison
Wesley. 2000.
[Hoare73] C.A.R. Hoare, N. Wirth. An axiomatic definition of the programming language
Pascal. Acta Informatica 2. 1973.
[Hopcroft02] J.E. Hopcroft, R. Motwani, J.D. Ulman. Introduccin a la Teora de autmatas,
lenguajes y computacin. Pearson Educacin. 2002.
[Jansen93] Paul Jansen, Lex Augusteijn y Harm Munk. An introduction to Elegant. Philips
Research Laboratories. 1993.
[JavaCC] JavaCC, Java Compiler Compiler. https://javacc.dev.java.net/
[Jazayeri75] M Jazayeri, W.F. Ogden y W.C. Rounds. The intrinsic exponential complexity of
the circularity problem for attribute grammars. Communications of the ACM 18
(12). 1975.
Anlisis Semntico en Procesadores de Lenguaje
144
[Johnson75] S. C. Johnson. YACC yet another compiler compiler. Technical Report Comput-
ing Science TR32, AT&T Bell Laboratories, Murray Hill. 1975.
[Kernighan91] Brian W. Kernighan, Dennis M. Ritchie. El lenguaje de programacin C. Segunda
Edicin. Pearson Educacin. 1991.
[Kernighan91] Brian Kernighan, Dennis M. Ritchie. El lenguaje de programacin C. 2 Edicin.
Prentice Hall 1991.
[Knuth68] Donald E. Knuth. Semantics of context-free languages. Mathematical Systems
Theory 2(2). 1968.
[Labra01] Jos Emilio Labra Gayo. Desarrollo Modular de Procesadores de Lenguajes a
partir de Especificaciones Semnticas Reutilizables. Tesis Doctoral. Departamento
de Informtica de la Universidad de Oviedo. 2001.
[Labra03] J.E. Labra, J.M. Cueva, R. Izquierdo, A.A. Juan, M.C. Luengo, F. Ortn. Intrpre-
tes y Diseo de Lenguajes de Programacin. Servitec. 2003.
[Louden97] Kenneth C. Louden. Compiler Construction: Principles and Practice. Brooks Cole.
1997.
[LRC] System for generating efficient incremental attribute evaluators.
http://www.cs.uu.nl/groups/ST/Software/LRC/
[Lucas69] Peter Lucas, Kurt Walk. On the Formal Description of PL/I. Annual Review of
Automatic Programming. Oxford Pergamon Press. 1969.
[Mason92] Tony Mason, John Levine, Doug Brown. Lex & Yacc. 2
nd
Edition. O'Reilly &
Associates. 1992.
[Meinke92] K. Meinke y J. V. Tucker. Universal algebra. En S. Abramsky, D. M. Gabbay, y T.
S. E. Maibaum, editores, Handbook of Logic in Computer Science, tomo I. Ox-
ford University Press, 1992.
[Milner78] Robin Milner. A theory of type polymorphism in programming. Journal of Com-
puter and Systems Sciences 17. 1978.
[Milner84] Robin Milner. A proposal for standard ML. CDM Symposium on Lisp and Func-
tional Programming Languages. 1984.
[Mosses91] P.D. Mosses. Action Semantics. Cambridge University Press. 1991.
[Muchnick97] Steven Muchnick. Advanced Compiler Design and Implementation. Morgan
Kaufmann. 1997.
[Nielson92] H. R. Nielson, F. Nielson. Semantics with Applications. Wiley. 1992.
[Pascal82] British Standards Institute. Specification for Computer Programming Language
Pascal. Publication BS6192:1982. British Standards Institute. 1982.
[Pierce02] Benjamin C. Pierce. Types & Programming Languages. MIT Press. 2002.
[SableCC] SableCC: Java object-oriented framework to generate compilers and interpreters.
http://www.sablecc.org/
[Saraiva99] Joao Saraiva. Implementing a Functional Implementation of Attribute Grammars.
Tesis Doctoral. Universiteit Utrecht. 1999.
[Scott00] Michael Scott. Programming Language Pragmatics. Morgan Kaufmann. 2000.
[Stroustrup93] Bjarne Stroustrup. El lenguaje de programacin C++. 3 edicin. Addison Wesley
Diaz de Santos. 1993.
[Waite84] W. M. Wite y G Goos. Compiler Construction. Springer-Verlag. 1984.
[Waite84] William M. Waite y Gerhard Goos. Compiler Construction. Springer-Verlag. 1984.
Referencias Bibliogrficas
145
[Watt00] David A. Watt, Deryck Brown.Programming Language Processors in Java: Com-
pilers and Interpreters. Prentice Hall. 2000.
[Watt96] David A. Watt, Muffy Thomas. Programming Language Syntax and Semantics.
Prentice Hall. 1996.
[Wilhelm95] Renhard Wilhelm, Dieter Maurer. Compiler Design. Addison Wesley. 1995.

También podría gustarte