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.