Subido por Julián Denis

Compiladores 01 16172

Anuncio
Capítulo 6
Análisis semántico
6. I
6.2
6.3
Atributos y gramáticas con
atributos
Algoritmos para cálculo de
atributos
La tabla de símbolos
6.4 Tipos de datos y verificación
6.5
de tipos
Un analizador sernántico para
el lenguaje TlNY
En este capítulo analizamos la fase del compilador que calcula la información ad~cional
necesaria para la compilación una vez que se conoce la estructura sintáctica de un
programa. Esta fase se conoce como análisis semántico debido a que involucra el cálculo
de información que rebasa las capacidades de las gramáticas libres de contexto y los
algoritmos de análisis sintáctico estándar, por lo que no se considera como sintaxis.'
La información calculada tambikn está estrechamente relacionada con el significado final,
o semántica, del programa que se traduce. Como el análisis que realiza un compilador es
estático por definición (tiene lugar antes de la ejecución), dicho análisis semántico tambien
se conoce como análisis semántico estático. En un lenguaje típico estáticamente tipificado como C.el análisis semántico involucra la construcción de una tabla de símbolos
para mantenerse al tanto de los significados de nombres establecidos en declaraciones e
inferir tipos y verificarlos en expresiones y sentencias con el fin de determinar su exactitud
dentro de las reglas de tipos del lenguaje.
El análisis semántico se puede dividir en dos categorías. La primera es el análisis de
un programa que requiere las reglas del lenguaje de programación para establecer su
exactitud y garantizar una ejecución adecuada. La complejidad de un análisis de esta clase
requerido por una definición del lenguaje varía enormemente de lenguaje a lenguaje. En
lenguajes orientados en forma dinámica, tales como LISP y Smalltalk, puede no haber
análisis semántico estático en absoluto, mientras que en un lenguaje como Ada existen
fuertes requerimientos que debe cumplir un programa para ser ejecutable. Otros lenguajes
se encuentran entre estos extremos (Pascal, por ejemplo. no es tan estricto en sus
requerimientos estáticos como Ada y C, pero no es tan condescendiente como LISP).
l. Este punto fuc cmneniadci con algún detalle en la secci<h 3.6.3 del c;ipititlo 3
CAP. 6 1 ANALISIS SEHANTICO
La segunda categoría de análisis semántico es el análisis realizado por un compilado
para mejorar la eficiencia de ejecución del programa traducido. Esta clase de análisis
por lo regular se incluye en análisis de "optimización", o técnicas de mejoramiento de
código. Investigaremos algunos de estos métodos en el capitulo sobre generación de c
digo, mientras que en este capitulo nos enfocaremos en los análisis comunes que por
exactitud son requeridos para una definición del lenguaje. Conviene advertir que las té
nicas estudiadas aqui se aplican a ambas situaciones. También que las dos categorías no
son mutuamente excluyentes, ya que los requerimientos de exactitud, tales como la
rificación de tipos estáticos, también permiten que un compilador genere código más e
ciente que para un lenguaje sin estos requerimientos. Además, vale la pena hacer no
que los requerimientos de exactitud que aqui se comentan nunca pueden establecer I
exactitud completa de un programa, sino sólo una clase de exactitud parcial. Tales req
rimientos todavía son útiles debido a que proporcionan al programador información p
mejorar la seguridad y fortaleza del programa.
El análisis semántico estático involucra tanto la descripción de los análisis a reali
como la implementación de los análisis utilizando algoritmos apropiados. En este se
do, es semejante al análisis léxico y sintáctico. En el análisis sintáctico, por ejemplo, uti
zamos gramáticas libres de contexto en la Forma Backus-Naus (BNF, por sus siglas en
inglés) para describir la sintaxis y diversos algoritmos de análisis sintáctico descendente
ascendente para implementar la sintaxis. En el análisis semántico la situación no es t
clara, en parte porque no hay un método estándar (como el BNF) que permita espe
ficar la semántica estática de un lenguaje, y en parte porque la cantidad y categoría del
análisis semántico estático varía demasiado de un lenguaje a otro. Un método para descri
el análisis semántico que los escritores de compiladores usan muy a menudo con buen
efectos es la identificación de atributos, o propiedades, de entidades del lenguaje q
deben calcularse y escribir ecuaciones de atributos o reglas semánticas, que expr
san cómo el cálculo de tales atributos está relacionado con las reglas gramaticales del le
guaje. Un conjunto así de atributos y ecuaciones se denomina gramática con atributos.
Las gramáticas con atributos son más útiles para los lenguajes que obedecen el principio de
la semántica dirigida por sintaxis, la cual asegura que el contenido semántico de un
programa se encuentra estrechamente relacionado con su sintaxis. Todos los lenguajes
modernos tienen esta propiedad. Por desgracia, el escritor de compiladores casi siempre
debe construir una gramática con atributos a mano a partir del manual del lenguaje, ya
que rara vez la da el diseñador del lenguaje. Aún peor, la construcción de una gramática
con atributos puede complicarse innecesariamente debido a su estrecha adhesión con la
estructura sintáctica explícita del lenguaje. Un fundamento mucho mejor para la expresi
de los cálculos semánticos es la sintaxis abstracta, como se representa mediante un ár
sintáctico abstracto. Incluso. el diseñador del lenguaje, también suele dejar al escritor d
compilador la especificacicln de la sintaxis abstracta.
Los algoritmos para la implementación del análisis semántico tampoco sqn tan claramente expresables como los algoritmos de análisis sintáctico. De nuevo, esto se debe en
parte a los mismos problemas que se acaban de mencionar respecto a la especificación
del análisis semántico. N o obstante, existe un problema adicional causado por la temporización del análisis durante el proceso de compilación. Si el análisis semántico se puede
suspender hasta que todo el análisis sintáctico (y la construcción de un árbol sintáctico
abstracto) esté completo, entonces la tarea de implementar el análisis semántico se vuelve
considerablemente más fácil y consiste en esencia en la especificación de orden para un
recorrido del árbol sintáctico. junto con los cálculos a realizar cada vez que se encuentra
un nodo en el recorrido. Sin embargo, esto implica que el compilador debe ser de paso
Atributos y gramátttas con atributor
259
múltiple. Si. por otra parte, el compilador necesita realizar todas sus operaciones en un solo
paso (incluyendo la generación del código), entonces la implementación del análisis semántic0 se convierte en mucho más que un proceso a propósito para encontrar un orden
correcto y un método para calcular la información semántica (suporiiendo que un orden correcto así exista en realidad). Afortunadamente, la práctica moderna cada vez más
permite al escritor de compiladores utilizar pasos múltiples para simplificar los procesos
de análisis semántico y generación de código.
A pesar de este estado algo desordenado del análisis semántico, es muy útil para estudiar gramáticas con atributos y cuestiones de especificación. ya que esto redundará en la
capacidad de escribir un código más claro, más conciso y menos proclive a errores para
análisis semántico, además de permitir una comprensión más fácil de ese código.
Por lo tanto, el capítulo comienza con un estudio de atributos y gramáticas con atributos. Continúa con técnicas para implementar los cálculos especificados mediante una
gramática con atributos, incluyendo la inferencia de un orden para los cálculos y los recorridos del árbol que los acompañan. Dos secciones posteriores se concentran en las
áreas principales del análisis semántico: tablas de símbolos y verificación de tipos. La última
sección describe un analizador semdntico para el lenguaje de programación TlNY presentado en capítulos anteriores.
A diferencia de capítulos previos, este capitulo no contiene ninguna descripción de
un generador d e analizador semántico ni alguna herramienta general para construir
analizadores semánticos. Aunque se han construido varias de tales herramientas, ninguna ha
logrado el amplio uso y disponibilidad de Lex o Yacc. En la sección de notas y referencias
al final del capítulo mencionamos algunas de estas herramientas y proporcionamos referencias a la literatura para el lector interesado.
6.1 ATRIBUTOS Y GRAMATICAS CON ATRIBUTOS
U n atributo es cualquier propiedad de una construcción del lenguaje de programación. Los
atributos pueden variar ampliamente en cuanto a la inf«rmación que contienen, su complejidad y en particular el tiempo que les torna realizar el proceso de traducci<ín/ejec~icióncuando
pueden ser determinados. Ejemplos típicos de atributos son
El tipo de <latosde una variable
E l valor de una expresión
L a ubicaciún de una variable en la memoria
E l cbdigo objeto de un procedimiento
El número de dígitos significativos en un número
Los atributos se pueden csrablecer antes del proceso de compilaciún (o incluso la construcción de un con~pilador).Por ejemplo, el número de dígitos significativos eri un número se
puede establecer (o por lo menos dar un valor mínimo) mediante la definición de un Ienguaje. Ademis, los atributos s6lo se pueden determiriar durante la ejecuciúti tiel progrini;~, ti11
como el valor de una expresicín (no consiante). o la ubicaci6n de una estructura {le datos dininiicaniente asignada. E l proceso (te calcular un atributo y asociar su valor calculado con
la construcciún del lenguaje en cuestión se define corno fijación del atrihuio. El tiempo que
cn;tnd« se presenta la í'ijacitin de un atributo se detoma el proceso de conipilación/ejcc~~ci<íri
iiominii tiempo de f¡,jación. Los iictnpos ilc fijacifin de :itrihutos iliferentes viiríon, c iircluso c l niis~no~iirihuiopuetlc teticl. iieinpos ilc: qnci(íri h;istantc tlií'ucntcs (le un !enguii~c
CAP. 6 1
ANALISIS SEMANTICO
a otro. Los atributos que pueden fijarse antes de la ejecución se denominan estáticos.
tras que los atributos que sólo se pueden fijar durante la ejecución son dinámicos. Natur
mente, un escritor de compiladores está interesado en aquellos atributos estáticos que s
jan durante el tiempo de traducción.
Considere la lista de muestra de atributos dada con anterioridad. Analizamos el ti
po de fijación y la significancia durante la compilación de cada uno de los atributos
la lista.
En un lenguaje estáticamente tipificado como C o Pascal, el tipo de datos de una vari
o expresión es un importante atributo en tiempo de compilación. Un verificador de
es un analizador semántico que calcula el atributo de tipo de datos de todas las en
des del lenguaje para las cuales están definidos los tipos de datos y verifica que es
tipos cumplan con las reglas de tipo del lenguaje. En un lenguaje corno C o Pascal, un v
rificador de tipo es una parte importante del análisis seinántico. Sin embargo,
en un
lenguaje como LISP, los tipos de datos son dinimicos, y un compilador LISP debe
nerar código para calcular tipos y realizar la verificación de tipos durante la ejecu
del programa.
Los valores de las expresiones por lo regular son dinámicos, y un compilador gener
código para calcular sus valores durante la ejecución. Sin embargo, algunas expresi
pueden, de hecho, ser constantes (por ejemplo 3 + 4 * S), y un analizador semántico
de elegir evaluarlas durante la compilación (este proceso se conoce como incorp
ción de constantes).
La asignación de una variable puede ser estática o dinámica, dependiendo del le
guaje y las propiedades de la variable misma. Por ejemplo, en FORTRAN77 toda
variables se asignan de manera estática, mientras que en LlSP todas las variable
asignan dinámicamente. C y Pascal tienen una mezcla de asignación de variables e
tica y dinámica. Un compilador por lo regular pospondrá los cálculos asociados
la asignación de variables hasta la generacibn de código, porque tales cálculos depe
den del ambiente de ejecución y. ocasionalmente, de los detalles de la máquina objetivo: (El capítulo siguiente trata esto con más detalle.)
El código objeto de un procedimiento es evidentemente un atributo estático. El generador de código de un compilador está comprometido por completo con el cálculo de
este atributo.
El número de dígitos significativos en un número es un atributo que con frecuencia
no se trata explícitamente durante la compilación. Está implícito en el sentido de que
el escritor de compiladores implementa la9 representaciones para los valores, lo que
general se considera como parte del ambiente de ejecución que se analiza en e1
pítnlo siguiente. Sin embargo, incluso el analizador léxico puede necesitar conoce
número de dígitos signitlcativos permitidos si va a producir constantes de manera
correcta.
Como vimos a partir de estos ejemplos, los cálculos de atributos son muy variados.
Cuando aparecen de manera explícita en un conipilador pueden presentarse en cualyuier
momento durante la compilación: aun cuando asociarnos el cálculo de atributos más estrechamente con la fase de ariilisis semántico, tanto el analimdor léxico como el analizador
sintáctico pueden necesitar disponer de información de atributo, y puede ser necesarici realizat rilgún anhlisis semántico al mismo tiempo que cl anilisis sintáctico. En este capítulo
nos enfocamos en cálculos típicos que se presentan antes de la generacibn de c6digo y tlespuCs del ;inálisis sintáctico (pero vdase la secciiín 6.2.5 para inforriiaci6n sobre el ;inálisis
,
,.,*gj
<;
.
Atrtbutos y gramáticas con atributos
26 1
semántica durante el análisis sintáctico). El análisis de atributos que se puede aplica; directamente a la generaciiín de código se analizará en el capítulo 8.
6.1.1
Gramáticas con atributos
En la semántica dirigida por sintaxis los atributos están directamente as«ciados con los
símbolos gramaticales del lenguaje (los ternlinales y no terminales).' Si X es un símbolo
gramatical, y u es un atributo asociado con X , entonces escribirnos X.u para el valor de u
asociado con X. Esta notación nos recuerda el indicador de campo de registro de Pascal o
(de manera equivalente) una operación de miembro de estructura en C. En realidad, una manera típica de implementar cálculos de atributo es colocar valores de atributo en los nodos
de un árbol sintáctico utilizando campos de registro (o miembros de estructura). Veremos
esto con más detalle en ta siguiente sección.
Dada una colección de atributos a,, . . . . nk, el principio de la semántica dirigida por sintaxis implica que para cada regla gratnatical XO -,,Yl X2 . . . X, (donde Xo es un no terminal,
mientras que las otras Xi son símbolos arbitrarios), los valores de los atributos X,.a, de cada
símbolo gramatical X, están relacionados con los valores de los atributos de los otros sínibolos en la regla. Si el mismo símbolo X, apareciera más de una vez en la regla gramatical,
entonces cada ocurrencia tendría que distinguirse de las otras mediante una subindización
adecuada, de manera que se puedan diferenciar los valores de atributo de diferentes ocurrencias. Cada relación está especificada por uria ecuación de atributo o regla semántica" de
la forma
donde& es una función matemática de sus argumentos. Una gramática con atributos para los atributos u l , . . . ,uk es la coleccicín de todas esas ecuaciones para todas las reglas gramaticales del lenguaje.
En esta generalidad las gramáticas con atributos pueden parecer muy complejas. En la
práctica Ias funciones A, por lo regular son bastante simples. Por otro lado, es raro que los
atributos dependan de un gran número de otros atributos, así que los atributos pueden separarse en pequeños conjuntos independientes de atributos interdependientes, y las gramáticas
con atributos pueden escribirse de manera separada para cada conjunto.
Por lo general, las gramáticas con atributos se escriben en forma tabular, con cada regla
gramatical enumerada con el conjunto de ecuaciones de atributo, o reglas semánticas asociadas con esa regla de la manera siguiente:'
2. La semántica dirigida por sintaxis podría fácilmente ser llamada sintaxis dirigida por scrnáiitira, puesto que en la mayoria de los lenguajes la sintaxis se diseña con la seniántica (incidental) cIc la
construcción ya en mente.
3. En nn irahajo posterior veremos las reglas semánticas como algo mis general que ccoacioms
. de :itrihutri. Por ahora, el lector (~rictle
consider:irl:is idéoricas.
J. Siempre usarenios el cnc;ibez:ido "reglas sernánticas" en estas tablas en ver de "ecnaciones de
:~trihtito"para tener cn ciientti tina intcrpretnción rnds general de las regla scm;intic;rs iiris ;~delanle.
CAP. 6 1
ANALISIS IEMANTICO
Regla gramatical
Reglas sernánticas
Regla I
Eciiaciones de atributo
asociadas
Kegla n
Ecuaciories de iitrihttto
asociadas
Continuamos directamente con varios ejemplos.
~jemplo6.1
Consideremos la siguiente grainática simple para núnreros sin signo:
rtrinzero + riiímero &giro / &ito
digito+O/1/2/3/4/5/6/7/8/9
El atributo más importante de un número es su valor, al cual le asignamos el nombre v
Ccrrla dígito tiene un valor que es directamente calculable del dígito real que representa. A
por ejemplo, la regla gramatical dígiro -+ O implica que dígito tiene valor O en este cas
Esto se puede expresar mediante la ecuación de atributo c l ~ ~ i t o . v a=l O, y asociamos e
ecnación con la regla disito -+ O. Adeniás, cada número tiene un valor basado en el díg
qne contiene. Si tin número se deriva utilizando la regla,
número + dígito
entonces el número contiene precisamente un dígito, y su valor es el valor de este dígito. L
ecuación de atributo que expresa este hecho es
mítnero. val
=
digiro. vid
Si un número contiene más de un dígito, entonces se deriva utilizando la regla gramatical
ndmerci 4 nrímero dígito
y debernos expresar la relación entre el valor del sínibolo en el lado izqnierdo de la regla
gramatical y los valores de los símbol«s en el lado derecho. Advierta que las dos ocurrencias de rtiírnrro en la regla gramatical deben distinguirse. pitesto que el 11N17ieroa la
derecha tendrá un valor diferente del correspondiente al rirííwro en la izquierda. Los disiinmiremos titilizmdo subíndices. y la regla gramatical la escribiremos con10 se muestra a
-,
c«ritinnacicín:
4
5
Atributos y gramaticas ron atributos
263
Ahora considere un número como 34. Una derivación (por la izquierda) ile este núinero es de
la manera siguiente: niírnem * núnwro iiígito * clígito dígiro * 3 ilíqito 34. Considere
el uso de la regla gramatical numerol + rnímero2 di@ en el primer paso de esa derivación. El no terminal ntírnero2 corresponde a1 dígito 3, mientras que díqiro corresponde 211
dígito 4 . Los valores de cada tino son 3 y 4: respwtivarnente. Para obtener el valor de n~írnefo~
(es decir, 34), debemos ntultiplicar el valor de n~irnero~
por 10 y agregar cl valor de dígilo:
34 = 3 * 10 + 4. En otras pirlabras, desplü~amosel 3 un lugar decimal hacia la izquierda y
sumamos 4. Esto corresponde a la ecuación de atributos
Una gramática con atributos completa para el atributo r d se da en la tabla 6.1.
El significado de las ecuaciones de atributo para una cadena particular pueden visualizarse utilizando el árbol de análisis gramatical para la cadena. Por ejemplo, el árbol de
análisis gramatical para el número 345 se da en la figura 6.1. En esta figura el cálculo correspondiente a la ecuación de atributo apropiada se muestra debajo de cada nodo iiiterior. Visualizar las ecuaciones de atributo como cálculos en e1 árbol de análisis grarrtatical
es importante para los algoritmos que calculan valores de atrihoto, como vereinos en la sección s i g ~ i e n t e . ~
Tanto en la tabla 6.1 como en la figura 6. l pusimos énfasis en la diferencia entre la rcprcsentacih sintictica de un dígito y el valor, o contenido setnántico, del dígito uti1ir;mclo
diferentes fuentes tipográficas. Por ejemplo, en la regln gramatical &ito -+ O, el dígito O
es un token 0 carácter, mientras que clígito.viz1 = O significa que el dígito tiene el valor ituménco 0.
:.
i:
Tdbla 6.1
Gramatira con atributos
para el ejemplo 6.1
Regla gramatical
Reglas semánticas
nrirnerot -+
rnímerot .vril =
UP.6 1
ANALBIS SEMANTICO
ruímero
(val= 34 * 10 + 5 = 345)
Figura 6.1
Árbol stntactico que muestra
~álculosde atr~butopan el
ejemplo 6.1
/ \
iiiímem
d(&~
( v d = 5)
(w1=3 * 1 0 + 4 = 3 4 )
//
niimrro
(id
1
= 3)
l
\dígifo
dtgito
(val = 3 )
5
(val = 4 )
1
4
1
3
Ejemplo 6.2
Considere la gramática siguiente para expresiones aritméticas enteras simples:
esp 4 exp + tenn / exp - term / temz
term 4 term *factor 1 factor
fucror -+ ( exp ) 1 número
Esta gramática es una versión un poco modificada de la gramática de expresión simpl
estudiada extensamente en capítulos anteriores. El atributo principal de una e.rp (o te
factor) es su valor numérico, el cual escribimos como val. Las ecuaciones de atributo
el atributo val se proporcionan en la tabla 6.2.
Tabla 6.2
Gramática ton atributos
pan el ejemplo 6.2
Regla gramatical
Reglas sern&nticas
expl -t expz + tenn
exp, -+ expz - t e m
ex/? -+ term
terin, .+ termz "factor
term -t fuct(~r
factor -r ( e . y )
factor -+ n ú m e r o
exp, .val = exp2 .sal 4- lerm.val
e.xp, .val = expz .val - tem.val
r.rp.va1 = tem.vul
r e m l .vd = t r m , .val * factor.va1
trm.va1 = factor. val
factor.va1 = exp.val
factor. val = número.va1
Estas ecuaciones expresan la relación entre la sintaxis de las expresiones y la semántica de los cálculos aritméticos que se realizarán. Observe, por ejemplo, la diferencia entre el
símbolo sintáctico + (un token) en la regla gramatical
:*;!
'4
$
e.rp, + exp,
+ tem
y la operación de adición o suma aritmética + que se realiza en la ecuación
e.rpip, .~
u=
l rxp2 .VLZI + trrnz.vul
..
Atr~butosy gramáticas
tan
atributos
265
Advierta también que no hay ecuación con número.va1 en el lado izquierdo. Coino veremos
en la sección siguiente, esto implica que nÚmero.va1 debe calcularse antes de cualquier
análisis semjritico que utilice esta gmniática con atributos (por ejemplo, mediante el análisis Iéxico). De manera alternativa, si queremos que este valor esté explícito en la gramática con atributos, debemos agregar reglas gramaticales y ecuaciones de atributo ri la gramática coi1
atributos (por ejemplo, las ecuaciones del ejemplo 6.1).
También podemos expresar los cálculos implicados mediante esta gramática con atributos agregando ecuaciones a los nodos en un árbol de análisis gramatical, como en el ejemplo 6.1. Por ejemplo, dada la expresión ( 3 4 - 3 i *42, podemos expresar la semántica de su
valor en su irbol de análisis gramatical como en la figura 6.2.
S,
f~gura6 2
A&ol de anillrts gramatical
para ( 3 4 - 3 ) * 4 2 que
muestra cálculor del atributo
val para la gramática con
atributos del ejemplo 6.2
term
(val = 31 * 4 2 = 1302)
term
/ l
(vul= 31)
\
/¿actor
(vul = 42)
1
1
número
(val = 42)
factor
(val = 3 1 )
A\'
e.v
(vol = 34 3
(
-
)
= 31)
11
term
e.tp
(val = 34)
I
term
(vol = 34)
I
factor
(val = 34)
(VUI= 3)
I
faclor
(val = 3 )
I
número
( w l = 3)
I
número
(val = 34)
Ejemplo 6.3
Considere la siguiente gramática simple para declaraciones de variable en una sintaxis tipo C:
decl-, type var-list
type 4 int / f l o a t
var-list -+ id, var-list / id
Queremos definir un atributo de tipo de datos para las variables dadas por los identificadores en una declaración y escribir ecuaciones que expresen cómo está relacionado
el atributo de tipo de datos con el tipo de la declaración. Hacemos esto construyendo una
gramática con atributos para un atributo rftype (utilizamos e1 nombre dtype para distinguir el atributo del no terminal typr).-La gramática con atributos para dtype se da en la
tabla 6.3. Harenios las observaciones siguientes respecto a las ecuaciones de atributo de
esa figura.
CAP. 6 1 ANALISIS
SEMAUTICO
En primer lugar, los valores de dtype son del conjunto [integer, r e d ) que correspon
a los tokens i n t y f loat.El no terminal type tiene un cltype dado por el token que re
senta. Este iltype corresponde al d t ~ p ede la vur-list entera, por la ecuación asociada co
regla gramatical para <lecl.Cada i d en la lista tiene este mismo tlope, por las ecuaci
asociadas con vur-list. Advierta que no hay ecuación que involucre el & p e del no ter
r l d . En realidad, una decl no necesita tener un dtype: no es necesario especificar el v
de un atributo para todos los símbolos gramaticales.
Como antes, las ecuaciones de atributo se pueden exhibir en un árbol de análisis gra
tical. Se proporciona.un ejemplo en la figura 6.3.
Tabla 6.3
Gramática ton atributos
para el ejemplo 6.3
figura 6.3
hrbol de analisis gramatical
para la cadena f loat
x , y que muestra el atributo
dtype como re especifica
mediante la gramática ton
atributos de la tabla 6.1
Regla gramatical
decl
Reglas sernánticas
-+ rype var-lisr
var-listdtype = gpe.<ftype
decl
--''
r?p
(~ltypr= real)
l
f loat
\
var-li.st
(dlype = r m l )
]
id
(X)
(dqpe = r e d )
\
vur-Iist
(dtype = r e d )
1
id
(Y)
(iitype = real)
En los ejemplos que hemos visto hasta ahora, sólo había un atributo. Las gramática
atributos pueden involucrar varios atributos interdependientes. El ejemplo siguiente e
"situación simple donde existen atributos interdependientes.
Ejemplo 6.4
Considere una modificaciRn de la gramática numérica del ejemplo 6.1, donde los números
pueden ser octales o decimales. Imaginemos que esto se indica mediante un sufijo de un
carácter o (para octal) o d (para decimal). Entonces tenemos la gramática signiente:
Atrtbutos y gramaticas con atributos
267
En este caso rmm y dígito requieren un nuevo atributo buse, que se utiliza para calcular el
atributo val. La gramática con atributos para hase y vril se proporciona en la tabla 6.4.
Tabla 64
Gramática con
atributos
el ejemplo 6.4
Regla gramatical
Reglas sernanticas
iiurn-hose -t
nu~nriirhnse
curbase -+ o
<.~rrbcrse
-+ d
nurn, riurnz digiro
iiiitn-hure i u1 = nirni 1111
nuni.hme = curbase.buse
carhnse.buse = 8
c«rbuse.hu.re = 1 O
riiiin l .w l =
if dígitavul = error or riuin2 . v d = error
then error
else mimz .vul * num, .hase + rlígiio.vrrl
nurni .hase = nwnl .huse
dígito.buse = ririrni .bu.se
num.val = ú(qito.val
dígito.base = r111m.l~ase
dígito.vul = O
dígiro. vul = I
-
riiim -t dígito
dígifo -t O
digiro -+ 1
rlí&
+9
if dígif<t.bu.se= 8 then error else 8
dígiro.vul =
if d(qito.base = 8 then error eise 9
Deberían observarse dos nuevas características en esta gramática con atributos. En primer lugar, la gramática BNF no elimina por sí misma la conibinacicín errónea de los dígitos
(no octales) 8 y 9 con el sufijo o.Por ejeniplo, la cadena 1890 es sintácticamente correcta
de acuerdo con la BNF anterior, pero no se le puede asignar ningún valor. De este modo, es
necesario un nuevo valor error p x a tales casos. Además, la gramática con atributos debe
expresar el hecho de que la introducción de 8 o 9 en un número con un sufijo o produce un
valor de error. La manera más fácil de hacer esto es emplear una expresión if-then-else en
las funciones de las ecuaciones y atributo apropiadas. Por ejemplo, la ecuación
num .val =
if dígitaval = error nr numz .val = error
then error
else num2 .val * numl .buve + dígiro.vu1
correspondiente a la regla gramatical numI -t num2 dígito expresa e1 hecho de que si cualquiera de nctin2.vcil o rli~ito.vu1es error entonces numi.vul también debe ser error, y scílo si
no es ése el caso rinrnl.~~irl
está dado por la fórmula n~irn~.vnl
* nurni.b~ise+ cli,ito.vnl.
Para coticluir con este ejemplo mostramos de nueva cuenta los cálculos de atributo en
un irbol de ;inálisis gramatical. La figura 6.4 ofrece un árbol de análisis gramatical para el
~iúinero3450,junto con 10s valores de atributo calculados de acuerdo con la gramática con
atributos de la tabla 6.4.
Figura 6.1
~ r b o lde analisis gramatical
que muestra cálculos de
atributo para el ejemplo 6.4
6.1.2
Simplificaciones y extensiones a gramática
con atributos
El uso de una expresión if-then-else extiende las clases de expresiones que pueden
cer en una ecuación de atributo de una manera útil. La colección de expresiones perm
en una ecuación de atributo se conoce como metalenguaje para la gramática con atributos
Por lo regular queremos un nietalenguaje cuyo significado sea suficientemente claro p
que no surja confusión sobre su propia seniántica. ?'anibién deseamos un metalenguaje q
sea cercano a un 1enguaje.de programación real, ya que, como veremos en breve, quere
convertir las ecuaciones de atributo en código de trabajo en un analizador semántico.
este libro utilizamos un metalenguaje que está limitado a expresiones aritméticas, lógicas
algunas otras clases de expresiones, junto con una expresión if-then-else, y ocasionalmen
te una expresión case o switch.
Una característica adicional útil a1 especificar ecuaciones de atributo es agregar al metalenguaje el uso de funciones cuyas definiciones se puedan dar en otra parte. Por ejemp
en las gramáticas para números hemos estado escribiendo ecuaciones de atributo para ca
una de las opciones de rlígito. En cambio, podríamos adoptar una convención más con
escribiendo la regla gramatical para dígito como dígito -+ D (donde se entiende que D es u
de los dígitos) y entonces escribir la ecuación de atributo correspondiente como
digito. vul = tzurnvul(r))
Aquí nurnvctl es una función cuya definición debe ser especificada en otra parte como un su-
plemento para la graniiítica con atributos. Por ejemplo, podemos d:ir la siguiente dctlnición
de nunriul corito c6digo en C:
int numval(char D)
{ return (int)D - íint)'O';)
Auibuter y gramáticar con atributos
269
Una shptificación adicional que puede ser útil al especificar gramática con atributos es
utilizar una.fomaambigua, pero más simple, de 1a.gramáticaariginal. De hecho, puesto que
se supone queel analizador sintáctico ya fue construido, toda ambigüedad se habrá abordado en esta e,tapa,y la gramática con atributos puede basarse libremente en construcciones
.
ninguna ambigüedad en los atributos resultantes: Por ejemplo, la graambiguas, sin implicar
mática'de expresión aritmdtica del ejemplo 6.2 tiene la siguiente forma más sencilla, pero
ambigua:
,
exp -+ exp + e x p / exp
- exp 1 exp e a p /
( exp )
/ número
Cuando se utiliza esta gramática el atributo vdl se puede definir mediante la tabla, que se
.
muestra en la tabla 6.3 (compare esta con la tabla 6.2).
,
Regla gramatical
~ e ~ semánticas
f&
exppf -+ exp2 + expl
e.xp, -9 e.ip2 exp3
exp, -+ expz * exp3
ex& -+ ( e%
exp -+ aúmaro
expt .va¡,= e x p ~val
. t exp3 .val
exp, .val = expz .val - e.rp3 .val
exppc .val = exp, .val * exp3 .val
exp, .val = exp, ,val
exp.~aaf número .val
-
Tambien se puede hacer una simplificación exhibiendo valores de atributo mediante el
uso de un árbol sintáctico abstracto en lugar de un árbol de análisis gramatical. Un árbol sintáctico abstracto debe tener siempre la estructura suficiente, de manera que se pueda expresar la semintica definida mediante una gramática con atributos. Por ejemplo, Ia expresión
isis gramatical y atributos val se mostraron en la figura 6.2,
pueden tener su sem
pletarnente expresada mediante el árbol sintáctico abstracto
el árbol sintáctico mismo se pueda especificar mediante una
ve en el ejemplo siguiente.
Figura 6.5
(val = 31 * 42 = 1302)
Arbol sintactico abstracto
para ( 3 4 - 3 ) * 4 2 que
muestra los cálculor de
atributo de val para la
gramitica con atributos de
la tabla 6.2 o la tabla 6.5
Ejemplo 6.5
(val =
/
34
(val = 34)
-
(sal = 3)
Dada la gramática para expresiones aritméticas enteras simples del ejemplo 6.2 (página
264), podemos definir un árbol sintáctico abstracto para expresiones mediante la gramática
con atributos dada en la tabla 6.6. En esta gramática con atributos utilizamos dos funciones
auxiliares rnkOpNode y mkillumNotle. La función mkOpNode toma tres parámetros (un
token de operador y dos árboles sintácticos) y construye un nuevo nodo de árbol cuya etiqueta de operador es e1 primer pi~rámetro.y cuyos hijos son el segundo y tercer parámctros.
La función tnkiVurnNocfe toma u n parámetro (un valor numérico) y construye u n nodo hoja
CAP. 6 1 ANALISIS SEHANTICO
representando un número con ese valor. En la tabla 6.6 escribimos el valor numérico c
número.le-xvd para indicar que es construido por el analizador léxico. De hecho, éste
dria ser el valor numérico real del número, o su representación de cadena, dependiendo
la implementación. (Compare las ecuaciones de la tabla 6.6 con la construcciún descen
te recursiva del irbol sintáctico de TlNY en el apéndice B.)
Tabla 6.6
Gramática con atributos para
árboles sintácticos abstractos
o expresiones aritméticas
enteras simples
Regla gramatical
expi -r expz
+ term
erp
- trrm
--,e.cp2
etp -+ term
ler1nl -+ f e m 2 *fili;ror
terrn +fuctor
factor -+ ( exp )
,f'ocror -+número
Reglas semanticas
erp E .tree =
mkOph%>rle(+. erp2 .tree, term.rrre)
a p i .Irre =
mkOpN<ide(-, expz .tree, rerm.tree)
exptree = termmre
ferm, .&e =
mkOpNoiie(*. term2 .tree, firctor. tree)
term.rree = fuctor.tree
factor.tree = exp.tree
factor.tree =
mkNumNodefnúmero.lrxva1)
Una cuestión que es fundamental para la especificación de atributos utilizando gr
inática con atributos es: jeómo podemos estar seguros de que una gramática con atrib
tos en particular es consistente y completa, es decir, que define de manera única los a
butos dados? La respuesta simple es que hasta ahora no podemos. Ésta es una cuestió
similar a la de determinar si una gramática es ambigua. En la práctica, son los algoritm
de análisis sintáctico que utilizamos lo que determina la aceptabilidad de una gramáti
y ocurre una situación semejante en el caso de las gramáticas con atributos. De este ni
do, los métodos algorítmicos para calcular atributos que estudiaremos en la siguie
sección determinarán si una gramática con atributos es adecuada para definir los valore
de atributos.
62 ALGORITMOS PARA CALCULO DE ATRIBUTOS
En esta sección estudiaremos las maneras en que se puede utilizar una gramática con a
butos como base para nn'compilador a fin de calcular y utilizar los atributos definidos p
las ecuaciones de la gramática con atributos. Fundamentalmente, esto equivale a convertir las
ecuaciones de atributo en reglas de cálculo. De este modo, la ecuación de attibiito
se visualiza como una asignación del valor de la expresión funcional en el lado derecho
para el atributo X,.ai. Para que esto tenga éxito, los valores de todos los atributos que aparecen
en el lado derecho ya deben existir. Este requerimiento se puede pasar por alto en la especificaciitn de las gramáticas con ;itributos, donde las ecuaciones se pueden escribir en orden
arbitrario sin afectar su validez. El problema de iniplementar un :ilgoritnio corre.;pr)ndicnte
Algoritmos para calculo de atributos
27 1
n una gramática con atributos consiste ante todo en hallar un orden para la evaluación y asig-
n:ición de atributos que aseguran que todos los valores.de atributo titiliz;id«s en cada cálciilo
estén disponibles cuando éste se realice. Las mismas ecuaciones de atrihuto indican las limitantes de orden en el cálculo de los atributos, y La primera tarea es hacer explícitas las liinit;intes
de orden utilizando gráficas tlirigitlas para representarlas. Estas gráficas dirigidas se conocen
como gráficas de deperidcncia.
6.2.1 Gráficas de dependencia y orden de evaluación
Dada una gramática con atributos, cada regla gramatical tiene una gráfica de dependencia
asociada. Esta gráfica tiene un nodo etiquetado por cada atributo Xl.ci, de cada símbolo en la
regla gramatical, y para cada ecuación de atributo
asociada con la regla graniatical hay un borde desde cada nodo X,,.u, en el lado derecho
para el nodo Xi.czj (q~ieexpresa la dependencia de Xi.q respecto a X,,.ci,). Dada una cadena
legal en el lenguaje generado por la gramática libre de contexto, la gráfica de dependencia
de la cadena es la unión de las gráficas de dependencia de las reglas gramaticales que representan cada nodo (no hoja) del árbol de análisis gramatical de la cadena.
Al dibujar la gráfica de dependencia para cada regla gramatical o cadena, los nodos asociados con cada síinbolo X son trzados en u11 grupo, de manera que las dependencias se
puedan visualizar como estructuras alrededor de un árbol de análisis gramatical.
Ejemplo 6.6
Considere la gramática del ejemplo 6.1. con la gramática con atributos como se proporciona en la tabla 6. l . Hay sólo ~ i natrihuto, w l , de modo que
. .para cada sí~nboloexiste sólo un
nodo en cada gráfica de dependencia, correspondiente a su atributo val. La regla gramatical
número, -+ rihnero2 digito tiene la ecuació~ide atributo asociada simple
izlhzero, .red = número2 .val * 10
+ clígito.vn1
La gráfica de dependencia para esta regla gramatical es
(En futuras gráficas de dependencia omitiremos los subíndices para símbolos repetidos, ya
que la representación gráfica distingue claramente las diferentes ocurrencias como nodos
distintos.)
De manera semejante, la gráfica de dependencia para la regla gratnatical rtcímero -+
dígito con la ecuacih de atributo r~rítnero.vcil= clígito.vc11es
WP. 6 1 ANALISIS SEN~NTICO
Para las reglas gramaticales restantes de la brma dígiro -+ D las gráficas de depend
son triviales (no hay bordes), ya que dígito.vu1 se calcula directamente del lado derec
la regla.
Finalmente, la cadena 345 tiene la siguiente gráfica de dependencia correspondien
su árbol de análisis gramatical (véase la figura 6.1):
Ejemplo 6.7
Considere la gramática del ejemplo 6.3 con la gramática con atributos para el atrib
tltype que se da en la tabla 6.3. En este ejemplo la regla gramatical vur-lisr, + i d , vur-11
tiene las dos ecuaciones de atributo asociadas
y la gráfica de dependencia
De manera similar, la regla gramatical vcir-list + i d tiene la gráfica de dependencia
var-1ist.dtype
1
i d .&pe
Las dos reglas cype -+ i n t y type -+ íloat tienen gráficas de dependencia trivides.
Finalmente, la regla decl i type wr-list con la ecuación asociada vnr-1ist.dtype =
tvpe.ilt)ye tiene la gráfica de dependencia
En este caso, como &el no está involncrada de manera directa en la gráfica de dependencia, no está conipletamente claro cuál regla gramatical tiene su gráfica asociada a ella. Por
esta razón (y algunas otras razones que comentaremos posteriormente). a menudo dibujarnos la griifica de dependencia sribrepucsta sobre un segrncnto de &bol de análisis gramatical correspondiente a la regla jrarnatical. De este modo. la gráfica (fe &pendencia anterior
se p c d e dibujar como
Algoritmos para dltulo de atributos
Advierta
y esto hace más clara la regla gramatical a la que está asociada la denendencia.
,
tariibién que cuando dibujamos los nodos del árbol de análisis gramatical stiprimirrios la notación punto para los atributos, y representamos los atribiitos de cada nodo escribi6ndolos a
continuaci6n de su nodo asociado. Así, la primera gráfica de dependencia en esle ejemplo
tamhih se puede escribir como
Finalmente, la grifica de dependencia para La cüdenü f loat x, y es
float
dype
id
(X)
r
~Irype
1
úfype
vnr-li~t
1
1
id
(Y)
Considere la gramática de números de base del ejerriplo 6.4 con la gramática con atributos
para los atributos buse y vol como se dan en la tabla 6.4. Dibujaremos las gráficas de dependencia para las cuatro reglas graniaticales
num-base + num carbuse
num -3 nurn dígito
nurn 4 digito
dígito 4 9
y para la cadena 3450, cuyo árbol de aiiálisis gramatical se muestra en la figura 6.4.
Comenzamos con la gráfica de dependencia para la regla gramaticül rrutrr-btrse -+ rrirm
LY~~/xJ.s~:
CAP. 6 1 ANÁLISIS
IEMANTICO
Esta gráfica expresa las dependencias de las dos ecuaciones asociadas rturn-huse.vul = num.
y iiutri.hase = carbuse.base.
A continuaci6n dibu&imos la gráfica de dependencia correspondiente a la regla gram
tical trwn -. num dígito:
E ~ t agráfica erpre\a las dependencias de las tres ecuaciones de atributo
nurn I .vctl =
if dígito.vu1 = error or mm2 . v d = error
then error
else num2 .val * nrtml .base dígito. vul
n m z .base = nuin, .buse
~lí,qito.hase= numl .buse
+
La gráfica de dependencia para la regla gramatical num -+ dígito es similar:
huse
num
vul
Finalmente, dibujarnos la gráfica de dependencia para la regla gramatical tlígito -i 9:
Esta gráfica expresa la dependencia creada por la ecuación digito.vul = if ciígito.base =
then error else 9, a saber, que dígito.ial depende de dígito.buse (es parte de la prueba en
expresión if). S610 resta dibujar la gráfica de dependencia para la cadena 3450. Esto se h
ce en la figura 6.6.
/S:C
.. .
- .
.c~rbuse2se
Figura 6 6
nrirn-base
val
,
Gráfica de dependencia
para la cadena 3450
(e'jemplo 6.8)
,
1
base
dígito
1
1
hose
t
ririm
l
i.iil
-
1
hase
I
díi'ito
l
1
1
4
r.d
5
val
O
Algoritmos para cálculo de atributos
275
Supongamos &ora que deseamos determinar un algoritn~oque calcula los citrihutos de
una gramática con atributos utilizando las ecuaciones de atributo como reglas para el cálculo. Dada una cadena particular de tokens para traducirse, la gráfica de dependencia del
árbol de anilisis gramatical de la cadena da un conjunto de liniitantes de orden bajo el cual
el algoritmo debe operar para calcular los atributos de esa cadena. En realidad, cualquier algoritmo debe calcular el atributo en cada nodo de la gráfica de dependencia cirires tie intentar catcular cualquier atrihuto sucesor. Un orden del recorrido de la gráfica de dependencia
que obedece esta restricción se denomina cIasificaciFn topológica. y una muy conocida
condición necesaria y suficiente para que exista una clasificación topológica es que la gráfica debe ser acíclica. Tales gráficas se conocen como gráficas acíclieas dirigidas, o DAG,
por sus siglas en inglés.
La gráfica de dependencia de la figura 6.6 es una DAG. En la figura 6.7 numeramos los
nodos de la gráfica (y eliminamos el árbol de análisis gramatical subyacente para facilitrir
la visualización). Una clasificación topológica está dada por el orden de nodo en el que los
nodos están numerados. Otra clasificación topológica está dada por el orden
Una cuestión que wrge en el uso de una clasificación topológica de la gráfica de dependencia para calcular valores de atributo es cómo se encuentran los valores de atributo en las
raíces de la gráfica (una raíz de una gráfica es un nodo que no tiene predecesores). En la fi. ~ valores de atributo en
gura 6.7, los nodos 1,.6, 9 y 12 son todos raíces de la g r á f i ~ aLos
estos nodos no dependen de ningún otro atributo, entonces se deben calcular utilizrindo la
información que está directamente disponible. Esta información se encuentra a rnenudo en
forma de tokens que son hijos de los correspondientes nodos de %bol de artálisis gramatical. En la figura 6.7, por ejemplo, el val del nodo 6 depende del token 3 que es el hijo del
nodo digito a1 que val corresponde (véase la figura 6.6). De este modo. e1 valor de atributo
en el nodo 6 es 3. Todos esos valores raíz necesitan ser calciilahlei antes del cálculo de conlquier otro valor de atributo. 'Tales cálculos se realizan con frecuencia mediante los :inrilizadores léxico 0 sintáctico.
Es posible basar un algoritmo para análisis de atributos en una construcción de la gr
ca de dependencia durante la compilación y una clasifimción topológica subsiguiente d
gráfica de dependencia con el fin de determinar un orden para la ev:iluacióu de los a
tos. Como la construcción de la gráfica de dependencia está basada en el irbol de an
gramatical específico en tiempo de compilación, este método se conoce en ocasiones c
método de &bol de análisis yametical. Dicho método es capaz de evaluar los atri
en cualquier gramttica con atributos que sea no circular, es decir, una gramática con
butos para la cual toda gráfica de dependencia posible es acíciica.
Existen varios problema con este método. En primer lugar, tenemos la complejidad
gada requerida por La constmcción en tiempo de compilación de la gráfica de depende
En segundo lugar, aunque este método puede determinar si una gráfica de dependenc
acíclica en tiempo de compilación, por lo general es inadecuado esperar hasta el mom
de compilaci6n para descuhrir un círculo vicioso, ya que es casi seguro que este represen
un error en la gramática con atributos original.En rralidad, para evitarlo debería pro
por adelantado una gramática con ahbutos. Existe un algoritmo para hacer esto (véa
sección de notas y referencias), pero es un algoritmo de tiempo exponencial. Natural
este algoritmo sólo necesita ejecutarse una vez. en el tiempo de construcción del cornpil
de modo que éste no es un argumento aplastante en su contra (al menos para los pro
tos de la constrncción del compilador). La complejidad de este método es un argumento
convincente.
La alternativa al método anterior para evaluación de atributos, por la que opta prác
mente todo compilador, es que el escritor de compiladores analice la gramática con atri
tos y fije un orden para La evaluación de atributos en tiempo de constmcción de compil
Aunque este método todavía utiliza el &bol de análisis gramatical como guía para evalu
de atributos, el método se conoce como método basado en reglas, ya que depende de un a
lisis de las ecuaciones de atributo, o reglas semánticas. Las gramáticas con atributos para
cuales un orden de evaluación de atributos se puede fijar en tiempo de constmcción del co
pilador forman una clase que es menos general que ta clase de todas las gramáticas con a
hutos no circulares, pero en la práctica todas las gramáticas con atributos razonables tien
esta propiedad. A menudo se les denomina gramáticas con atributos completamente
circulares. Continuaremos con una exposición de los algoritmos basados en reglas p
esta clase de gramáticas con atributos después del ejemplo siguiente.
Ejemplo 6.10
Considere nuevamente las gráficas de dependencia del ejemplo 6.8 y las clasificaciones
pológicas de la gráfica de dependencia analizada en el ejemplo 6.9 (vkase la figura 6.7). A
cuando los nodos 6,9 y 12 de la figura 6.7 son raíces de la DAG, y, por consiguiente, t
podría ocurrir al principio de una clasificaci<ín topológica, en un método basado en r
esto no es posible. La razón es que cualquier val puede depender de la base de su nod
gito asociado, si e1 token correspondiente es 8 o 9. Por ejemplo, la gráfica de dependenci
para digito -+ 9 es
Así, en la figura 6.7 el nodo 6 puede haber dependido del nodo S, el nodo 9 puede haber dependido del nodo 8, y el nodo 12 puede haber dependido del nodo I l . En un método basa-
d« en reglas estos nodos serían forzados a ser evaluados después que cualquier n d o del que
ellos pudier:rn dcpznder. Por lo tanto. kin orden de evaluaci6n que. itigatnos, evaluara prinie-
Algoñtmos para calculo de atubutos
277
ro el nodo 12 (véase el ejemplo 6.91, aunque es correcto para el árbol particular de la ligura 6.7, no es un orden vilido para un algoritmo basado en reglas, puesto que violaría el orden pira otros ;írbolcs de análisis grinnatical.
(S
6.2.2 Atributos sintetizados y heredados
La evaluacicín de atributos bas:tdos en reglas depende de un recorrido cxplícito o iniplícito
del irbol de análisis gramatical o del árbol sintáctico. Diferentes clases de recorridos varian
en potencia en términos de las clases de dependencias de atributo que se pueden manejar.
Para estudiar las diferencias primero debemos clasificar los atributos mediante las clases de
dependencias que exhiben. La clase más simple de dependencia a manejar es la de los atributos sintetizados, que se define a continuación.
..
g:9. Definición
*..
t.
Un atributo es sintetizado si todas sus dependencias apuntan de hijo a padre en el árbol de
ana'1' t s.'~ sgramatical. De manerd equivalente, un atributo u es sintetizado si, dada una regla
gramatical A + XIX2 . . . X,,. la única ecuación de atributo asociada con una a en el lado
izquierdo es de la forma
Una gramática con atributos en la que todos los atributos están sintetizados se conoce como
gramática con atributos S.
Ya vimos varios ejemplos de atributos sintetizados y gramáticas con atributos S. El atributo val de números en el ejemplo 6.1 está sintetizado (véanse las gráficas de dependencia
en el ejemplo 6.6, página 271), como el atributo vtil de las expresiones aritméticas enteras
simples en el ejemplo 6.2, página 264.
Dado que un árbol de análisis gramatical o árbol sintactico se ha construido mediante un analizador sintictico, los valores de atributo de una gramática con atributos S se
pueden calcular median. un recorrido ascendente, o postorden, del árbol. Esto se puede
expresar mediante el siguiente pseudocódigo para un evalnador de atributo postorden recursivo:
procedure PastEva~( T: treeiiocle );
hegin
for cada hijo C de T do
Po.stEvul( C );
calcule todos los atributos sintetizados de T;
end;
Ejemplo 6.1 1
<:onsiileremos la gratnática con atributos del ejcriiplo 6.2 para expresiones aritméticas sirnpies, con el atributo sintetizado vid. Dada la estructura siguiente para un árbol siniiclico 4 tal
corno cl de la i'igt~ra6 5. página 261))
typedef
typedef
typedef
f
enum (Pius,Minus,Times) OpKind;
enum {OpKind,ConstKind} ExpKind;
struct streenode
ExpKind kind;
OgKind og;
struct streenode *lchild,*rchild;
int val;
> STreeNode;
typedef STreeNode *SyntaxTree;
el pseudocódigo PostEval se traduciría en el ccídigo C de la figura 6.8 para un recomd
izquierda a derecha.
Figura 6.8
Código C para el evaluador
de atributo postorden para
el ejemplo 6.1 1
void postEval(SyntaxTree t)
{ int tenp;
if (t->kind = OpKind)
( postEval(t-7lchild);
postEval(t-zrchild);
switch (t->op)
{ case Plus:
t->val = t->lchild->val + t->rchild->val;
break;
case Minus:
t->val = t->lchild->val t->rchild->val;
break;
case Minus:
t->val = t->lchild->val * t->rchild->val;
break;
1 / * end switch * /
1 / * end if * /
/ * end postEva1 * /
-
Por supuesto, no todos los atributos son sintetizados.
Definición
Un atributo no sintetilado \e denomina heredado.
Ejemplos de atributos heredados que ya hemos visto incluyen al atributo dtwe del
ejernplo 6.3 (página 265) y cl atributo base del ejemplo 6.4 (página 266). Los atributos heredados tienen dependencias que fiuyen ya sea de padre a hijos en el árbol de análisis gramatical (a lo que deben su nombre) o de hermano a hermano. Las figuras 6.9(a) y ib) ilustran las dos categorías hásicas de dependencia de atributos heredados. Cada una de estas
c~
'ises
.. . de dependencia se preserttrtn en el ejemplo 6.7 para el atributo cbpe L.a r a h cle que
;unhas se clasifiquen corno herellatlas es que en algorilmos para calcular atribut«s heredados, la hereucia entre hermanos a itietiudo se implcmenta de tal tnaiiera que los valores
de atributo se pasen de herniano a herntano u t r c d s del padre. En efec&, esto es necesario
\ i los bordes del irbol siiirácticcr d o apuntan ile padre a hijo (de este tnoclo un hijo no puede
279
Algoritmos para calculo de atributos
tener acceso a su padre 0 hermanos directamente). Por otra p;rrte, s i algunas estructuras en
un irbol sintáctico se implementan a través de apuntadores hermanos, entonces la herencia
entre hermanos puede coniinuar directarnerite a lo largo de una cadena de herr~iario,corno
se representa en la figura h.%c).
(a) Herencia de padres a hermanos
(b) Herencia de hermano a hermano
(c) Herencia hermana vía apuntadores hermanos
Volvemos ahora a Los métodos algorítmicos püra La evaluación de atrihutos heredados.
Los atributos here&ados se pueden calcular nlediante un recorrido preorden, o combinado
preordenlenorden del árbol de análisis gramatical o del árbol sintáctica. De manera esquemática. esto se puede representar mediante el siguiente pseudocódigo:
procedure PreEv<d( T: tremode );
begin
for cudu hijo C de T do
cczlcule todos los cztrihutos heredados ú e C;
PrcEvrzl( C );
end;
A diferencia de los atributos sintetizados, el orden en e l que se calculan los atrihutos heredados de los hijos es importante, porque los atributos heredados pueden tener dependencias
entre los atributos de los hijos. El orden en el que se visita cada hijo C de Ten e l pseudocódigo precedente debe, por lo tanto, adherirse a cualquier requerimiento de estas dependencias. En los dos ejemplos siguientes demostrareinos esto utilizando los atrihutos heredados
clt)~pey base de 10s ejemplos anteriores.
Ejemplo 6.12
Considere la gramática del ejemplo 6.3 que iiene e l atributo heredado cliy)e y cuyas gráficas de dependencia se proporcionan en e l ejemplo 6.7, página 272 (véase la gramática con
atributos en la tabla 6.3, pigina 266). Sup«ng;irnos primero que un árbol de anilisis gramatical se ha construi<lo explicit:imente a partir de la gramática, la que repetimos aquí para
poder hacer referencia a ella con facilidxl:
-
tlccl -,tvpe tur-li.st
int / f loat
i:cii.-Iist 4 id, ~<ri.-li.st
1 id
ypr
CAP. 6 1 ANALISIS SEIIANTICO
Entonces el pseudocódigo para un procedimiento recursivo que calcula el atributo cl
para todos los nodos requeridos se muestra a continuación:
prncedure EvalType ( T: treenode );
begin
case clasenodo de T of
decl:
EvalType ( tipo hijo de T );
Asignar dvpe de tipo hijo de T a vur-list hija de T;
EvnlType ( var-list hija de T );
m'<:
if hijo (le T = int then T.drype := intcger
else T.dtype := real;
var-list:
asignar T.diype at-primer hijo de T;
if tercer hijo de T no es ni1 then
asignar Tdtype u1 tercer hijo;
EvalType ( tercer hijo de T );
end case;
end EvalType;
Advierta cómo operaciones preorden y en orden están mezcladas, dependiendo de la cl
diferente de nodo que se está procesando. Por ejemplo, un nodo decl requiere que el d
de su primer hijo se calcule primero y después se asigne a su segundo hijo antes de una
mada recursiva de EvalType a ese hijo; éste es un proceso en orden. Por otra parte, un no
wrr-list asigna drype a sus hijos antes de hacer cualquier llamada recursiva; éste es un p
ceso preorden.
En la figura 6.10 mostramos el árbol de analisis gramatical para la cadena f loat
y junto con las gráficas de dependencia para el atributo drype, y numeramos los nodos e
orden en e1 cual se calcula drype de acuerdo con el pseudocódigo anterior.
Figura 6.10
Arbol de anilisis gramatical
que muestra orden
de recorrido pan el
ejemplo 6.12
... .
,C
drvpe
II
II
float
_
-.
-.
-.
-.
/'/y..decl
d
e
uar-list@
'--.
8
'
&pe
id
ix)
@
,
&pe
1
dope
uar-list @
1
i
id
@
(Yf
Para proporcionar una forma completamente concreta a este ejemplo, convertimos
el pseudocódigo anterior a código C real. También, en vez de utilizar un 6rbol de anrilisis gramatical explícito. suponemos que se ha construido un árbol sintáctico, en el que
~ur-listse representa mediante una lista de hermanos de nodos i d . Entonces una cadena de declaración tal como f loat x, y tendría el árbol sintáctico (compare esto con la
figura 6.10)
Algoritmos para calculo de atributos
y el orden de evaluación de los hijos del nodo decl sería de izquierda a derecha (primero el
nodo type, luego el nodo x y finalmente el nodo y ) . Observe que en este árbol ya incluimos
el dtype del nodo type; suponemos que esto se ha calculado previamente durante el análisis
sintáctico.
La estructura del jrbol sintáctico la proporcionan las siguientes declaraciones en C:
typedef enum {decl,type,id) nodekind;
typedef enum (integer,realf typekind;
typedef struct treeNode
( nodekind kind;
struct treeNode
lchild, * rchild, * aibling;
typekind dtype;
/ * para nodos id y type * /
char * name;
/ * 8610 para nodos id */
* SyntaxTree;
El procedimiento EvulType tiene ahora el código correspondiente en C:
void evalrype (SyntaxTree t)
{ switch (t->kind)
( case decl:
t->rchild->dtype= t-plchild->dtype;
evalType(t->rchild);
break;
case id:
if (t->sibling l = NULL)
{ t->aibling->dtype= t->dtype;
evalType(t->sibling);
>
1
break;
) / * end switch * /
/ * end evalType * /
Este ccítiigo se puede simplificar al siguiente procedimiento no recursivo, que funciona enteramente al nivel del nodo raíz (decl):
282
CAP. 6 1 ANALISIS SEMANTICO
void evalTne ( SyntaxTree t )
( if (t->kind -= decl)
f SyntaxTree p = t->rchild;
P - > d t m e = t->lchild->dtype;
while (0->sibling ! = MILL)
( p->sibling->dtype = p->dtype;
p = p->sibling;
1
1 / * end if * /
). / * end evalType * /
Ejemplo 6.13
Considere la gramática del ejemplo 6.4 (página ?66), que tiene el atributo heredado base
gráficas de dependencia están dadas en el ejemplo 6.8, pígina 273). Repetiremos la gr
tica de ese ejemplo aquí:
nirm-base 1; num curbuse
carbase 4 o d
tium 1; num digito cligito
/
/
dígito-.0/1/2/3~4/5/6/7/8/9
Esta gramfitica tiene dos nuevas características. En primer lugar, existen dos atributos,
atributo sintetizado val y cl atributo heredado base, del cual depende val. En segundo Lu
el atributo base es heredado del hijo derecho al hijo izquierdo de un num-base (es de
desde carbase hasta num). Así, en este caso debemos evaluar los hijos de un nztm-base
derecha a izquierda en lugar de izquierda a derecha. Procederemos a dar el pseudocód
para un procedimiento EvulWithBase que calcula tanto base como val. En este caso buse
calcula en preorden y vul en postorden durante un paso simple (en breve comentaremos
cuestitín de pasos y atributos múltiples). El pseudocódigo se presenta a continuación (vd
la gramática con atributos en la tabla 6.4, página 267).
procedure EvalWithBuse ( T : treenode );
hegin
case clusenodo de T of
num-base:
EvalWithBase ( hijo derecho de T );
asignur base del hijo derecho de T a la base del hijo izquierdo;
EvulWi?hBase ( hijo izquierdo de T j;
asignar valor de hijo izquierdo de T a T.vul;
tz11m:
asignar T.base a la buse del hijo izquieráo de T;
EvulWibhBase ( hijo derecho de T );
if hijo derecho de T no es ni1 then
nsignur 7 b m e a 1 1 huse del hijo derecho (le T;
EvairlCVithBase ( hijo derecho de T ) ;
if wlores de hijos izquierdo y derecho # error then
T.vd := T.bnse*(vulor de hijo izquier(1o) + w l o r de hijo rlrrt.clzo
else % 1x11 : error;
-
Algontmor para cálculo de atributos
c.trrhose:
if hijo de T := o then l h a s e :-. 8
else T.h<ise:= 10;
tlQit0:
if T.hase = S and &jo de T = 8 or 9 then T.wd := error
else %val := rirtntvrd ( hijo de 7' );
end case;
end Evc~lWithB<i.se;
De,iamos para 10s ejercicios la construcción de las declaraciones en C en el caso de un árbol
sintáctico apropiado y tina traducción del pseudocódigo para E~~ulWithRnse
a código en C.
§
En las gramjticas con atributos con combinaciones de atnbtítos sintetizados y heredados,
si los atributos sintetizados dependen de los atributos heredados (además de otros ütrihtitos
sintetimios). pero los atributos heredados no dependen de ningún atributo sintetiza&, entonces es posible calcular todvs los atributos en un solo paso sobre el árbol de análisis gramatical o el árbol sint2ictico. El ejemplo anterior es un buen ejemplo de cómo se hace esto,
y el orden de evaluación puede resurnirse combinando los procedimientos en pseudoc6digo
PostEvtrl y PreEscil:
hegin
for radu hijo C de T do
c<rlcultrtodos los uirihui»s Irereclridos de C;
Comhiizc4Eial ( C );
rnlccdr roilor los ntnbutos rrnrt,tr:~rdoc de T;
end;
Las situiiciones en las cuales los atributos hcredatlos dependen de atributos sintetizados son
más complejas y requieren de mis de un pííso sobre los árboles de análisis gran1:itical « sinkíctico, como lo rnuestra el siguiente ejemplo.
Ejemplo 6.14
Considere la siguiente versión simple de una gramática de exprcsión:
Esta gramática tiene una sola operación, de división, indica& por el toke~i/. Tarribién tiene
(los ver,iortes de núrneros: números enteros cvmpvestos de secuencias de dígitos. las c~mies
se indican iiietiiürite el token num y números de punto flotante, que se indican por medio
del token num.num. La idea de esla gramática ei que las operaciones se pueden iorcrpretar de manera diferente, dependiendo de si son operaciones de pu~itoflotante o esirict:imcnte entenis. La división, en pxticulx, es bastante difcrentc. clepcrldiciido (le j i se
permiten las í'rxxiones. Si no. la división con frecuencia se llama opci-acih div. y cl valor
i!e 5 / 4 cs 5 tfiv 4 = 1 . S i se !efierc a I U divisifin de punto Slotaiite. cntonccs 5 1 4 time u n
b:lIor tic l.?.
Siipong;imvs :hora qiic nrl Ienpijc de progrnii;;icii'w requiere eupri.sii~iicsií~i.~cI;:d;is
CAP. 6 1
AHALISISSEHANTICO
.
5 / 2/ 2 O (suponiendo la asociatividad por la izquierda de la división) es 1.25, rnientr
el significado de 5/2/2 es
Para describir estas semánticas se requieren tres a
tos: un atributo booleano sintetizado isFloat, que indica si alguna parte de una exp
tiene un valor de punto tlotante; un atributo heredado efype, con dos valores int y
que da el tipo de cada subexpresión y que depende de isFlour; y finalmente, el val c
lado de cada subexpresión, que depende del efype heredado. Esta situación también re
re que se identifique la expresión de nivel superior (de nianera que sepamos que no ha
subexpresiones por considerar). Hacemos esto aumentando la gramática con un sím
inicial:
S 4 exp
Las ecuaciones de atributo se dan en la tabla 6.7. En las ecuaciones para la regla gra
e.xp -+ un utilizamos Float (num.vul) para indicar una función que convierte el val
tero num.vul en un valor de punto flotante. También empleamos la 1 para la divisi6n
to flotante y div para la división entera.
Los atributos isFloat, eiype y val en este ejemplo se pueden calcular mediante dos
sos sobre el árbol de análisis gramatical o el árbol sintáctico. El primer paso calcula el a
..:.3 %
buto sintetizado isFloat mediante un recorrido postorden. El segundo paso calcula tanto cly;d
atributo heredado etype como el atributo sintetizado val en un recorrido combinado pre
den/postorden. Dejamos la descripción de estos pasos y los correspondientes cálculos
atributo para la expresión 5/2/2.0 para los ejercicios, además de la construcción del pseu
cúdigo o código en C para efectuar los pasos sucesivos en el árbol sintáctico.
Tabla 6 1
Gramatica con atributos
para el ejemplo 6.14
Regla gramatical
Reglas sernánticas
S -iexp
erp.e@pe =
if exp.isFloat thenfloat else int
S.val = exp val
exp, .isFlout =
exp2 .isFlout or exp, .rsFloat
rxp2 .e@pe = expl espe
erp, rtype = expl etype
exp, .val =
ii'exp, .e@pe = rnt
then exp2 .val div exp, .val
else expz .val /exp3 .val
e.xp.isFloat = false
exp.val =
i f eqetype = tnt then num .val
else Floarfnum.val )
exp.isFloat = true
exp.val = num. num .val
exp, -, rxpz / erp3
exp -+ num
exp -t num.num
I
7. Esta regla no es la niisnia regla que la empleada en C. Por ejemplo, cl valor de 5 / 2 / 2 . O es 1.0
en C. no 1.25.
31
1
9
Algoritmos para calculo de atr~butos
285
6.2.3 Atributos como parametros y valores devueltos
Con frecuencia al calcular atributos tiene sentido utilizar paránictros y valores de iiinción devueltos para comunicar los valores de atributo en vez de alinacenarlos corno cnnipos en una
estructura de registro de {íbol sintáctico. Esto es p~uticulmentecierto si niuchos de los valores de atributo son los misinos o se utilizan scílo de manera temporal para calcular otros valores de atributo. En este caso no tiene niucho sentido eniplear el espacio en el árbol sintáctico para almacenar valores de atributo en cada nodo. [Jn procedimiento de recorrido recursivo
simple que calcule atributos heredados en preorden y atributos sintetizados en postorden puede, de hecho, pmar los valores de atributos heredados como parámetms a llamadas recursivas de
hijos y recibir valores de atributos sintetizados como valores devueltos de esas mismas llamadas. En capítulos anteriores ya se presrntiuon varios ejemplos de esto. En particular, el cilculo
del valor sintetizado de una expresión aritmética se puede hacer mediante un procedimiento
de análisis sintáctico recursivo que devuelve el valor de la expresión actual. De manera similar, el árbol sintáctico como un atributo sintetizado debe él mismo ser calculado mediante un
valor devuelto durante el análisis sintáctico. puesto que hasta que se haya c«nstrnid», no existe todavía estructura de datos en la cual pueda registrarse a sí mismo como un atributo.
En situaciones más complejas, como cuando debe ser devnelto más d e u n atributo
sintetizado, puede ser necesario emplear una estrnctura de registro o unión como un valor
devuelto, o puede dividirse el procedimiento recursivo en varios procedimientos para cubrir
los diferentes casos. Ilustramos esto con un ejemplo.
Ejemplo 6.15
Considere el procedimiento recursivo EvulWithBu,se del ejernplo 6.13. En este procedimiento
el atributo base de un número se calcula sólo una vez y entonces se utiliza p m todos los cálculos subsiguientes del atributo vul. De manera semejante, el atributo vtrl de una parte del número
se emplea sólo como algo tempord en el cálculo del valor del número completo. 'Tiene sentido
convertir a base en un parámetro (como un atributo heredado) y a r'ul en un valor devuelto. El
procedimiento modificado EvulWitftBuse queda conlo se presenta a continuación:
function EvdWithBuse ( T: treenode; buse: integer ): integer;
var ternI), temp2: integer;
begin
case clusenodo de T of
num-buse:
rerrzp := EvulWithBase ( hijo derecho de T ) ;
return EvalCl/itlzBuse ( hijo izquierdo de T, temp );
num:
ternp := EvcrlWithBase ( hijo irqctierdu de T, huse );
if hijo derecho de T no es ni1 then
tentp2 := Evul WithBuse ( hijo derecho de T, huse );
if temp r f error and te1np2 J. error then
return ba.se*teinp + temp2
else return error;
else return temp;
curlmse:
if hijo de T = o then return 8
else return 10;
digiro:
if huse = 8 and hijo de T = 8 or 9 then return error
else return riurnvcrl ( hijo de T );
end ease;
end f~vtrlWithHtrse;
EnP. 6 1
anAua nnhwrico
Naturalmente, esto funciona sólo porque el aiributo base y el atributo vul tienen el mi
tipo de datos integer o "entero", ya que en un caso EvulWithBase devuelve el atributo
(cuando el nodo del árbol de análisis gramatical es un nodo cczrhuse) y en los otros c
EvdWithBase devuelve el atributo val. También es algo irregular que la primera Ilam
a EvalWithBase (en el nodo del árbol de análisis gramatical num-hme raiz) deba tener
valor base facilitado, aun cuando todavía no exista, y lo cual es ignorado posteriorme
Por ejemplo, para iniciar el cálculo se tendría que hacer una llamada como
EvulWithBase(nodorraiz,O);
con un valor de base de prueba O. Por lo tanto, sena más racional distinguir tres casos: e
so num-base, el caso carbuse y el caso num y dígito, así como escribir tres procedimie
separados para estos tres casos. El pseudoc6digo aparecería eotonces como se muestr
enseguida:
function EvalBusetlNutn ( R treenode ): integer;
(* sólo llanzuilo en nodo rciiz *)
begin
return EvalNum ( hijo izquierdo de T, EvalBuse(hijo derecho de 7') );
end EvalBasedNuin;
function EvalBase ( R treenode ): integer;
(* sólo llanziido en nodo cahase *)
hegin
if hijo de T = o then return 8
else return 10;
end EvalBase;
function EvalNum ( T: rreenode; base: integer ): integer;
var tetnp, remp2: integer;
hegin
case clu.senodo de T of
nunz:
ternp := EvalCVithBuse ( hijo izquierdo de T, base );
if hijo derecho (le T no es ni1 (nulo)then
temp2 := EvcilI.VithBuse ('hijoderecho de T, base );
if temp i:error and femp2 J: error then
return base*temp + remp2
else return error;
else return renzp;
rfígito:
if base = 8 and hijo de T = 8 or 9 then retnrn error
else return nnrnval( hijo de T );
end ease;
end EvdNum;
6.2.4 El uso de estructuras de datos externas para almacenar
valores de atributo
En aquellos casos en que los \alores de atributo no se prestan I'icilniente para el método
de los pxhnctros y vitlorcs <levueltos(lo que es cierto cn particular cucindo los v;ilores (le
airihuto tienen un;r eslrnctiira <ignilic;itivay pticde ser necesaria e11p~iiitositrhiit-ariosiiur:~i~te
Algoritmos para calculo de atributos
287
la txailucci<ín),totlavía puede no ser razonable almacenar valores de atributo en los nodos
dcl árbol sintáctico. En tales casos las estructuras de datos tales como las tablas de búsqueda, gráficas y otras estructuras pueden ser útiles para obtener el componamieiito correcto y
accesibilidad de los valores de atributo. La misina gramiitica con atributos se puede riiudificar para reflejar esta necesiclad reemplarartdo ecuaciones de :ttributo (que representan
asignaciones de valores de atributo) mediante llamadas a procedimientos que representan operxiones en las estructuras de (latos apropiadas que se emplea11para Iniinterier los valores
de atributo. Las reglas semánticas resultantes ya no representan entonces una gramática con
:itributus, pero todavía son útiles en la descripción de la seniáiitica de los atributos. rnieiitras
que la operación de los procedimieiitos se ve con claridad.
Considere el ejemplo anterior con el proceditnicnto Ev<iIWlhRttse empleantio parámetros y
valores devueltos. Corno el atributo h~r.se,una vez que se establece, se mantiene fijo el tiernpo que dure el cálculo del valor, podemos utilizar una variable no local para alniacenar su
valor, cn ver. de pasarlo como un parámetro en cada ocasión. (Si htrse no estuviera fijo, en
u n proceso recursivo así esto sería riesgoso o incluso incorrecto.) De este modo, podemos
alterar el pseud«cí>digo para EvcilWilhBose de La niünera siguiente:
function EiwlWithB<i.se( T: treenode 1: iizteger;
var tenzp, tetnp2: Nfteger;
begin
case clasenoiio de T uf
num-bme:
,SetBrise ( hijo derecho de T );
return EvdWithBme ( hijo iryi~ierilode T 1;
11 llm:
r m p := E d WithBnse ( htjo izquierdo de T );
if hijo clerecho de Tizo e.s ni) (rudo) then
temp2 := EvdWithBase ( hijo derecho de T );
if ternp i,
error and terrzpZ f error then
return bn,se*tetttp temp2
else return error;
else return temp;
+
dígito:
if base = 8 and hijo (le T = 8 or 9 then return error
else return I ~ U I I ~ V L I(I hijo de T ) ;
end case;
end EvtdWitizBuse;
procedure SetB~iseí T: rrtwoik );
begin
if hqo (le T = o ihei1 hir te := 8
else hare : 10;
end S e t R t ~ ~; r
-
tlquí sepafiimos el proceso de ;isignaci<ína la variable hri.re no local e11 cl procedimiento
SvrBcm, el cual sólo es llamado en u n nudo cvri>osc,.El resto del c6digo de I<vcilWiiltBtr.~i.
ci1toiiccs se refiere simple~ric~ite
;t hase de manera directa, sin pasarlo como u n p;irirnetr«.
'TainhiCn podenios cümhi:~rlas I-cgl;issernrinticni pitrn reflejar el uso ,le I;I wrii~blen o
Ii1c:il Í v w . 131 esre c;tso las reg1:rs tcntlríxi un aspecto :ilgo parecido ;I lo \igi~icntc.ilonile
uti1i~;iinosla ;isignxiríri p:tra iriiiic;ir de iiintlera explícita el cst;ihlccirriic~iiode la v:rri;lhlc
¡ l o 1,)caI h!i,W
EAP. 6 1 ANALISIS
SENANTICO
Regla gramatical
Reglas semCInticas
iwm-buse -+
mtm curhuse
curhase i o
curbase -r d
nurn~.-, numi dígiro
nurn-basc.i.111= riirm.vu1
etc.
huse := 8
base := 10
num, .val =
iP dfgitu.val = errur or numz.val = error
then error
else ntrmz.v d * buse + digiro.vid
etc.
Ahora base ya no es un atributo en e1 sentido en que lo henios utilizado hasta ahora,
reglas semánticas ya no fonnan una gramática con atributos. No obstante, si se sab
base es una variable con Las propiedades adecuadas, entonces estas reglas todavía de
de manera adecuada la semántica de un num-base para el escritor del compilador.
Uno de los ejemplos principales de una estructura de datos externas al árbol sintá
es la tabla de símbolos, que almacena atribntos asociados con procedimientos, variab
constantes declaradas en un programa. tJna tabla de símbolos es una estructura de dat
diccionario con operaciones tales como inserción, búsqueda y eliminación. En la sig
sección analizaremos cuestiones en torno a la tabla de símbolos en lenguajes de progr
ci6n típicos. Nos contentaremos en esta sección con el siguiente ejemplo simple.
Ejemplo 6.17
Considere la gramática con atributos de las declaraciones simples de la tabla 6.3 (pá
266) y el procedimiento de evaluación de atributo para esta gramática con atributos dad
el ejemplo 6.12 (página 279). Por lo general, la informacidn en las declaraciones se inse
en una tabla de símbolos utilizando los identificadores declarados como claves y almac
dos allí para una búsqueda posterior durante la traducción de otras partes del programa.
consiguiente, suponemos para esta gramática con atributos que existe una tabla de símb
que almacenará el nombre del identificador junto con su tipo de datos declarado, y
insertarán pares de nombre-tipo de datos en la tabla de símbolos mediante un proced~
to de inserción denominado insert, que se declara como se muestra a continuación:
procedure insert ( name: string; dtype: typekind);
De este modo, en vez de almacenar el tipo de datos de cada variable en el árbol sintácti
lo insertamos en la tabla de símbolos utilizando este procedimiento. También, puesto
cada declaración tiene sólo un tipo asociado, pdemos utilizar una variable no local par
macenar el dtype constante de cada declaración durante el procesamiento. Las reglas semá
ticas resultantes son las siguientes:
-
Regla gramatical
Reglas sernánticas
(lec1 jype vur-lis1
lypa -. i n t
t y ) e + f loat
wir-lirll * i d , vttr-lisb
vur-list-1 i d
dlype = Niteger
útype = reul
insert(id .rrunre, d@[)ef
inserffid.riumr, rltype)
Algoritmos para dlculo de atributos
289
En las llamadas a irrsert hemos utilizado i d . riurne para hacer referencia a la cadena de identificador, que suponemos es calculada por el analizador léxico o analizador sintáctico. Estas
reglas semánticas son m u y diferentes de la gramática con atributos correspondiente; la [-egla gramatical para una úecl no tiene, de hecho, ninguna regla semántica. Las dependencias
no están expresadas tan claramente, aunque salta a la vista que las reglas de type deben
procesarse antes que las reglas asociadas de vur-lisl, debido a que las llamadas a inserr dependen de dtype, lo que está establecido en las reglas de type.
El pseudocódigo coirespondiente a un procedimiento de evaluación de atributo EvuETvpe
es como se presenta a continuación (compare esto con el c6digo de la página 280):
procedure EvalType ( T: treenode 1;
begin
case clusenodo de T of
decl:
EvalType ( tipo hijo de T );
EvalTjpe ( var-list hijo de T );
type:
if hijo de T = i n t then dtype := uiteger
else dtype := real;
var-list:
insert(nombre del primer hljo de T, dtype)
if tercer hiJo de T no es nrl then
EvalType ( tercer hijo de T );
end case;
end EvalTvpe;
6.2.5 El cálculo de atributos durante el análisis sintáctico
Una cuestión que se presenta de manera natural es hasta qué punto los atributos se pueden
calcular al mismo tiempo que la etapa de análisis sintáctico, sin esperar a realizar pasos adicionales sobre el código fuente por medio de recorridos recursivos del árbol sintáctico. Esto es particularmente importante respecto al árbol sintáctico mismo, el cual es un atributo
sintetizado que debe ser construido durante el análisis sintáctico, si se va a emplear para
análisis semántica adicional. Históricamente, la posibilidad de calcular todos los atributos
durante el análisis sintáctico era incluso de mayor interés, ya que se había puesto mucho énfasis en la capacidad de un compilador para realizar la traducción en un paso. En la actualidad esto es menos importante, asi que no proporcionaremos un análisis exhaustivo de todas
las técnicas especiales que se han desarrollado. Sin embargo, vale la pena ofrecer una perspectiva básica de sus ideas y requerimientos.
Saber cuáles atributos se pueden calcular con éxito durante un análisis sintáctico depende en gran medida de la potencia y propiedades del método de análisis sintáctico empleado.
Una restricción importante es determinada por el hecho de que todos los métodos de análisis sintáctico principales procesan el programa de entrada de izquierda a derecha (esto es lo
que significa la primera L en las técnicas de análisis sintáctico LL y LR que se estudiaron
en los dos capítulos anteriores). Esto es equivalente al requerimiento de que los atributos
puedan evaluarse mediante un recorrido del árbol de análisis gramatical de izquierda a derecha. Para los atributos sintetizados esto no es una restricción, porque los hijos de un nodo
pueden procesarse en orden arbitrario. en particular de izquierda a derecha. Pero, para los
atributos heredados, esto significa que puede no haber dependencias "hacia atrás" cn la grá6ca de dependencia (las dependencias apuntan de derecha a izquierda en el árbol de análisis gramatical). Por cjemplo, la gramática con atributos del ejemplo 6.3 (pigina 266) viola
CAP. 6 1
ANALISIS
SEM~TICO
esta propiedad, porque la base de un nurn-base tiene su base dada por el sufijo o o d,
atributo vcd no se puede calcular hasta que el sufijo al final de la cadena se ve y se proc
Las gra~náticascon atributos que satisfacen esta propiedad se llaman de atrihuto L (por 1
del término izquierda a derecha en inglés) y se detine de la manera siguiente.
Una gramática con atrihutos para los atributos a , , . . . , uk es de atrihuto 1, si, para ca
atributo heredado u,, y para cada regla gramatical
x,-+
x , x , . . . x,,
_ ..
1as txuaciones asociadas para a, son todas de la forma
Xi.oj =,fliiXO.al,. . . , Xt>ak,X,.u,, . . . , Xl.uk,. . . , Xi-,.al, . . . , X , - I . L I ~ )
Es decir, el valor de (9 para X, sólo puede depender de los atrihutos de los símbolos X,,, . .
X, que se presentan a la izquierda de Xi en la regla gramatical.
,
Como un caso especial, ya advertimos que tina gramática con atributos S es de at
buto L.
Dada una gramática con atributos L en la que los atrihutos heredados no dependa
de los sintetizados. un analizador sintáctico descendente recursivo puede evaluar todos 1
atributos convirtiendo los heredados en parámetros y los sintetizados en valores devuelt
de la manera antes descrita. Por desgracia, los analizadores LR, ctnno un analizador sint
rico LALR(1) generado por Yacc, son adecuados para controlar principalmente atribut
sintetizados. La razón reside, irónicamente, en la mayor potencia de los analizadores L
respecto a los analizadores LL. Los atributos sóIo se pueden calcular cuando la regla gr
rnatical a utilizarse en una derivación se vuelve conocida, porque sólo entonces las ecu
ciones son determinadas por el cálculo de atributo. Sin embargo, los analizadores
posponen la decisiítn de qué regla gramatical utilizar en una derivación hasta que el lado
recho de una regla gramatical está completamente formado. Esto hace difícil que los atrib
tos iteredados estén disponibles, a menos que sus propiedades permanezcan fijas para toda
las posibles selecciones del lado derecho. Expondremos brevemente el uso de la pila de an
lisis sintáctico para calcular los atributos en los casos más comunes, con aplicaciones p
Yacc. Las fuentes para técnicas más complejas se mencionan en la sección de notas y re
rencias.
'
CBfcuIo de atributos rintetizados durante el análisis sintáctico LR Éste es el caso más sencillo para tin
analizador LR. Un analizador LR generalmente tendrá una pila de valores en la que se almacenan los atributos sintetizados (quizá como tiniones o esuucturas?si hay más de un atributo para cada símbolo gramatical). La pila de valores será manipulada en paralelo con la
pila de análisis sintáctico, con los nuevos valores que se están calctilando de acuerdo con las
ecuaciones de atributo cada vez que se presenta un desplazamiento « una reduccilín en la pide la
la de anilisis sintáctico. Ilustrarnos esto en la tabla 6.8 para la gramática con :itrib~~tos
tabla 6.5 (página 269)).que es la versión ambigua de la grainatica de eupresicín aritrriética
4inpls. Por simplicidad utilizamos notaci6n abreviada para la graitiitica e ignor;imvs algun<~s
tle l o s tietalles de u11 algoritmo de :rn:ílisis si~itictico1.R en la i:ibla. Eri pxiicular. no
Algorrtn~ospara cálculo de atributos
29 1
se indican los núnieros de csiado. iio se ri~iicstrae l sínibolti iiiicinl aiirnentado y no se expresan las reglas de elirrriiiiiciii~ide airihigüedrtd iiiiplícitas. L a t;ihla coniiciie dos nuevas coI~irnriasadeiriás de las acciones habituales de análisis sir~táctico:la pila tic valores y acciones scniánticas. Las accictnes semáiiticiis indican cdrrio oclirren los cálculos en la p i h
de valores a niedida cpe se presentan las rctliicciones en la pila de awílisis sintáctico. ([..os
dcsplaminient»s se ven como la inserción de valores de token tanto en la pila de an5lisis
sintáctico coino en la pila de valores, aunque esto puede diferir en a r i a l i ~ ~ d o r esintácticos
s
individuales.)
Conio ejernplo de una accidiiseinántica cotisidere el paso 10 de la tabla 6.8. L a pila de
valores contiene los valores enteros 12 y S. separados por e l token t. con el 5 en la pasie
superior de la pila. 1.a ;icci6ii del análisis sintáctico es reducir rnediante E .--+ E t E, y lii
accicín seináiitica cimeqx)ndiente de I;i cuhla 6.5 es calcular de acuerdo con l a ecuaci6n
El .~.<i/= & . c d + 5,. v d . Las ;~ccionescorrespoiidientes en la pila de valores que realiza cl
analizador sin(6ctico son las que se muestran a coriiinuacidn (en psetidocódigo):
obtieite E , .idde l a pila de valores )
descarta e l toketi
]
obtiene E, .vid de la pila de valores )
suma ]
( inserta el resultado de iiuevo eii la pila de valores
(
(
(
(
pop 13
[~op
pop 12
t l = rZ -t 13
push rl
+
/
Eli Yacc la situacií,n representada por la recluccidn e n e l paso 10 se escrihiríü como la
regla
Aquí las pseudov;iriables Si representan posiciones en el lado derecho de la regla qiie se
rstri retlociendo y se convierten a posiciones en la pila de valores contando hacia atris a partir de la derecha. De este rriodo. $3. que corresporide al E más a Iri derecha, se encontrar5
en la parte superior o tope de la pila, inientrtis cpe $1se hallará dos posiciones por dehijo
del tope.
Tdbla 6.8
Aaioner remanticas y de
análisis sintáctico para la
expresión 3 * 4 + 5 durante
un análisis rintktico LR
Pila de
análisis
sintáctico
I
S
$n
3
4
$E
<SE*
SE*n
5
6
7
S
'1
10
%E*/<
RE
Acción
de analisis
sintactico
Entrada
3*4+5 'S
*4+5 $
*4t5 $
4t5 $
+5 $
t5 $
SE+n
+5 S'
5%
S
CI+li
3'
$E+
dcylaz:i~nienro
rectuccih de E --r n
dzsplazntnie~ito
desplar~rniento
reducción <le E n
redtrcci6n de
E .-- IJ * E
dcs~iln~aniieiito
~le,spl~~:it~iienii~
rzdi~cciiínJc E -a n
r.cclirccii>nde
--
Pila de
valores
Acción
sernantica
$
$n
E .vol = n .~:d
'S 3
si*
$3
*n
$3*4
E2 .1,d
S 17
', I Z +
'; 17 + n
SI?+;
1; --+ f',' +
Il
7F
i
1:' .I.<II
-= n .~ r i i
f I
=
5' 17
*E
i
.l.d
n riil
I < . i ~ ,-:
i/
i<,. ~ , i i l
IC- l ' , d + l..'<
. l , d
U?.6 1 AN~LISISSEM~NTICO
Herencia de un atributo sintetizado previamente cakculado durante el análisis rintáctfco IR Debido a l
trategia de evaluación de izquierda a derecha del anrüisis sintáctico LR, una acción as
da a un no terminal en el lado derecho de una regla puede utilizar atributos sintetizados
los símbolos a la izquierda del mismo en la regla, puesto que estos valores ya se insert
en la pila de valores. Para ilustrar esto brevemente considere la opción de producción A.
B C, y suponga que C tiene un atributo heredado i que depende de alguna manera del a
buto sintetizado s de B.: C.i = f (5,s). El valor de C.i puede almacenarse en una vari
antes del reconocimiento de C introduciendo una producción E entre B y C que program
almacenamiento de la parte superior de la pila de valores:
Regla gramatical
Regias semánticas
A-BDC
B+. ..
D-rc:
C-3..
calcule B.s )
saved-i = f (vulstack[top])
[ ahora está disponible ,yaved-i )
.
En Yacc este proceso se vuelve aún más fácil, ya que la producción E no necesita ser in
ducida explícitamente. En su lugar, la acción de almacenamiento del atributo ca~cuiado
escribe simplemente en el lugar de la regla donde se va a registrar:
A : B { saved-i = f($l);
>
C ;
(Aqui la pseuciovariable $1se refiere al valor de B, que está en la parte superior de la p
cuando se ejecuta la acción.) Tales acciones incrustadas en Yacc se estudiaron en la secci
5.5.6 del capítulo anterior.
Una alternativa para esta estrategia está disponible cuando siempre puede predecirse
posición de un atributo sintetizado previamente calculado en la pila de'valores. En es
caso, el valor no necesita ser copiado en una variable, sino que se puede acceder a él dire
tamente en la pila de valores. Considere, por ejemplo, la siguiente gramática con atributo
L con un atributo dtype heredado:
Regla gramatical
Reglas semhnticas
decl -t type vur-list
type -+ int
type -3 float
var-listl -+ var-lista ,i d
var-list.dtype = .ipe.&pe
type.dtype = integer
type.drype = real
insert(Ld .narne, var-listl.&ype)
var-list2.dtype= var-list&pe
insert(id .name, var-list.dr/pe)
iw-lisr + i d
En este caso el atributo clrype, que es un atributo sintetizado para el no terminal rype, se puede calcular en la pila de valores justo antes de que se reconozca la primera vw-list. Entonces, cuando se reduce cada regla para vnr-lis?,&pe se puede encontrar en una posición fija en la pila de valores contando hacia atrás desde la parte superior o tope: cuando se reduce
vw-iirt -t id. dt-ye está justo debajo de la parte superior de la pila. mientras que cuando
se reduce iar-lixt, -3 v(ir-li.~t~
,id, dvpe está tres posiciones debajo del tope de la pila. Podemos implementiw esto en un rinalizador siiitictico LR mediante la eliminación de las dos
Algoritmos para cálculo de atributos
293
reglas de copia para +pr en la gramática con atributos anterior y entrando a la pila de
valores de manera directa:
Regla gramatical
Reglas semánticas
drc1-r type vur-1i.s~
npe -. i n t
Qpe
f loat
iur-iist, + var-list2 ,i d
w~r-li,~C
+i d
type.&pe = integer
tvpt..ilIype = rtul
insert(id .narnr, ~ ~ ~ i ~ r l l l c k [ l o p - i ~ )
11)
insert(id .nome. r~ul.rtack~op-
-)
(Advierta que, como vur-lisr no tiene atributo sintetizado dtvpe, el analizador sintáctico
debe insertar un valor de prueba en la pila para mantener la ubicación apropiada en ésta.)
Existen varios problemas con este métdo. En primer.lugar, requiere que el programador
tenga acceso de manera directa a la pila de valores durante un análisis sintáctico, y esto puede ser peligroso en analizadores sintácticos generados de manera automática. Por ejemplo,
Yacc no tiene una convención de pseudovariable para tener acceso a la pila de valores bajo
la regla actual que se está reconociendo, como se requeriría con el método anterior. De este
modo, para implementar un esquema de esta clase en Yacc, se tendría que escribir código
especial para hacerlo. El segundo problema es que esta técnica sólo funciona si la posición
del atributo previamente calculado es predecible a partir de la gramática. Por ejemplo, si hubiéramos escrito la gramática de la declaración anterior de manera que vur-lkr fuera recursiva por la derecha (como hicimos en el ejemplo 6.17), entonces un número arbitrario de i d
podría estar en la pila, y no se conocería la posición de d y p e en la pila.
Con mucho, la mejor técnica para tratar con atributos heredados en el análisis sintáctico LR es utilizar estructuras de datos externas, tal como una tabla de símbolos o variables
no locales, con el fin de mantener los valores de atributos heredados, y de agregar producciones E (o acciones incrustadss como en Yacc) para permitir que se presenten cambios a
estas estructuras de d.dtos en ios momentos apropiados. Por ejemplo, una solución para el
problema de dtype que se acaba de comentar puede encontrarse en el anjlisis de las acciones incrustadas en Yacc (sección 5.5.6).
Sin embargo, deberíamos estar conscientes de que incluso este último métocio no carece
de riesgos: la adición de producciones E a una gramática puede agregar conflictos de análisis sintáctico, de manera que una gramática LALR(1) se puede convertir en una gramática
no LR(k) para cualquier k (véase el ejercicio 6.15 y la sección de notas y referencias). En situaciones prácticas, no obstante, esto rara vez sucede.
6.2.6 l a dependencia del cálculo de atributos respecto a la sintaxis
Como el tema final en esta sección, vale la pena advertir que las propiedades de los atributos dependen en gran parte de la estructura de la gramática. Puede pasar que las modificaciones a la gramática que no cambian las cadenas legales del lenguaje provoquen yue
el cálculo de los atributos se vuelva más simple o más complejo. En realidad, tenemos el
siguiente:
Teorema
(De Knuth [l9681). Dada una grüniáticü con atributos, todos los atributos heredados se
pueden cambiar en atributos sintetiz;id«s mediante la modificación adecuada de ILI pramátic;i. sin cambiar el lenguaje de la misma.
<**:m-
UP. 6 1 ANALISIS SEMANTICO
Daremos un ejemplo de cómo un mibuto heredado se p u d e convertir en un atributo
tizado mediante modificación de la gramática.
Ejemplo 6.18
Vuelva a considerar La gramática de declaraciones simples de los ejemplos anteriores
decl-+ type iur-list
type -+ i n t 1 float
vur-list -+ id , vnr-list / id
El atributo clvpe de la gramática con atributos de la tabla 6.3 es heredado. Sin embarg
volvenios a escribir la gramática de la manera siguiente,
iiecl -+vur-list id
var-list -+ vqr-list i d
ype -+ i n t / f loat
, 1 type
entonces se aceptan las mismas cadenas, pero el atributo dtype ahora se vuelve sintetiza
de acuerdo con la siguiente gramática con atributos:
Regla gramatical
Reglas semanticas
<lec1+ viir-lis! id
iur-listi + var-list2 id ,
id .&pe = iw-listdtype
id .dtype = vur-li.stt .dtype
vur-¡¡.S!, .dtype = vur-listz .dtype
vur-li.st.iltyp = 1 p e . d ~ p e
typedope = integer
f)pe.dtype = real
sur-lis1 -. rype
type -+ int
tjpe -+ f l o a t
Ilustraremos cómo este cambio en la gramática afecta al árbol de análisis gramatical y el c
lo del atributo dtype en la tigura 6.11, que exhibe el &bol de análisis gramatical para la c
f loat x, y,junto con las dependencias y valores de atributo. Las dependencias de los
valores i d .d@pe respecto a los valores del padre o hermano se trazan como líneas d'
tinuas en la figura. Aunque estas dependencias parecen ser violaciones a la afirmaci
que no hay ztributos heredados en esta gramática con atributos, de hecho estas depe
cias son siempre hacia hojas en el árbol de análisis sintáctico (es decir, no recursivo) y
den conseguirse mediante operaciones en los nodos padre apropiados. De este modo,
dependencias no se ven como herencias.
F~gura6 11
decl
_--a
Arbol de análiws gramatical
para la cadena fioat
x, y que muestra el
atributo dtype como se
eípecifico med~antela
gramát~cacon atr~butor
del ejemplo 6.18
/ -
/a-
r - i r
,
,
,
,
,
,
,
,
,
f loat
dtype = rrnl
.-.
.....
..
id dtype = red
(Y)
!
....
- - - - _ _ _ _ - -,
..
4
/
De hecho, d teorema establecido es menos útil de lo que puede parecer. Modificar la gramática con el fin de cambiar los atributos heredados a atnbutas sintetizados con frecuencia
hace a la granática y las reglas semántieas mucho m& complejas y difinles de comprender.
Por lo tanto, no es una manera recomendable pata tratar con los problemas de cálculo de
atributos heredados. Por otra parte, si un cálculo de atributo parece anormalmente difícil,
puede ser porque Ia gramática está definida de
ada para su c&ulo, y un
cambio en la gramática puede valer la pena,
organizaciones diferentes: así como la investigación de buenas estrategias de orgmización,
es uno de los temas principales de un curso de estructura de datos. Por lo tanto, en este texto
8. Antes que destruir esta iiiforrnación, es más prohabk que una operación de elimin<rciún(deirte)
la retire de la vista. ya sea alinacenándolü en otra p:irte: o marcándola ct>inoinactiva.
Descargar