Contenido

¿Por qué compiladores?Una breve historia 2 Programas relacionados con los compiladores 4 Proceso de traducción 7 Estructuras de datos principales en un compilador 13 Otras cuestiones referentes a la estructura del compilador 14 Arranque automático y portabilidad 18 Lenguaje y compilador de muestra TINY 21 C-Minus: Un lenguaje para un proyecto de compilador 26 Ejercicios 27 Notas y referencias 29

2.1 El proceso del análisis Iéxico 32 2.2 Expresiones regulares 34 2.3 Autómatas finitos 47 2.4 Desde las expresiones regulares hasta los DFA 64 2.5 Implementación de un analizador Iéxico TINY ("Diminuto") 75 2.6 Uso de Lex para generar automáticamente un analizador Iéxico 8 1 Ejercicios 9 1 Ejercicios de programación 93 Notas y referencias 94

3 GRAMATICAS LIBRES DE CONTEXTO Y ANÁLISIS SINTACTICO 95
3.1 El proceso del análisis sintáctico 96 3.2 Gramáticas libres de contexto 97 3.3 Árboles de análisis gramatical y árboles sintácticos abstractos 106 3.4 Ambigüedad 1 14 3.5 Notaciones extendidas: EBNF y diagramas de sintaxis 123 3.6 Propiedades formales de los lenguajes libres de contexto 128 3.7 Sintaxis del lenguaje TlNY 133 Ejercicios 138 Notas y referencias 142

4 ANALISIS S~NTACT~CO DESCENDENTE 143
4.1 4.2 4.3 4.4 4.5 Análisis sintáctico descendente mediante método descendente recursivo 144 Análisis sintáctico LL(I) 152 Conjuntos primero y siguiente 168 Un analizador sintáctico descendente recursivo para el lenguaje TINY 180 Recuperación de errores en analizadores sintácticos descendentes 183 Ejercicios 189 Ejercicios de programación 193 Notas y referencias 196

5 ANALISIS SINTACTICO ASCENDENTE

197

Perspectiva general del análisis sintáctico ascendente 198 Autómatas finitos de elementos LR(0) y análisis sintáctico LR(0) 20 1 Análisis sintáctico SLR( I) 2 10 Análisis sintáctico LALR(l) y LR(I) general 217 Yacc: un generador de analizadores sintácticos LALR(I) 226 Generación de un analizador sintáctico TlNY utilizando Yacc 243 Recuperación de errores en analizadores sintácticos ascendentes 245 Ejercicios 250 Ejercicios de programación 254 Notas y referencias 256

Atributos y gramáticas con atributos 259 Algoritmos para cálculo de atributos 270 La tabla de símbolos 295 Tipos de datos y verificación de tipos 3 13 Un analizador semántico para el lenguaje TlNY Ejercicios 339 Ejercicios de programación 342 Notas y referencias 343

334

7 AMBIENTES DE EJECUCION
7.1 7.2 7.3 7.4 7.5

345

Organización de memoria durante la ejecución del programa 346 Ambientes de ejecución completamente estáticos 349 Ambientes de ejecución basados en pila 352 Memoria dinámica 373 Mecanismos de paso 'de parámetros 381

CONTENIDO

7.6

Un ambiente de ejecución para el lenguaje TlNY Ejercicios 388 Ejercicios de programación 395 Notas y referencias 396

386

8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 8.1 O

Código intermedio y estructuras de datos para generación de código 398 Técnicas básicas de generación de código 407 Generación de código de referencias de estructuras de datos 416 Generación de código de sentencias de control y expresiones lógicas 428 Generación de código de llamadas de procedimientos y funciones 436 Generación de código en compiladores comerciales: das casos de estudio 443 TM: Una máquina objetivo simple 453 Un generador de código para el lenguaje TlNY 459 Una visión general de las técnicas de optimización de código 468 Optimizaciones simples para el generador de código de TlNY 48 1 Ejercicios 484 Ejercicios de programación 488 Notas y referencias 489

Apéndice A: PROYECTO DE COMPILADOR 491
A. I Convenciones Iéxicas de C- 49 1 A.2 Sintaxis y semántica de C- 492 A.3 Programas de muestra en C- 496 A.4 Un ambiente de ejecución de la Máquina Tiny para el lenguaje C- 497 A S Proyectos de programación utilizando C- y TM

500

Apéndice 1: LISTADO DEL COMPILADOR TINY 502 ApéndiceC: LISTADO DEL SIMULADOR DE LA MAQUINA TlNY 545

Bibliografía 558

Prefacio

Este libro es una introducción al campo de la constmcción de compiladores. Combina un estudio detallado de la teoría subyacente a1 enfoque moderno para el diseño de compiladores, junto con muchos ejemplos prácticos y una descripción completa, con el código fuente, de un compilador para un lenguaje pequeño. Está específicamente diseñado para utilizarse en un curso introductorio sobre el diseño de compiladores o construcción de compiladores a un nivel universitario avanzado. Sin embargo, también será de utilidad para profesionales que se incorporen o inicien un proyecto de escritura de compilador, en la medida en que les dará todas las herramientas necesarias y la experiencia práctica para diseñar y programar un compilador real. Existen ya muchos textos excelentes para este campo. ¿Por qué uno más'? La respuesta es que la mayoría de los textos actuales se limita a estudiar uno de los dos aspectos irnpor[antes de la construcción de compiladores. Una parte de ellos se restringe a estutli:~rla teoría y los principios del diseño de compiladores, y sólo incluye ejemplos breves dc I:i aplicación de la teoría. La otra parte se concentra en la meta práctica de producir un compilador real, ya sea para un lenguaje de programación real, o para una versión reducida de alguno, e incursiona sólo superficialmente en la teoría en la cual se basa el código para explicar su origen y comportamiento. Considero que ambos enfoques tienen carencias. Para cornpreuder en realidad los aspectos prácticos del diseño de compiladores, se necesita h. ber comprendido la teoría, y para apreciar realmente la teoría, se requiere verla en acción en una configuración práctica real o cercana a la realidad. Este texto se encarga de proporcionar el balance adecuado entre la teoría y la práctica, y de suministrar suficientes detalles de impiementación real para ofrecer una visión real de las técnicas sin abrumar al lector. En este texto proporciono un compilador completo para un lenguaje pequeño escrito en C y desarrollado utilizando las diferentes técnicas estudiadas en cada capítulo. Además, ofrezco descripciones detalladas de técnicas de codificación para ejemplos adicionales de lenguajes a medida que se estudian los temas relacionados. Finalmente, cada capítulo concluye con un extenso conjunto de ejercicios, que se dividen en dos secciones. La primera contiene los diversos ejercicios que se resuelven con . . Y lápavel , piz y que implican poca prograkaciítn. La segunda contiene aquellos que involucran una cantidad importante de programación. Al escribir un texto así se debe tener en cuenta los diferentes sitios que ocupa un curso de compiladores en diferentes currículos de ciencias de la comput;ición. En cilgnnos programas se requiere haber tornado un curso de teoría de autómatas; en otros, uno sobre lenguajes de programaci6n; mientras que en otros es suficiente con haber tomado el curso de estructuras de datos. Este texto sólo requiere que el lector haya tomado el curso hahitual sohre estructuras de datos y que conozca un poco del lenguaje C; incluso csth pl:~nccrdo

Introducción
¿Porque compiladores? Una breve historia Programas relacionados con los compiladores Proceso de traducción Estructuras de datos principales en un compilador

1 .S Otras cuestiones referentes a la estructura del compilador
1.6 Arranque automático y portabilidad 1.7 Lenguaje y compilador de muestra TlNY 1.8 C-Minus: un lenguaje para un proyecto de compilador

Los compiladores son programas de computadora que traducen un lenguaje a otro. Un compilador toma como su entrada un programa escrito en su lenguaje fuente y produce un programa equivalente escrito en su lenguaje objetivo. Por lo regular, el lenguaje fuente es un lenguaje de alto nivel, tal como C o C++, mientras que el lenguaje objetivo es código objeto (también llamado en ocasiones código de máquina) para la máquina objetivo, es decir, código escrito en las instrucciones de máquina correspondientes a la computadora en la cual se ejecutará. Podemos visualizar este proceso de manera esquemática como sigue:

Programa fuente

-

Programa objetivo

Un compilador es un programa muy complejo con un número de líneas de código que puede variar de i 0,000 a 1,000,000. Escribir un programa de esta naturaleza, o incluso comprenderlo, no es una tarea fácil, y la mayoría de los científicos y profesionales de la computación nunca escribirán un compilador completo. N o obstante, los compiladores se utilizan en casi todas las formas de la computación, y cualquiera que esté involucrado profesionalmente con las computadoras debería conocer la organización y el funcionamiento básicos de un compilador. Además, una tarea frecuente en las aplicaciones de las computadoras es el desarrollo de programas de interfaces e intérpretes de comandos, que son más pequeños que los compiladores pero utilizan las mismas técnicas. Por lo tanto, el conocimiento de estas técnicas tiene una aplicación práctica importante. El propósito de este texto no sólo es el de proporcionar este conocimiento básico. sino también el de ofrecer al lector todas las herramientas necesarias, así como la experiencia práctica, para diseñar y programar un compilador real. Para conseguir esto es

necesario estudiar las técnicas teóricas, principalmente las provenientes de la teoria de los autómatas, que hacen de la construcción de compiladores una tarea manejable. A l abordar esta teoria evitaremos suponer que el lector tiene un conocimiento previo de la teoria de autómatas. En vez de eso aquí aplicaremos un punto de vista diferente al que se aplica en un texto estándar de teoria de autómatas. en el sentido de que éste está dirigido especificamente al proceso de compilación. Sin embargo, un lector que haya estudiado teoría de autómatas encontrará el material teórico bastante familiar y podrá avanzar más rápidamente a través de estas secciones. En particular, un lector que tenga buenos fundamentos en la teoría de autómatas puede saltarse las secciones 2.2, 2.3. 2.4 y 3.2 o revisarlas superficialmente. En cualquier caso, el lector debería estar familiarizado con matemáticas discretas y estructuras básicas de datos. También es esencial que conozca un poco de arquitectura de máquinas y lenguaje ensamblador. en particular para el capitulo sobre la generación de código. El estudio de las técnicas de codificación práctica en sí mismo requiere de una cuidadosa planeación, ya que incluso con buenos fundamentos teóricos los detalles de la codificación pueden ser complejos y abrumadores. Este texto contiene una serie de ejemplos simples de construcciones de lenguaje de programación que se utilizan para elaborar el análisis de las técnicas. El lenguaje que empleamos para éste se denomina TINY. También proporcionamos (en el apéndice A) un ejemplo más extenso, que se compone de un subconjunto pequeño, pero suficientemente complejo, de C, que denominamos C-Minus, el cual es adecuado para un proyecto de clase. De manera adicional tenemos numerosos ejercicios; éstos incluyen ejercicios simples para realizar con papel y lápiz. extensiones del código en el texto y ejercicios de codificación más involucrados. En general, existe una importante interacción entre la estructura de un compilador y el diseño del lenguaje de programación que se está compilando. En este texto sólo estudiaremos de manera incidental cuestiones de diseño de lenguajes. Existen otros textos disponibles para profundizar mas en las cuestiones de diseño y conceptos de lenguajes de programación. (Véase la sección de notas y referencias al final de este capitulo.) Comenzaremos con una breve revisión de la historia y razón de ser de los compiladores, junto con una descripción de programas relacionados con ellos. Después examinaremos la estructura, mediante un ejemplo concreto simple, de un compilador y los diversos procesos de traducción y estructuras de datos asociadas con él. Al final daremos una perspectiva general de otras cuestiones relacionadas con la estructura de compiladores, incluyendo el arranque automático de transferencia ("bootstrapping") y portabilidad, para concluir con una descripción de los principales ejemplos de lenguajes que se emplean en el resto del libro.

1.1 iPOR QUE COI1PILADORES? UNA BREVE HISTORIA
Con el advenimiento de la computadora con programa almacenado, iniciado por John von Neumarin a finales de la década de 1940, se hizo necesario escribir secuencias de códigor, o programas, que darían como resultado que estas computadoras realizaran los cálculos deseados. A l principio estos programas se escribían en lenguaje de máquina: códigos nuni6ricos que rcpresentaban las operaciones reales de la iiváquina que iban a ckctiiarse. Por +inplo,

representa la instrucción parti mover el nílrnertt 2 a la i~hicacióri 0000 (en sistenia hex;~<lecirii;il I en los procesa<loresIritel 8x86 que se u ~ i l i men las PC cle IBM. Por wpuesto, I;i escrilim t [le tales c6cligos es muy tediosa y corisiiiirc niucho tiempo, por Itr que esta tbrri~:iilc coiliii y c:ici<ín pronto t'iw rccniplazaJa por cl Ienguzije ensamblador, m el cu;iI I;is i~~si~-~~cciotic:s

¿Por que compiladores? Una breve historia

las localidiides de memoria son li~rniits sinibólicas dadas. Por tjernplo, la instrucción en len. p a j e ensnmbl;idor
MOV X , 2

es equiv;ilente a la instriiccicin de máquina anterior (suponiendo que la localidad de memo. ria sirnbcilicit X cs 0000). Un ensamhlador traduce los códigos simbólicos y las localidadc. de rriemoria del lenguaje enwniblxior a los códigos numéricos correspondientes del lengua je de máquina. El lenguaje ensarnblador mejoró enortncmente la velocidad y exactitud con la que podían escribirse los programas, y en la actualidad todavía se encuentra en LISO, cn especia cuando se necesita una gran velocidad o brevedad en el código. Sin embargo, el Ienguajt etisamblador tiene varios defectos: aún no es fácil de escribir y es difícil de leer y corii~ i prender. Adeiiiás, el lenguaje ensamblador depende n extremo de la niáquina en particda1 para la cual se haya escrito, de manera que el c d i g o escrito para una conip~itatlora debc i volver : escribirse por completo para otra máquina. corno es evidente, el siguiente paso t'trndamental en la tecnología de progriirnación fue escribir las operaciones de un programa dt una manera concisa que se pareciera mucho a la notación niatemática o lenguaje natural de manera que fueran indepentlientes de cualquier máquina en particular y todavía se p~idierar traducir niediante iin programa para convertirlas en código ejecut&le. Por ejemplo, el aiitcrior código del Lenguaje ensarnblacior se ptiede escribir de manera concisa e independiente de una máquina en particular corno

A l principio se temía que esto no fuera posible, o que s i lo fuera, el código objeto sería tan poco eficiente que restiltaría inútil. El desarrollo del lenguaje FORTRAN y su compil:rdor, llevado a cabo por un equipo en IBM dirigido por John Backus entre 1954 y 1957 demostró que estos teniores eran infundados. No obstmte, el éxito de este proyecto se debió sólo a un gran esf~ierzo.ya que la mayoría de los procesos involucr~dos la traducción de lenguajes de programación no fueroti en bien comprendidos en el momento. Más o rne'nos al mismo tiempo en que el primer compilador se estaba desarrollando, Noain Chornsky corrienzó a estudiar la estructura del lenguaje natural. Sus hallazgos fiiialriien- hicieron que la construcción de compiladores se volviera mucho más fácil e incluso pudiera ser automatizado hasta cierto punto. Los estudios de Chonisky condujeron a la clasificación de los lengmjes de acuerdo con la complejidad de sus gramáticas (las reglas que especifica~isu estructura) y la potencia de los algoritmos necesarios para reconocerlas. La jerarquía de ~ h o m s k ycomo ahora se le conoce, se compone de cuatro niveles de gra, máticas, denominadas gramáticas tipo O, tipo 1, tipo 2 y tipo 3, cada una de las cuales es tina especialización (le su predecesora. Las gramáticas de tipo 2, o gramáticas libres de contexto, demostraron ser las más útiles para lenguajes de programación, en la actualidad son la manera estándar para representar la estructura de los lenguajes de programación. El estiidio del problema del análisis sint&ctico(la determinación de algoritmos eficientes para e1 reconocimiento de lenguajes libres de contexto) se llevó a cabo en las décadas de los 60 y 70 y condujo a una solución muy completa de este probleina, que en la actualidad se ha vuelto una parte estándar de la teoría de compiladores. Los lenguajes libres de contexto y los algoritmos de análisis sintáctico e estudiart eii los capítulos 3, 4 y 5. Los autbmatas finitos y las expresiones regulares, que corresponden a Ins gr;iniiticas de tipo 3 de Chomsky, se encuentran estrechmente relacionados con las gr;~ni;íticas libres de contexto. A l comenzar a generarse casi al iiiisino ticnipo que el trahnjo de Chornsky, su estudio contlrfjo a inét«dos siiiibtilicos p x n expresar la estructur:i de las p;tlabrns. o tokens, Jc un Icnguaje de prognini:tcióri. En el capítulo 2 se an;ilizaii los :iutcírri;itas ririitos y las expresiones regulares.

CAP. I 1 INTRODUCCldN

h<ucho más complejo ha sido el desarrollo de métodos para la generación de código objeto eficaz, que comenzó con 10s primeros compiladores y continúa hasta nuestros días. Estas técnicas suelen denominarse, incorrectamente, técnicas de optimización, pero en realidad deberían llarnarse técnicas de mejoramiento de código, puesto que casi nunca producen un código objeto verdaderamente óptirno y sólo mejoran su eficacia. En el capítulo 8 se describen los fundamentos de estas técnicas. A medida que el problema del análisis sintáctico se comprendía bien, se dedicó mucho trabajo a desarrollar programas que automatizarían esta pMe del desarrollo de compiladores. Estos programas originalmente se llamaron compiladores de compilador, pero se hace referencia a ellos de manera más acertada como generadores de analizadores sintácticos, ya que automatizan sólo una parte del proceso dc compilación. El más conocido de estos programas es Yacc (por las siglas del término en inglés "yet another compiler-compiler", "otro compilador de compilador más"), el cual fue escrito por Steve Johnson en 1975 para el sistema Unix y se estudiará en el capítulo 5. De manera similar, el estudio de los autómatas finitos condujo al desarrollo de otra herramienta denominada generador de rastreadores (O generador de analizadores léxicos), cuyo representante más conocido es Lex (desarrollado para el sistema Unix por Mike Lesk más o menos al nlismo tiempo que Yacc). Lex se estudiará en el capítulo 2. A fines de los 70 y principios de los 80 diversos proyectos se enfocaron en automatizar la generaciún de otras partes de un compiiador, incluyendo L generación del c6digo. Estos intena tos han tenido menos éxito, posiblemente debido a la naturaleza compleja de las operaciones y a nuestra poca comprensión de las mismas. No las estudiaremos con detalle en este texto. Los avances más recientes en diseño de compiladores han incluido lo siguiente. En primer lugar, los compiladores han incluido la aplicación de algoritmos más sofisticados para inferir y10 simplificar la información contenida en un programa, y éstos han ido de la mano con el desarrollo de lenguajes de progranación más soí'isticados que permiten esta clase de análisis. Un ejemplo típico de éstos es el algoritmo de unificación de verificación de tipo de Hindley-Milner, que se utiliza en la compilación de lenguajes funcionales. En segundo lugar, los compiladores se han vuelto cada vez más una parte de un ambiente de desarrollo interactivo, o IDE (por sus siglas en inglés de "interactive development environment"), basado en ventanas, que incluye editores, ligadores, depuradores y administradores de proyectos. Hasta ahora ha habido escasa estandariración de estos IDE, pero el desarrollo de ambientes de ventanas estándar nos está conduciendo en esa dirección. El estudio de estos temas rebasa el alcance de este texto (pero véase la siguiente sección para una breve descripción de algunos de los componentes de un IDE). Para sugerencias de literatura relacionada véase la sección de notas y referencias al final del capítulo. Sin embargo, a pesar de la cantidad de actividades de investigación en los últimos años, los fundamentos del diseño de compiladores no han cambiado demasiado en los últimos veinte años, y se han convertido cada vez más en una parte del núcleo estándar en los currículos para las ciencias de la computación.

1.2 PROGRAMAS RELACIONADOS CON LOS COMPILADORES
En esta sección describiremos brevemente otros programas que están relacionados, o que se utilizan, con los compiladores y que con frecuencia vienen junto con ellos en un ambiente de desarrollo de lenguaje completo. (Ya mencionamos algunos.)
INTÉRPRETES

Un intérprete es un traductor de lenguaje, igual que u n compiiador, pero diticre de éste en que ejecuta el programia fuente inmediatamente, en vez de generar un código objeto que se ejecuta después de que se completa la traducción. En principio, cualquier lenguaje de programacióii se puede interpretar o compilar, pero se puedc preferir un intérprete a u11 cotnpilador dependiendo tlcl Icngnaje que se esté usando y de la situación en la cucil

Programar relacionados

ton

los compiiadorer

5

se presenta la traducción. Por ejemplo, BASIC es un lenguaje que por lo regular es interpretado en vez de compilado. De manera similar, los lenguajes funcioriales, corno LISP, tienden a ser interpretados. Los intérpretes también se utilizan con frecuencia en situaciones relacionadas con la enseñanza o con el desarrollo de software, donde los programas soti probablemente traducidos y vueltos a traducir muchas veces. Por otra parte, es preferible usar un compilador si lo que importa es la velocidad de ejecución, ya que el código objeto compilado es siempre más rápido que el código fuente interpretado, en ocasiones hasta por un factor de 10 o más. No obstante, los interpretes comparten muchas de sus operaciones con los compiladores, y ahí pueden incluso ser traductores híbridos, de manera que quedan en alguna parte entre los intérpretes y los compiladores. Analizaremos a los intérpretes de manera ocasional, pero en este texto nos enfocaremos principalmente en la compilación.
ENSAMBLADORES

Un ensamblador es un traductor para el lenguaje ensamblador de una computadora en particular. Como ya lo advertimos, el lenguaje ensamblador es una forma simbólica del lenguaje de máquina de la computadora y es particularmente fácil de traducir. En ocasiones un compilador generará lenguaje ensamblador como su lenguaje objetivo y dependerá entonces de un ensamblador para terminar la traducción a código objeto.
LIGADORES

'Tanto los compilridores como los ensambladores a menudo dependen de un programa conocido como ligador, el cual recopila el código que se compila o ensambla por separado en diferentes archivos objeto, a un archivo que es directamente ejecutable. En este sentido, puede hacerse una distinción entre código objeto (código de máquina que todavía no se ha ligado) y código de ináquina ejecutable. Un ligador también conecta un programa objeto con el código de funciones de librerías estándar, así como con recursos suministrados por el sistema operativo de la computadora, tales como asignadores de memoria y dispositivos de entrada y salida. Es interesante advertir que los ligadores ahora realizan la tarea que originalmente era una de las principales actividades de un compilador (de aquí el uso de la palabra cotnpilrdor:construir mediante la recopilación o cornuilczcicín de fuentes diferentes). En este texto no estudiaremos el proceso de ligado porque depende demasiado de los detalles del sistema operativo y del procesador. Tampoco haremos siempre una distinción clara entre el código objeto no ligado y el código ejecutable, porque esta distinción no tiene importancia para nueitro estudio de las técnicas de compilación.
CARGADORES

Con frecuencia un compilador, ensamblador o ligador producirá un código que todavía no está completamente organizado y Itsto para ejecutarse, pero cuyas principales referencias de memoria se hacen relativas a una localidad de arranque indeterminada que puede estar en cualquier sitio de la memorka. Se dice que tal código es relocalizable y un cargador re$olverá tadas las direcciones relocalizables relativas a una dirección baae, o de inicio, dada. El uso de un cargador hace más flexible el código ejecutable, pero el proceso de carga con frecuencia ocurre en segundo plano (como parte del entorno operacional) o coniuutamente con el ligttdo. Rara vez un cargador es en realidad un programa por separado.
PREPROCESADORES

Un preprocesador es un programa separado que es invocado por el compilador antes de que comience la traducción real. Un preprocesador de este tipo puede eliminar los comentarios, incluir otros archivos y ejecutar sustituciones de macro (una macro es una descripción abreviada de una secuencia repetida de texto). Los preproccsadorcs pueden ser requeridos por el lenguaje (como en C) o pueden ser :tgregados posteriores que proporcioricn facilidades xlicion:tles (como el prcproccs;~lorR;itfor para I'OKTRAN).

CAP. I i INTRODUCCIÓN
EDITORES

Los compiladores por lo regular aceptan programas fuente escritos utilizando c~ialquier editor que' pueda producir un archivo estándar, tal como un archivo ASCII. Mis recienteniente, los compiladores han sido integrados junto con editores y otros programas en u n ambiente de desarrollo interactivo o IDE. En un caso así, un editor, mientras que aún produce archivos esthndar, puede ser orientado hacia el forniato o estructura del lenguaje de programación en cuestión. Tales editores se denominan basados en estructura y ya incluyen algunas de las operaciones de un compilador, de manera que, por ejemplo, pueda informarse al programador de los errores a medida que el programa se vaya escribiendo en lugar de hacerlo cuando está compilado. El coinpilador y sus programas acompañantes también pueden llamarse desde el editor, de modo que el prograinador ptieda ejecutar el programa sin tener que abandonar el editor.
DEPURADORES

Un depurador es un programa que puede utilizarse para determinar los errores de ejecución en un programa compilado. A menudo está integrado con un compilador en un IDE. La ejecución de un programa con un depurador se diferencia de la ejecución directa en que el depurador se mantiene al tanto de la mayoría o la totalidad dc la inforinación sobre el código fuente, tal conlo los números de línea y los nombres de las variables y procediniientos. También puede detener la ejecución en ubicaciones previamente especificadas denominadas puntos de ruptura, ademhs de proporcionar información de cuáles funciones se han invocado y cuhles son Los valores actuales de las variables. Para efectuar estas funciones el conipilador debe suministrar al deptirador la información simbólica ;ipropiada, lo cual en ocasiones puede ser difícil, en especial en un conrpilador que intente optimizar el código objeto. De este modo, la depuración se convierte en una cuestibn de conipilación, la que, sin embargo, rebasa el alcance de este libro.
PERFILADORES

Un perfilador es un programa que recolecta estadísticas sobre el comportamiento de un programa objeto durante la ejecución. Las estadísticas típicas que pueden ser de interés para el programador son el número de veces que se llama cada procedimiento y el porcentaje de tiempo de ejecución que se ocupa en cada uno de ellos. Tales estadísticas pueden ser muy útiles para ayudar al programador a mejorar la velocidad de ejecución del progrrnna. A veces el compilador utilizará incluso la salida del perfilador para mejorar de manera automática el código objeto sin la intervención del programador.
ADMINISTRADORES DE PROYECTO

. L

Los modernos proyectos de softwae por lo general son tan grandes que tienen que ser eniprendidos por grupos de programadores en lugar de por un solo programador. En tales casos es importante que los archivos que se esthn trabajando por personas distintas se encuentren coordinados, y este es el trabajo de un programa de administración de proyectos. Por ejemplo, un administrador de proyecto debería coordinar la mezcla de diferentes versiones del mismo archivo producido por programadores diferentes. También debería mantener una historia de las modificaciones par+ cada uno de los grupos de archivos, de modo que puedan mantenerse versiones coherentes de un programa en desarrollo (esto es algo que también puede ser útil en un proyecto que lleva a cabo un solo progr;iinador). Un administrador de proyecto puede escribirse en una forma independiente del lenguaje, pero cuando se integra junto con un coinpiliidor, p~icde niantener información acerca del compilador específico y las operaciones de ligado necesarias para construir un programa -jccutable completo. Dos programas populares de administración de proyectos en sistenias Unix son sccs y rcs (source code control system, . ststcina de control para cbrligo L'ucnte") y (revision control system, "sistenta de coiitrol pr:~ rcvisi(ín"~,

Proceso de traducción

7

1.3 PROCESO DE TRADUCCIÓN
Un compilador se compone internamente de vanas etapas, o fases, que realizan distintas operaciones lógicas. Es útil pensar en estas fases como en piezas separadas dentro del compilador, y pueden en realidad escribirse como operaciones codificadas separadamente aunque en la práctica a menudo se integren juntas. Las fases de un compilador se ilustran en la figura 1.1, junto con los tres componentes auxiliares que interactúan con alguna de ellas o

Figura 1.1
fases de un compilador

Código objetivo

consideremos otra vez la línea de código en C que ya habíamos dado. las cuales se estudiwiin con más detalle en los capítulos siguientes. Un analizador léxico puede realizar otras funciones junto con la de reconocimiento de tokens. la tabla de símbolos y el manejador de errores. Esta estructura se puede representar como un jrbol de anlílisis gramatical de la forma siguiente: . De este modo. considere la siguiente línea de código. y puede introducir literales en la tabla de literales (las literales incluyen constantes numéricas tales como 3. el cual generalmente está en la forma de un flujo de caracteres.) ANALIZADOR LÉXICO O RASTREADOR (SCANNER) Esta fase del compilador efectúa la lectura real del programa fuente.1415926535 y cadenas de texto entrecomilladas como "iHola. Repreienta un elemento estructural denominado expresión. Aquí describiremos brevemente cada una de las fases. puede introducir identificadores en la tabla de símbolos. se puede imaginar que un rastreador realiza una función similar al deletreo. las cuales son como las palabras de un lenguaje natural. como el inglés.CAP. mundo!"). Como ejemplo. Por ejemplo. Esto es semejante a realizar el análisis gramatical sobre una frase en un lenguaje natural. ANALliADOR SINT~CTICO(PARSER) El analizador sintáctico recibe el código fuente en la forma de tokens proveniente del analizador léxico y realiza el análisis sintáctico. (Las tablas de literales y de símbolos se analizarán m6s"ampliamente en la siguiente sección y el manejador de errores en la sección 1 S. la cual es una expresión de aqignación compuesta de una expresión con subíndice a la izquierda y una expre~ión aritmética entera a la derecha. El análisis sintáctico determina los elementos estructurales del programa y sus relaciones. El rastreador realiza lo que se conoce como análisis léxico: recolecta secuencias de caracteres en unidades significativas denominadas tokens. I 1 INTRODUCClON con todas: la tabla de literales. Como ejemplo. Los resultados del análisis sintáctico por lo regular se representan como un árbol de análisis gramatical o un árbol sintáctico. que determina la estructura del programa. que podría ser parte de un programa en C: Este código contiene 12 caracteres diferentes de un espacio en blanco pero sólo 8 tokens: a I identificador corchete izquierdo identificador corchete derecho asignación número signo más número index 1 4 + 2 Cada token se compone de uno o más caracteres que se reúnen en una unidad antes de que ocurra un procesamiento adicional.

pero no es eficaz en su representación de esa estructura. pero la mayoría de los lenguajes de programación tienen características que se pueden determinar antes de la ejecución e incluso no se pueden expresar de manera adecuada como sintaxis y analizarse mediante el analizador sintáctico. entonces ya no será necesario mantener los paréntesis cuadrados [ y ] que representan esta operación en la entrada original.) Un árbol de análisis gramatical es un auxiliar útil para visualizar la sintaxis de un programa o de un elemento de programa. Se hace referencia . (En ocasiones los árboles sintácticos se denominan árboles sintácticos abstractos porque representan una abstracción adicional de los árboles de análisis gramatical. o estructura. Los analizadores sintácticos tienden a generar un árbol sintáctico en su lugar. La semántica de un programa determina su comportamiento durante el tiempo de ejecución.rpresión identificador a I identificador index número 4 I número 2 Advierta que los nodos Internos del árbol de análisis gramatical están etiquetados con los nombres de las estructuras que representan y que las hojas del árbol representan la secuencia de tokens de la entrada. en oposición a su sintaxis. (Los nombres de las estructuras están escritos en un tipo de letra diferente para distinguirlos de los tokens. si sabemos que una expresión es una operación de subíndice.) Un árbol sintáctico abstracto para nuestro ejemplo de una expresión de asignación en C es el siguiente: erpresión de asignación identificador a /-" / \ expresión de subíndice identificador index \ / \ exprestón aditiva número 4 número 2 A árbol sintáctico muchos de los nodos han desaparecido (incluyendo los nodos de tokens). ANALIiADOR SEM~NTICO La semántica de un programa es su "significado".Proceso de traducción expresión expresión de subíndice e.xpresión uditiva expresión [ expresión 1 upraión + e. el cual es una condensación de la información contenida en el árbol de análisis gramatical. Por ejemplo.

y el análisis de tal semántica es la tarea del analizador semántico.) Claro que existen posibilidades mucho niás con>plejas(algunas de las cuales se mencionarán en el capítulo 8). El punto más anticipado en el que la mayoría de las etapas de optimización se pueden realizar es precisamente después del análisis semántico. del código. la expresión 4 + 2 se puede calcular previamente por el compilador para ciar como resultado 6. En nuestro ejemplo todos los tipos tienen sentido. Los compiladores individuales muestran una amplia variación no sólo en los tipos de optimizaciones realizadas sino también en la colocación de las fases de optimización. Entonces el 'analizador semántico registrda el árbol sintác6co con los tipos de todas las subexpresiones y posteriormente verificaría que la asignación tuviera sentido para estos tipos.a tales características como semántica estática. no se pueden determinar mediante un compilador porque éste no ejecuta el programa.) Las características típicas de la semántica estática en los lenguajes de programación comunes iicluyen las declaraciones y la verificación de tipos. aquellas propiedades del programa que solamente se pueden determinar al ejecutarlo.sidn <iditivu entero entero i d e n t if i c a d o r a arreglo de enteros i d e n t i f icador index entero número 4 entero número 2 entero OPTlMlZADOR DE CÓDIGO FUENTE Los compiladores a menudo incluyen varias etapas para el mejoramiento. En nuestro ejemplo esta optimizaci6n se puede realizar de manera directa sobre el árbol sintáctico (con anotaciones) al colapsar el subárbol secundario de la derecha del nodo raíz a su valor constante: . (Los atributos también se pueden introducir en la tabla de símbolos. es decir. (La semántica "dinámica" de un programa. y el resultado del análisis semántico en el árbol sintáctico podría representarse por el siguiente árhol con anotaciones: sentencia de risignacidri expreskjn de subíndice r-rl>rt. u optimización. Indicamos esta posibilidad al proporcionar esta operación como una fase por separado en el de compilación. Las partes extra de la información (como los tipos de datos) que se calculan mediante el analizador semántico se llaman atributos y con frecuencia se agregan al árbol como anotaciones o "decoraciones".) En nuestro ejemplo de ejecución de la expresión en C la información de tipo específica que se tendría que obtener antes del análisis de esta línea sería que a sea un arreglo de valores enteros con subíndices proveniente de un subintervalo de los enteros y que index sea una vaiabie entera. y declararía un error de correspondencia de tipo si no fuera así. (Esta optimización particular es conocida como incorporación de constantes. y puede haber posibilidades para el mejoramiento del código que dependerán sólo del código fuente. a saber. En nuestro ejemplo incluimos una oportunidad para la optimización a nivel de fuente.

y efectivamente el optimizador del código fiieiite podría continuar el uso de esta representación en su salida. denominado así porque contiéne las direcciones de (y hasta) tres localidbdes en memoria. o RI. En este sentido. en primer lugar calculando el resultado de la suma y después reemplazando a t por su valor para obtener la sentencia en tres direcciones En la figura 1. también puede referirse de manera más general a crcalquier representación interna para el código fuente utilizado por el compilador. también se puede hacer referencia al árbol sintáctico como un código intermedio. GENERADOR DE CODIGO El generador de código toma el código intermedio o R I y genera el código par3 la niáquina objetivo.1 indicamos la posibilidad de que el optimizador del código fuente pueda emplear código en tres direcciones al referirnos a su salida como a un código intermedio.) Ahora el optimizador mejoraría este código en dos etapas. En nuestro ejemplo el código en tres direcciones para la expresión original en C podría parecerse a esto: t = 4 + 2 a [index] = t (Advierta el uso de una variable temporal adicional t para almacenar el resulrado intermedio de la suma. aunque la mayoría tle los conipiladores . Históricamente. Sin embargo. tal como el código de tres direcciones o una representación lineal semejante. En este texto escribiremos el c6digo objetivo en la fornia de lenguaje ensarnh1. el código intermedio ha hecho referencia a una forma de representación de código intermedia entre el código fuente y el código objeto.idor para facilitar su coinprerisión.Proceso de traducción entero identificador a 6 entem iden tif icador index arreglo de enteros entero Muchas optimizaciones se pueden efectuar directamente sobre el árhol. el cual se ha utilizado en muchos compiladores de Pascal. pero una elecciGn estándar es el código en tres direcciones. Existen muchas variedades diferentes de tal código. En ocasiones este sentido más general se indica al hacer referencia al código intermedio como representación intermedia. pero en varios casos es irás I'ácil optiniizar una forma linealizada del irbol que esté más cercana al código ensamblador. Otra selección popular es el código P.

No sólo es necesario emplear instrucciones que existan en la máquina objetivo. el código intermedio (suponiendo que éstos no sean iguales).generan el código objeto de manera directa. una posible secuencia de código muestra para la expresión dada podría ser (en un hipotético lenguaje ensamblado~) MOV MUL MOV ADD MOV RO. No obstante. ..> RO . reemplazando las instrucciones lentas por otras rápidas. . . . En este código también partimos del supuesto de que la máquina realiza direccionamiento de byte y que los enteros ocupan dos bytes de memoria (de aquí el uso del 2 como el factor de multiplicación en la segunda instrucción).Í . Dichas mejoras incluyen la selección de modos de direccionamiento para mejorar el rendimiento. la tabla de literales y la tabla de símbolos. En el código objetivo de muestra dado es posible hacer varias mejoras. OPTlMlZADOR DE C~DIGOOBJETIVO En esta fase el compilador intenta mejorar el código objetivo generado por el generador de código. Con estas dos optimizaciones nuestro código objetivo se convierte en MOV RO. . . . 6 . 6 . tales como el árbol sintáctico.> dirección a + RO Esto completa nuestra breve descripción de las fases de un compilador.> R1 sumar RO a R1 constante 6 . Una de ellas es utilizar una instrucción de desplazamiento para reemplazar la multiplicación en la segunda instrucción (la cual por lo regular es costosa en términos del tiempo de ejecución).> dirección en R1 En este código utilizamos una convención propia de C para direccionar modos. valor de index . Es en esta fase de la compilación en la que las propiedades de la máquina objetivo se convierten en el factor principal. doble valor en RO dirección de a . index RO. En iruestro ejemplo debemos decidir ahora cuántos enteros se almacenarán para generar el código para la indización del arreglo. Querernos enfatizar que esta descripción sólo es esquemática y no necesariamente representa la organización real de un compilador trabajando. de manera que &a es la dirección de a (es decir. index SHL RO MOV &a[ROl. También analizamos sólo de manera superficial las estructuras de datos necesarias para mantener la información necesaria en cada fase.> RO doble valor en RO constante 6 .. tal como el direccionamiento indizado para realizar el almacenamiento en el arreglo. . En su Lugar. Por ejemplo. tal como cuántos bytes o palabras de memoria ocuparán las variables de tipos de datos enteros y de punto flotante. las fases que describimos están presentes de alguna forma en casi todos los compiladores. &a R1. sino que las decisiones respecto a la representación de los datos desempeñarán ahora también un papel principal. Dedicamos la siguiente sección a una breve perspectiva general de las estructuras de datos principales cn u n compilador. los compiladores muestran una amplia variación en sus detalles de organización. RO *R1. Otra es emplear un modo de direccionamiento más poderoso. . 2 R1. y eliminando las operaciones redundantes o innecesarias. valor de index . la dirección base del arreglo) y que *R1 significa direccionamiento indirecto de registro (de modo que la última instrucción almacena el valor 6 en la dirección contenida en Rl). .

generalmente representa el token de manera simbólica. El escritor del compiladar se esfuerza por implementar estos algoritmos de una manera tan eticaz como sea posible. aunque también se pueden utilizar diversas estructuras de irbol. eliminación y acceso necesitan ser eficientes. por lo regular se construye como una estructura estándar basada en un apuntador que se asigna de manera dinámica a medida que se efectúa el análisis sintiictico. En ocasiones. un nodo de expresión tiene requerimientos diferentes de los de un nodo de sentencia o un nodo de declaración). variables. cada nodo de árbol sintáctico por sí mismo puede requerir de atributos diferentes para ser almacenado. TOKENS Cuando un rastreador o analizador léxico reúne los caracteres en un token. constantes y tipos de datos. TABU DE S~MBOLOS Esta estructura de datos mantiene la información asociada con los identificadores: funciones. Una estructura de datos estándar pasa este propósito es la tabla de dispersión o de cálculo de dirección. o se almacenan en otras estructuras de datos.Estructuras de datas principales en un compilador 1. de acuerdo con la clase de estructura del lenguaje que represente (por ejemplo. por el analizador semántico. el analizador semántico agregará tipos de datos y otra información. las operaciones de inserción. un compilador debería poder compilar un programa en un tiempo proporcional a1 tamaño del programa. en O(n) tiempo. En la mayoría de los lenguajes el analizador léxico sólo necesita generar un token a la vez (esto se conoce como búsqueda de símbolo simple). En esta sección señalaremos algunas de las principales estructuras de datos que son necesarias para las fases como parte de su operación y que sirven para comunicar la información entre las fases. el analizador sintáctico o el analizador semántico puede introducir identificadores dentro de la tabla. . cada nodo en el árbol sintáctico puede estar representado por un registro variable. es decir. ÁRBOL SINTÁCTICO Si el analizador sintictico genera un árbol sintáctico. En este caso. En este caso se puede utilizar una variable global simple para mantener la información del token. El árbol entero puede entonces conservarse como una variable simple que apunta al nodo raíz. puede ser necesario un arreglo de tokens. En ocasiones se utilizan varias tablas y se mantienen cn una lista o pila. En ocasiones tambi6n es necesario mantener la cadena de caracteres misma u otra información derivada de ella. como un valor de un tipo de datos enumerado que representa el conjunto de tokens del Lenguaje fuente. para ahorrar espacio. posteriornmte. En realidad. Por ejemplo. En otros casos (cuyo ejemplo más notable es FORTRAN). con cada clase de nodo conteniendo solamente la información necesaria para ese caso. Puesto que la tabla de símbolos tendrá solicitudes de acceso con tanta frecuencia. La tabla de símbolos interactúa con casi todaslas fases del compilador: el rastreador o analizador léxico. tales como la tabla de símbolos. naturalmente. y las fases de optimización y generación de código utilizarán la información proporcionada por la tabla de símbolos para efectuar selecciones apropiadas de código objeto. el tipo de datos de una expresión puede conservarse como un campo en el nodo del árbol sintáctico para la expresión. que permiten una asignación y desasignación selectivas. donde n es una medida del t a m o del programa (por lo general el número de caracteres). estos campos se asignan de manera dinámica.4 ESTRUCTURAS DE DATOS PRINCIPALES EN UN COMPIIADOR La interacción entre los algoritmos utilizados por las fases de un compilador y las estructuras de datos que soportan estas fases es. es decir. Cada nodo en la estructura es un registro cuyos campos representan la información recolectada tanto por el analizador sintáctico como. sin aumentar demasiado la complejidad. preferiblemente operaciones de tiempo constante. tal como el nombre asociado con un token identificador o el valor de un token de número. De manera ideal. muy fuerte.

la sccuenciación de las operaciones. en especial si se dispone de la compilación por separado en el lenguaje. OTRAS CUESTIONES REFERENTES A LA ESTRUCTURA DEL COMPILADOR La estructura de un compilador se puede ver desde muchos ángulos distintos. En los compiladores que realizan optimizaciones complejas debe ponerse particular atención a la selección de representaciones que permitan una fácil reorganización. . ubicación del NEXT aún no conocida <código para la parte "thenM> NEXT : <código para la parte "else"> Por L regular debe dejarse un espacio en blanco para el valor de NEXT. es decir. Este problema se resolvió mediante el uso de archivos temporales para mantener los productos de los pasos intermedios durante la traducción o bien al compilar "al vuelo". Las limitantes de memoria son ahora un problema mucho menor. y es posible requerir que una unidad de compilación entera se mantenga en la memoria.. Esto se consigue fácilmente con el u\o de un archivo temporal. el cual ae llena o una vez que 5e logra conocer el valor.TABLA DE LITERALES La búsqueda y la inserción rápida son esenciales también para la tabla de literales.3 describimos sus fases. También es necesaria para que el generador de código construya direcciones simbólicas para las literales y para introducir definiciones de datos en el archivo de código objetivo. la cual almacena constantes y cadenas utilkzadas en el programa. Algo típico de éstos es la necesidad de direcciones de corrección hacia atrás durante la generación del código. una tabla de literales necesita impedir las eliminaciones porque sus datos se aplican globalmente al programa y una constante o cadena aparecerá sólo una vez en esta tabla. La persona que escribe el cornpilatlor clehel-ío estar . Por ejemplo. La tabla de literales es importante en la reducción del tamaño de un programa en la memoria al permitir la reutilización de constantes y cadenas. manteniendo sólo la información suficiente de las partes anteriores del programa fuente que permita proceder a la traducción. CÓDIGO INTERMEDIO De acuerdo con la clase de código intermedio (por ejemplo. . debe generarse un salto desde la prueba para la parte bicondicional "else" antes de conocer la ubicación del código para el "else": CMP X. este código puede conservarse como un arreglo de cadenas de texto. un archivo de texto temporal o bien una lista de estructuras ligadas. Otros puntos de vista son posibles: la estructura física del compilador. cuando se traduce una sentencia condicional tal como if x = O then . Sin embargo. y así sucesivamente. Con todo.O JNE NEXT . En la sección 1. . los compiladores ocasionalmente encuentran útil generar archivos intermedios durante alguna de las etapas del procesamiento. código de tres direcciones y código P) y de las clases de optimizaciones realizadas. ARCHIVOS TEMPORALES Al principio las computadoras no poseían suficiente memoria para guardar un programa completo durante la compilación. las cuales representan la estructura lógica de u n compilador. else .

Esto puede. mientras que el generador de código es parte de la etapa final. Por consiguiente. PASADAS Un coinpilador a inenudo encuentra conveniente procesar todo el progmina fuente varias veces antes de gcncrx el código. en parte. Estas repeticiones son conocidas como pasadas. En la práctica esto ha probado ser difícil de conseguir.Otras cuestiones referentes a la estructura del tompilador 15 familiarizada con tantos puntos de vista de la estructura del cotnpilador corito sea posible. es útil separar las etapas del análisis de las etapas de ¡ síntesis. por lo tanto. ETAPA INICIAL Y ETAPA FINAL Esta perspectiva considera al coinpilador separado en aquellas funciones que dependen sólo del lenguaje fuente (lqetapa inicial) y aquellas operaciones que dependen únicamente del lenguaje objetivo (la etapa final). el análisis sintáctico y el análisis semintico pertenecen a la parte de análisis. En esta sección consideraremos otros aspectos de la estructura del conipilador y señalaremos cómo se aplica cada punto de vista. el aniilisis léxico. . por consiguiente. ya que la estructura del compilador será determinante para su confkthilidad. parte de la etapa inicial. ser culpa de los cambios rápidos y fundamentales tanto en los lenguajes de pro-" gramación como en las arquitecturas de las máquinas. De manera ideal. mientras que la síntesis requiere de técnicas niis especializadas. -F 6 o luente ' inicial Comgo intermedio I Etapa fina' & Cooigo oqetivo Esta estructura es especialmente importante para la portabilidad del compilador. No obstante. Como cs tiatural. ya sea del código fuente (lo que involucra volver a escribir la etapa inicial) o del código objetivo (lo que implica reescribir la etapa final). mientras que la síntesis del código intermedio es a inenudo independiente del objetivo y. El análisis tiende a ser más inatemitico y a comprenderse mejor. pero también es difícil retener de manera eficaz a toda la información que uno pudiera necesitar al cambiar a un nuevo lenguaje objetivo o al crear las estructuras de datos adecuadamente generales para permitir un cambio a un nuevo lenguaje fuente. del compilador. Esto es similar a la división en análisis y síntesis: el analizador léxico. inicntras que las operaciones involucradiis en la producción del código traducido se conocen como la parte de síntesis. algo del inálisis de optimización puede ser dependiente del objetivo y. en la cual el compilador está diseñado con un enfoque hacia la modificación. Las etapas de optimizacicín pueden involucrar tanto análisis como síntesis. el compilador estaría estrictamente dividido en estas dos seccioties. Después del paso iniciitl. Sin embargo. mientras que Iii generación del código es la síntesis. una pasada coiiiste en proccs. una tentativa consistente para separar las etapas inicial y final redundar6 en beneficios pamtina portabilidad mís fácil. En esta perspectiva las operaciones del conipilador que analizan el programa fuente para calcular sus . con la representación intermedia como el medio ile comunicación entre ellas: . y los denominados compiladores portátiles todavía tienden a poseer características que dependen tanto del lenguaje fuente como del lenguaje objetivo.-. . eficacia. utilidad y mantenimiento.tr la reprc~cntocicíiiiilternictli:i. tlonde sc corlstruye un irbol sintáctico o un ccídigo intermedio a partir tle la fuente. propiedades se clasifican como la parte de análisis del conipilador. de modo que cada una se puea da modificar de manera independiente respecto a la otra. el analizador sintáctico y el analizador semántico son parte de la etapa inicial. parte de la etapa final.

^~n realidad. Estas descripciones (junto con la estructura sintáctica y Iéxica formales) generalmente son recopiladas en un manual de referencia del lenguaje. y una tercera pasada para generación de código y optimización a nivel del objetivo. un compilad& puede ser de una pasada. pero en ocasiones se hace más fácil por la existencia de un conjunto de programas de prueba estándar (una suite o serie de pruebas) contra el cual un compilador puede ser contrastado (una serie de pruebas así existe para Ada). Esto a menudo no es una tarea fácil.5. En cualquier caso. Ocasionalmente. en el que todas las fases se presentan durante un paso único. Los compiladores fuertemente optimizadores pueden emplear incluso más pasadas: cinco. y 13s técnicas de semántica formal no se estudiarán aquí. Las pasadas nueden corresoonder o no a las fases. especialmente en la comunidad de programación funcional. aunque la denominada semántica denotacional se ha convertido en uno de los métodos más comunes. 3. o definición de lengua. sintáctica y semántica especificadas en las secciones 2. (Modula-2 es un lenguaje cuya estructura requiere que un compilador tenga por lo menos dos pasadas. El lenguaje de ejemplo TINY empleado en el texto tiene su estructura Iéxica.) La mayoría de los compiladores con optimizaciones utilizan más de una pasada. Con un nuevo lenguaje. tal como la ANSI (American National Standards Institute) o ISO (Intemational Organization for Standardization). un lenguaje tendrá su semántica &da mediante una definición formal en términos matemáticos. En ocasiones esta definición de lenguaje ha alcanzado el nivel de un lenguaje estándar que ha sido aprobado por algtina de las organizaciones de estandarización oficiales. i que las estructuras Iéxicas y sintácticas de un lenguaje de programaciún por lo regular son especificadas en términos formales y utilizan expresiones regulares y gramáticas libres de contexto. Esto resulta en una compilación efkaz pero también en (por lo regular) un código objetivo menos eficiente.ie. alterando su estructura o produciendo una representación diferente. y ningún método ha conseguido el nivel de un estándar. el escritor de compiladores debe interpretar la definición de lenguaje e implementar un compilador acorde con la definición de lenguaje. respectivamente.7 y 6. la semántica de un lenguaje de programación todavía es comúnmente especificada utilizando descripciones en inglés (uotro lenguaje natural). Sin embargo. Pascal y C tienen estándares ANSI. Ada tiene un estándar aprobado por el gobierno de Estados Unidos. Similarmente. Cuando existe una definición formal para un lenguaje. . En este caso. Tanto Pascal como C son lenguajes que permiten L compilación de una pasaa da. por lo regular se emplea una pasada para análisis léxico y sintáctico. D E F I N I C I ~ NDE LENGUAJE Y COMPILADORES Advertimos en la sección l. entonces (en teoría) es posible dar una prueba inatenxítica de que un compilador se aviene a la definición. I i INTROOUCCI~N agregando información a ella.5. una definición de lenguaje y un compilador con frecuencia son desarrollados de manera simultánea. El apéndice A contiene un manual de referencia del lenguaje mínimo para el lenguaje de proyecto de compilador C-Minus. a menudo una pasada consistirá de varias etapas.CAP. puesto que las técnicas disponibles para el escritor de compiladores pueden tener un impacto fundamental sobre la definición del lenguaje. Por ejemplo. la manera en la que se define un lenguaje tendrh un impacto fundamental sobre las técnicas que son necesarias para construir el compilador. seis o incltiso ocho no son algo fuera de lo común. Sin embargo. Una situación más común para el escritor de compiladores es que el lenguaje que se está implementando es bien conocido y tiene una definición de lenguaje existente. FORTRAN. dependiendo del lenguaje. otra pasada para análisis semántica y opti&ación a nivel del fuente. las técnicas para hacerlo así rebasan el alcance de este texto. Varios métodos actualmente en uso son empleados para esto. hacer esto es tan dificil que casi nunca se hace.

en la medida que no necesita generarse código para mantener el ambiente. Por ejemplo. También. tales como LISP y Smalltalk. mientras que en C son parte de la especificación de una librería estándar). porque se requiere que la memoria también sea liberada automáticamente.Otras cuestiones referentes a la estructura del compiladar 17 Un aspecto de la construcción de compiladores que es particularmente afectado por la definición de lenguaje es la estructura y comportürniento del ambiente de ejecución. tienen un efecto decisivo sobre L complejidad del sistema de ejecución. En ocasiones una definición de lenguaje especificará que se debe proporcionar cierta pragmática. vale la pena notar aquí que la estructura de datos permitida en un lenguaje de prograntación. denominadas pragmas. son parte del propio lenguaje. desde el cual el prograniador puede organizar la asignación dinámica.. Sin embargo. Pascal. Esto es complicado. Ejemplos de mecanismos de interfaces son el suministro de la entrada y facilidades de salida. varias directivas de compilador. generan un listado de compilador para la parte del programa contenida dentro de los pragmas. y esto a su vez requiere complejos algoritmos de "recolección de basura". aunque una completa relación de esta área rebasa el alcance de este libro. FORTRAN77. además del acceso al sistema de archivos de la máquina objetivo. En segundo lugar. son parte de la definición del lenguaje. puesto que involucran consider:iblcs detalles que varían clemasiado de un sistema operativo a otro. . no trataremos cuestiones sobre interfaces de entraddsalida y sistenta operativo. En este texto veremos directivas de cornpilador solamente en el contexto de la generación de un listado con información para propósitos de la depuración del compilador. mensajes de error. prasma LIST(0FF). sin apuntadores o asignación dinámica y sin llamadas a funciones recursivas. Finalmente. en orden creciente de complejidad. Los ambientes de ejecución se estudian con detalle en el capítulo 7. Esto hace la labor de asignación particularmente fácil para la persona que escribe el compilador.. C y otros lenguajes provenientes del lenguaje Algol permiten una forina limitada de asignación dinámica y llamadas de funciones recursivas y requieren un ambiente de ejecución "semidinámico" o basado en pilas con una estructura dinjmica adicional conocida conio hetzp. los lenguajes funcionales y la mayoría de los lenguajes orientados a objetos. las sentencias de Ada prasma LIST(0N) . Examinaremos esos métodos junto con nuestro estudio de los ambientes de ejecución. y particularmente las clases de llamadas de funciones y valores devueltos permiiidos. Por ejemplo. tablas de referencia cruzada) así como opciones de optimización de código (rendimiento de ciertas optimizaciones pero no otras). . Tanto la interface como las opciones son calificadas colectivamente como las pragmáticas del compilador. En particular. donde toda la asignación de memoria se hace antes de la ejecución. permite un ambiente de ejecución completamente estrítico. Pascal y C especifican ciertos procedimientos de entradalsalida (en Pascal. En Ada. OPCIONES DE COMPllADOR E INTERFACES Un aspecto importante de la construcción de compiladores es la inclusión de mecanismos para hacer interfaces con el sistema operativo y para proporcionar opciones al usuario para diversos propósitos. a los tres tipos básicos de ambientes de ejecución. mn como se describe i continuación: En primer lugar. Ejemplos de opciones de usuario incluyen la especificación de listar características (longitud. requieren un ambiente "c«mpletamente dinámico" en el que toda asignación se realiza de manera automática mediante código generado por el compilador.

Esto requiere que un compilador genere código extra. l I INTRODUCCI~N MANEJO DE ERRORES Una de las funciones más importantes de un compitador es su respuesta a los errores en un programa fuente. Éstos pueden complicar sustancialmente la administración de un sistema de ejecución. sino también ciertos errores de ejecución. entonces solamente necesitamos compilar el nuevo coinpilador utilizando el compilador existente para obtener un programa ejecutable: Cornpilador para lenguaje A escrito en lenguaje B Cornpilador en ejecución para lenguaje A Si el compilador existente para el lenguaje B se ejecuta en una máquina diferente de la in5quina objetivo. Los errores pueden ser detectados durante casi cualquier fase de la compilación. a menudo esto no es adecuado. 1. Un compilador escrito en el lengwje H (por 1~111.CAP. Por consiguiente. un manejador de errores debe contener operaciones diferentes. No consideraremos la implementación de un mecanismo así. Un enfoque más razonable en la actualidad es escribir el compilador en otro lenguaje para el cual ya exista un compilador. el cual realizará pruebas de ejecución apropiadas para garantizar que todos esos errores provocarán un evento apropiado durante la ejecución. Así fue en realidad como se escribieron los primeros compiladores. por lo tanto. pero mostraremos c h o un compilador puede generar código de prueba para asegurar qué errores de ejecución especificados ocasionarán que se detenga la ejecución. cada una apropiada para una fase y situación específica. este lenguaje de impleinentación (o lenguaje anfitrión) tendría que ser lenguaje de máquina. es decir. Para que el compilador se ejecute inmediatamente. las técnicas de manejo de errores para cada fase se estudiarán de manera separada en el capítulo apropiado. Estos errores estáticos (o de tiempo de compilac'ión) deben ser notificados por un compilador. Ésta y otras situaciones niis coutplejas se describen mejor al esquematizar un conipiliidor como un diagrama T (Ilanriido así ticbido a su fortna). La compilación produce entonces un compilador cruzado. El más simple de tales eventos será detener la ejecución del programa. C d a fase de un compilador necesitará una clase ligeramente diferente de manejo de errores.6 ARRANQUE AUTOMATICO Y PORTABILIDAD Hemos analizado el lenguaje fuente y el lenguaje objetivo como factores determinantes en la estructura de un compilador y la utilidad de separar cuestiones de lenguaje fuente y objetivo en etapas inicial y final. puesto que esencialmente no existían compiIadores todavía. entonces la situación es un poco inás complicada. y una definición de lenguaje puede requerir la presencia de mecanismos para el manejo de excepciones. Si el compilador existente ya se ejecuta en la máquina objetivo.qiccqr hosi . Pero no hemos mencionado el tercer lenguaje involucrado en el proceso de construcción de compiladores: el lenguaje en el que el compilador mismo está escrito. un compilador que genera código objetivo para tina iniquina diferente de aquella en la que puede ejccutarse. Una definición de lenguaje por lo general requerirá no solamente que los errores estrlticos sean detectados por un compilador. Sin embargo. y. y es importante que el compilador sea capaz de generar mensajes de error significativos y reanudar la compilación después de cada error. especialmente si un programa puede continuar ejecutándose desde el punto donde ocumó el error.

utilizando un coinpilador existente para el lenguaje B en la máquina H para traducir un compilador de lenguaje A a H escrito en B. si tenemos dos compiladores que se ejecutan en la misma mhquina H.Arranque automático y portabilidad 19 o anfitrión) que traduce lenguaje S (de source o fuente) en lenguaje T (por I<iri. entonces la consideraremos como ccídigo ejecutable para una máquina hipotética). Expresamos lo anterior como sigue: En segundo lugar podemos utilizar un compilador de la "máquina" ti a la "tnáquina" K para traducir el lenguaje de irnplementación de otro compilador de H a K. uno de los curtles traduce lenguaje A al lenguaje B mientras que el otro traduce el lenguaje B al lenguaje C. Expresamos esto últiino como sigue: Ahora el primer escenario que describimos anteriormente. puede verse como el siguiente diagrama.qlcigr Tcit. el compibador produce código para la misma máquina que aquella en la que se ejecuta). Primero. el cual es precisamente un caso especial del diagrama anterior: . El resultado es un compilador de A a C en la máquina H. es decir. esperamos que H sea lo mismo que T (es decir. pero no es necesario que éste sea el caso. entonces podemos coinbinarlos dejando que la salida del primero sea la entrada al segundo. Típicamente.y~t u objetivo) se dibuja como el siguiente diagrama T: Advierta que esto es equivalente a decir que el compilador se ejecuta en la "ináquina" H (si H no es código de ináquina. Los diagrainas T se pueden combinar en dos maneras..

2 y 1. Entonces volvemos a compilar el compilador "bueno" para producir la versión final. traduciendo d a mente aquellas características de lenguaje que en realidad sean utilizadas en el compilador (teniendo.26. Podemos escribir un compilador "rápido e impreciso" en lenguaje ensamblador. Una vez que tenemos el compilador "rápido e impreciso" en ejecución. por ejemplo. ~ Después del arranque automático tenemos un compilador tanto en código fuente como en código ejecutable. La ventaja de esto es que cualquier mejoramiento al código fuente del Figura 12a Primera etapa en un proceso de arranque automático Compilador es&o en su ~ompilado. Este compilador "rápido e impreciso" también puede producir código muy ineficiente (¡solamente necesita ser correcto!). Este proceso se denomina arranque automático por transferencia. naturalmente. cómo podemos enfocar el problema de la circularidad. Compilador "rápido e impreciso" escrito en lenguaje de máquina / . I I INTRODUCCI~N El segundo escenario que describimos (donde el compilador de lenguaje B se ejecuta en una máquina diferente.en ejecucibn pero ineficiente propio lenguaje A .CAP. el compilador mismo no puede ser compilado) existen ventajas importantes que se obtienen de este enfoque. limitado nuestro uso de esas características cuando escribamos el compilador "bueno"). lo utilizamos para compilar el compilador "bueno". Considere. si todavía no existe compilador para el lenguaje fuente. Este proceso se ilustra en las figuras 1 . lo cual resulta en un compilador cruzado para A) puede describirse de manera similar como sigue: Es común escribir un compilador en el mismo lenguaje que está por compilarse: Mientras que parece ser un enredo de circularidad (puesto que.

Transportar ahora e1 compilador a una nueva computadora anfitrión solamente requiere que la etapa final del código fuente vuelva a cscribirse para generar código para la nueva máquina.3a Transportación de u n n compilador escrito e su propio lenguaje fuente (paso 1 ) Cornpilador original figura 13b Transportación de u n campilador escrito en su propio lenguaje fuente (paso A K .Lenguaje y compilador de muestra T Y N l Fiqura 1.3h.7 LENGUAJE Y COMPllADOR DE MUESTRA TlNY Un libro acerca de la construcción de compiladores estaría incompleto sin cjcmplos para cada paso cn el proceso de conipilacih.- *Código fuente del cornptlador redirtgido a K Compilador redirigido 4 Compilador cruzado 1.ado. y el compilador es nuevamente recompilado mediante el compilador cruzado para producir una versión de trabajo para la nueva máquina. Pero existe otra ventaja. aplicando el mismo proceso de dos pasos anterior.--. y Esto se ilustra en las figuras 1 . En muchos casos iIustrrirein«s técniciis con ejeinplos . Fioura 1.2b s g n a etapa en un eud po e o de arranque rcs automático Compilador escrito en su propio lenguaje A Versión final del compilador ~om~ilador-en ejecución pero ineficiente (de la primera etapa) compilador puede ser transferido inmediatamente a un coinpilador que esté trabajando. 3 ~ 1. Éste se compila entonces utilizando el compilador antiguo para producir un compilador cniz.

tiene demasiado detalle y sería algo abrumador para estudiar dentro del marco conceptual de un texto. uno que esperaríamos utilizar en la programación cotidiana. estos ejemplos no son suficientes para mostrar cónm se conjuntan todas las partes de un compilador. En cambio. Una expresión aritmética puede involucrar constantes enteras.1 apéndice B. . Este requerimiento (que se ha demostrado en un compilador real) es difícil.que son sustraídos de lenguajes existentes.También existen sentencias de lectura y escritura que realizar: i~raddsalida. El código completo del compilador se recopila cn i. Un listado del simulador de TM en C aparece en el apéndice C. Intentaremos satisfacer estos requerimientos al proporcionar código fuente completo en C (ANSI) para un lenguaje pequeño cuyo compilador se pueda comprender fácilmente una vez que las técnicas se hayan entendido. y las variables son declaradas simplemente al asignar valores a las mismas (de modo parecido a FORTRAN o BASIC). Las expresiones en TINY también se encuentran limitadas a expresiones aritméticas enteras y hooleanas. Las expresiones hooleanas pueden aparecer solamente conio pruebas en sentencias de control: no hay variables booleanas. paréntesis y cualquiera de los cuatro operadores enteros +. Todas las variables son variables enteras. Una expresión hooleana se compone de una comparación de dos expresiones aritméticas que utilizan cualesquiera de los dos operadores de comparación < y =. es decir. Por eso. con las propiedades matemáticas habituales. Un problema adicional es la elección del lenguaje de máquina a utilizar como el lenguaje objetivo del compilador TINY. No hay procedimientos ni declaraciones. 1. Pero la elección de un procesador específico también tiene el efecto de limitar la ejecución del código objetivo resultante para estas máquinas.Los comentarios. Pascal y Ada. -. no podría esperar demostrar de manera adecuada todas las características que un compilador "real" necesita. tales como C. Ambas sentencias de control pueden ellas mismas contener secuencias de sentencias: Una sentencia "if' tiene una parte opcional "else" y debe terminarse mediante la palabra clave end. Daremos un rápido vistazo a esta máquina aquí. Por otra parte. El código para su compilador se analizará a medida que se cubran las ttknicas. * y /. No obstante. Nuevamente. tantbién es necesario exhibir un compilador completo y proporcionar un comentario acerca de su funcionamiento. la complejidad al utilizar código de máquina real para un procesador existente hace que una selección de esta naturaleza sea difícil. un compiiador para un lenguaje muy pequeño. simplificamos el código objetivo para que sea el lenguaje ensamblador para un procesador hipotético simple. cuyo listado pudiera compriniirse en aproximadamente 10 páginas de texto. el cual conoceremos como la máquina TM (por las siglas de "TINY Machine"). que no deben estar anidados. C++. se permiten dentro de llaves tipográficas. asignación o E/S (entradalsalida). variables. Llamaremos a este lenguaje TINY y lo utilizaremos como un ejemplo ejecutable para las técnicas estudiadas en cada capítulo. pero aplazaremos una descripción más extensa hasta el capítulo 8 (generación de código). Existen solamente dos sentencias de control: una sentencia "it" y una sentencia "repeat". (división entera). En esta sección proporcionaremos una perspectiva general del lenguaje y su compilador.71 Lenguaje TINY Un programa en TINY tiene una estructura muy simple: es simplemente una secuencia de sentencias separadas mediante signos de punto y coma en una sintaxis semejante a la de Ada o Pascal. Un compiiador "real".

4 proporciona un programa de muestra en este lenguaje para la conocida Func i h factorial.c parse. con números de línea y en el orden dado.h. . con los prototipos de función disponibles externamente dados en el archivo de cabecera e implementatlos (posiblemente con funciones locales estáticas adicionales) cn el archivo de código asociado. todavía es lo suficientemente extenso para ejemplificar la mayoría de las características esenciales de un compilador. Los archivos restantes se componen de pares de archivos de cabecerdcódigo. Los archivos scan. 12 Compilador TlNY 1 . ( introducir un entero 1.c . write fact ( salida del factorial de x 1 end Aunque TINY carece de muchas características necesarias para los lenguajes de programación reales (procedimientos. flfllra 14 Un programa en lenguaje T ~ N Yque proporcma a la &la el fattorial de su entrada Programa de muestra en lenguaje TINY calcula el factorial - 1 read x. donde enumeramos los archivos de cabecera ("header") (por inclusión) a la izquierda y los archivos de código ("code") a la derecha: globals h uti1.h code. c uti1. Los archivos util contienen funciones de utilerías necesiirias pira generar la rcpresentaci6n interna del cíldigo fuente (el irhol sinticticoi .Lenguaje y tompilador de muestra TlNY 23 La figura 1. . Utilizaremos este programa como un ejemplo de ejecución a través del texto. c symtab c analyze c code. analyze y cgen corresponden exactamente a las fases del analizador léxico. parse. analizador sintáctico. main. excepto que main. arreglos y valores de punto flotante son algunas de las omisiones más serias). c está listado antes de globals . El archivo main. l . El código fuente para estos archivos se encuentra listado en el apéndice B. c contiene el programa principal que controla el compilador. until x O. El cotnpitador TINY se compone de los siguientes archivos en C.h symtab h ana1yze. c cgen. h . h cgen. if x > O then ( no calcule si x <= O j fact := 1.h 8can.c scan. . Contiene las definiciones de los tipos de dhos y variables globales utilizadas a lo largo del compilador. repeat fact := fact * x. y asigna e inicializa las variables globales. analizador semántica y generador de código (le la figura l . El archivo de cabecera global8 h está incluido en todos los archivos de código.h parse.

. Los archivos symtab contienen una implementación de tabla de cálculo de dirección de una tabla de símbolos adecuada para usarse con TINY.79 y 94 del apéndice B): eyntaxTree = garse ( ) .CAP.tny (El compilador también agregará la extensión tny si es on~itida. symtab.c garse .h. la tabla de sín~bolos interactúa solamente con el analizador semántico y el generador de código (de modo que aplazaremos un análisis de esto hasta el capítulo 6).1 se encuentran ausentes: no hay tabla de literales o manejador de error por separado y no hay fases de optimización. typeCheck(syntaxTree).h. Suponiendo que el nombre del archivo ejecutable es tiny.h. también hemos hecho el compilador de cuatro pasadas: la primera pasada se compone del analizador léxico y el analizador sintáctico. buildSymtab(syntaxTree). Ignorando las banderas y la compilación condicional. Tampoco hay código intermedio separado del árbol sintáctico. Para reducir la interacciún entre estos archivos. c que controla estas pasadas es particularmente simple. c Aunque este diseño para el compilador TINY es algo poco realista.uchiwt) y (si la generación del .. El código en main.h.se puede utilizar para compilar un programa fuente de TINY en el archivo de texto samgle. util. Construye un compilador que únicamente realiza análisis sintáctico y léxico. Las banderas.codefile). scan. util.tny al emitir el comando tiny samgle. lo que construye el árbol sintáctico. globals . tiene la ventaja pedagógica de que los archivos por separado corresponden aproximadamente a las fases. analyze. Los componentes restantes de la figura 1. Adicionalmente.h.7. descrita en la sección 1. y se pueden analizar (así como compilar y ejecutar) de manera individual en los capítulos que siguen. también integramos banderas de compilación condicional que hacen posible construir compiladores parciales.) Esto imprimirá un listado de programa en la pantalla (el cual be puede redirigir a un . Por flexibilidad. la última pasada es el generador de código. c symtab. la segunda y tercera pasadas se encargan del análisis semántico. main. se describen a continuación: MARCA O BANDERA ARCHIVOS NECESARIOS PARA COMPILACIÓN (ACUMULATIVOS) SU EFECTO SI SE ACTIVA NO-PARSE NO-CODE Construye un compilador solamente de análisis léxico.c. I 1 INTRODUCCldN y exhrbir la información de error y listado.c.c. con sus efectos. con la segunda pasada construyendo la tabla de símbolos mientras que la tercera pasada realiza la verificación de tipos. code~en(syntaxTree. 77. El compilador TINY se puede compilar por cualquier compilador C ANSI.h. Construye un compilador que realiza el análisis semántico pero no genera código.on dependientes de la máquina objetivo (la máquina TM.3). Los archivos code contienen utilerías para la generación de código que . scan. garae. analyze. el código central es como sigue (véanse las líneas 69.

De hecho.lenguaje y comp~ladorde muestra TlNY 25 código se activa) también generará el archivo de código objetivo samgle t m (para utilizarse con la máquina TM. Para dar alguna idea de la simplicidad de esta máquina traducimos el código para la expresión C en el lenguaje ensamblador de TM (compare éste con el lenguaje ensamblador hipotético para la misma sentencia en la sección 1. 11 Máquina TM 1 . La máquina TM tiene instrucciones suficientes para ser un objetivo adecuado para un lenguaje pequetio como TINY. todos por sus siglas en inglés. página 12): LDC 1. TM tiene algunas de las propiedades de las computadoras con conjunto de instrucciones reducido (o RISC.0 carga 6 en registro 1 LDC 1.6(0) ST 1.0(0) carga O en registro 1 * la instrucción siguiente * supone que a está en la localidad 20 en memoria LDA 1. Presenta la información resumida acerca de la tabla de sírnbolos y la verificación de tipos. que establece para la direcciijn . que se describe más adelante). como en 1 0 ( 1) (instrucción 2 del código precedente).3. Presenta el árbol sintáctico en un formato linealizado. Empleamos el lenguaje ensamblador para esta máquina como el lenguaje objetivo para el compilador TINY. Existen diversas opciones para la información en el listado de compilacióii. de Reduced Instruction S@ Computers).10(1) carga valor para 10 + R1 en RO carga 2 en registro 1 LDC 1. LD es "carga desde niemona" y LDA es "dirección de carga". todos dados por instrucciones diferentes: LDC es "constante de carga". SU EFECTO SI SE ACTIVA Hace eco (duplica) el programa fuente TlNY hacia el listado junto con los números de línea.0(0) carga O en registro 1 * la instrucción siguiente * supone que index está en la localidad 10 en memoria LD 0.0(0) almacena Rl en O+RO Advertirnos que existen tres modos de direccionamiento para la operación de carga.0 pon R ~ * R Oen RO LDC 1. Presenta la información en cada token a medida que el analizador léxico lo reconoce. Imprime conientarios del rastreo de la generación de código hacia el archivo de código.1.1. Se cncuentran disponibles las siguientes banderas: MARCA O BANDERA Echosource TraceScan Traceparse TraceAnalyze TraceCode .20(1) carga 20+R1 en RO pon R ~ + R O RO en ADD 0.2(0) MUL 0. Notamos también que las direcciones siempre deben ser dadas corno valores de "registro+desplaiamiertto". en que toda la aritmética y las pruebas deben tener lugar en registros y los modos de direccionamiento son muy limitados.

tm samgle. Este comando provoca que el archivo de código sea ensamblado y cargado.0. para evitar la coniplejidad extra de vincularse con rutinas externas de entradalsalida. De este modo. Fe describe en el apéndice A. si sample.8 C-MINUS: UN LENGUAJE PARA UN PROYECTO DE COMPILADOR Un lenguaje más extenso que TINY. en el sentido de que no hay etiquetas o direcciones simbólicas. tm TM simulation (enter h for helg) Enter command: go Enter value for IN instruction: 7 OUT instruction grinta: 5040 HALT: . tny es el programa de muestra de la figura 1. esto en realidad se refiere a la localidad absoluta 10. Esto se debe al f«nnnt« tinifornie simple del ensamhlador de TM.. donde las operaciones fueron de "dos direcciones"). Así. Sin embargo. . la máquina TM contiene facilidades integradas de EIS para enteros. los dispositivos estándar durante la simulación.calculada el agregar el desplazamiento 10 al contenido del registro l . entonces el ~imulador se puede ejecutar de manera TM interactiva. entonces el factorial de 7 i e puede calcular con la interacción siguiente: . pero cl registro es ignorado y el <lesplazamiento mismo es cargado como una constante. El cooiando LDC también requiere un formato de registrotdesplazantiento.0 Halted Enter command: guit Simulation done. se puede emplear al emitir el comando donde sample. El simulador TM se puede compilar desde el código fuente tm. t m es. Éste es un subconjunto considerablemente restringido de C. el archivo de código producido por el compilador TINY a partir del archivo fuente sample tny. evitamos la complejidad agregada de traducir el lenguaje ensainblador a código de máquina. el compilador TINY todavía debe calcular direcciones absolutas para los saltos.)' También advertimos que las instrucciones aritméticas MUL y ADD pueden tener solamente operandos de registro y son instrucciones de "tres direcciones". Suponiendo que el archivo ejecutable se llama tm. éstas son leídas desde. nuestro simulador no es un verdadero ensamblador. y escritas hacia.4.3. Nuestro simulador para la máquina TM lee el código ensamblador directamente de un archivo y lo ejecuta. página 12. en que el registro objetivo del resultado se puede especificar independientemente de los operandos (contraste esto con el código en la sección 1. (Puesto que el O fue cargado en el registro 1 en la instrucción previa.. También. c utilizando cualquier compilador C ANSI. 0. adecuado para un proyecto de compilador. 1. Por ejemplo. al 1. por ejemplo.

Los errores sintácticos incluyen tokens olvidados o colocados de 2.3. La entradalsalida en este programa es proporcionada por una función read y una función write que se pueden definir en términos de las funciones estándar de C scanf y grintf. 'Tiene una sentencia "if' y una sentencia "while". En el apéndice A proporcionamos una guía sobre cómo modificar y extender el compilador TINY a C-Minus.5 Un programa de C-Minur que da a la salida el factarial de su entrada int fact( int x ) / * función factorial recursiva * / { if (X > 1) return x * fact(x-1). con una llamada a r a n ' Como un ejemplo de un programa en C-Minus.Ejercicios 27 cual llamaremos C-Minus. y haga una lista de todos los programas acompaiíantes que se encuentran disponibles con el compilador junto con una breve descripción de sus funciones.4 usando una función recursiva. o funciones sin tipo).3 Los emres de compilación pueden dividirse aproximadamente en dos categorías: errores sintácticos y errores semánticos. I Seleccione un compilahr conocido que venga empacado con un ambiente de desarrollo. Para que sea consistente con otras funciones en C-Miiius. arreglos de enteros y funciones (incluyendo procedimientos. Una función main se debe declarar al último.2 Dada la asignación en C dibuje un irbol de análisis gramatical y un árbol sintáctico para la expresión utilizando como guía el ejemplo semejante de la sección 1. main es declarüda como una función void ("sin tipo") con una lista de parámetros void. 1. pero la máquina TM todavía es un objetivo razonable para su compilador.Sescribimos el programa factorial de la figura 1. Tiene declaraciones (estáticas) locales y globales y funciones recursivas (simples). La ejecución comienza ni. x = read() . EJERCICIOS 1. if ( x > O ) write( fact(x) 1 ). Contiene enteros. muchos compiladores de C aceptaran csta anotación. Mientras qne esto difiere del C ANSI. 1 void main( void ) { int x. . else return 1. particularmente en sus requerimientos de generación de código. C-Minus es un lenguaje más complejo que TINY. 1. Carece de casi todo lo demás. Figura 1. Un programa se compone de una secuencia de declaraciones de variables y funciones. en la figura 1.

Utilizamos una flecha * para indicar la reducción de un patrón de dos diagramas T a un solo diagrama T. a. donde x es una variable de tipo .7 1. Un formateador de texto b. y = x + 2 .6 1.4 Esta pregunta supone que usted trabaja cowun compillidor que tiene una opción para producir salida en lenguaje ensamblador.CAP. I I INTRODUCCI~N manera incorrecta. Por ejemplo. 1.5 1. tal como la asignación x . utilizando propagación de constantes (e incorporación de constantes).8 Determine si su compilador realiza propagaciÓn/incorporación bajo estas circunstancias. Los errores semánticos incluyen tipos incorrectos en expresiones y variables no declaradas (en la mayoría de los lenguajes). en la cual permitimos que tenga lugar una secuencia de redncciones. Un módulo de impresión a. ¿Qué implicación tiene hacer esto en el número de pasadas? Describa las tareas que realizan los programas siguientes y explique en qué se parecen o se relacionan con los compiladores: c. Proporcione dos ejemplos más de errores de cada clase en un lenguaje de su elección. e. ¿En qué difiere esto del inciso b? Si su compilador aceptara la entrada directamente desde el teclado. Utilice diagramas T para describir los pasos que tornaría para crear un compilador trabajando pira Pascal. arreglo. determine si dicho compilador lee el programa entero antes de generar mensajes de error o bien genera mensajes de error a medida que los encuentra. ¿Cómo influye esto en el número de pasadas'? 1. b. tal como el paténtesis derecho olvidado en la expresión aritmética (2+3 . a. . por el código Determine si su compilador efectuó proptación de constantes. Una situación relacionada con la propagación de constantes y la incorporación de constantes es el uso de las constantes nombradas en un programa. Seleccione un compilador con el que esté familiarizado y determine si se enumeran todos los errores sintácticos antes de los ermres semibticos o si los errores de sintaxis y de semántica están entremezclados. Proporcione tantas razones como pueda de por qué la propagación de constantes es más difícil que la incorporación de constantes. Utilizando una constante nombrada x en vez de una variable podemos traducir el ejemplo anterior como el siguiente código en C: const int x = 4. b. Una optimización relacionada pero ntás avanzada es la de propagación de constantes: una variable que actualmente tiene un valor constante es reemplazada por ese valor en las expresiones. el código (en sintaxis de C) se reemplazaría. Determine si su compilador realiza optiiiiizaciones de incorporación de constantes. Un preprocesador de lenguaje Suponga que tiene un traductor de Pascal a C escrito en C y un compilador trabajando para C. 2. c. d. . Podemos considerar esta flecha como una "relación de reduccih" y formar su cerradura transitiva **.

el cual incluye un compilador de Pascal que produce códiga P. en el que las letras establecen lenguajes arbitrarios. Un método así es utilizado por el sistema P de Pascal. Para una visión general de los conceptos de los lenguajes de programación. el manejo de errores en los capítulos 4 y 5. a.10 El proceso de transportar un compilador puede ser considerado como dos operaciones distintas: reasignaeión del objetivo (la modificación del compilador para producir código objetivo para una nueva máquina) y reasignación del anfitrión (la modificación del compilador para ejecutarse en una nueva miquina). Puede encontrarse también ahí información adicional acerca de la jerarquía de Chomsky (a\í como en el capítulo 3).simula la ejecución del código P. véase Louden [1993] o Sethi [1996]. IOTAS P REFERENCIAS La mayoría de los temas mencionados en este capítulo se tratan con más detdle en capítulossubsiguientes. Éste se describe con detalle en Stallman [1994]. un compilador que produce código ejecutable para la máquina anfitrión. b. Describa los pasos necesarios para obtener un compilador trabajando para Pascal en una máquina arbitra& dado un sistema P de Pascal.6 y en la figura 1. Lex se estudia en el capítulo 2. Tanto el compilador de Pascal como el intérprete de código P están escritos en código P. Pueden hallarse descripciones completas de los compiladores de C en Fraser y Hanson [1995] y Holub 119901. Un compilador popular de C/C++ cuyo código fuente se encuentra ampliamente disponible a través de Intemet es el compilador Gnu. 1. Describa los pasos necesarios para obtener un compilador trabajando en el cúdigo nativo de su sistema. determine cuáles lenguajes deben ser iguales para que L reducción sea válida. la generación de código. en vez de utilizar el intérprete de código P). finalmente. . el código en tres direcciones y el código P en el capítulo 8. Analice las diferencias de estas dos operaciones en términos de diagramas T. Una útil referencia para la teoría de autómatas desde un punto de vista matemático (en oposición al punto de vibta práctico adoptado aquí) es la de Hopcroft y Ullman [1979].3 es utilizar un intérprete para el código intermedio producido por el compilador y suprimir una etapa final del todo. y las notas y referencias de esos capítulos proporcionarán las referencias adecuadas. la verificación de tipos. y muestre los pasos de la a reducción simple que la hacen válida: Proporcione un ejemplo prktico de la reducción que describe este diagrama. Por ejemplo. tablas de símbolos y análisis de ahlbutos en el capítulo 6. en el inciso a (es decir. con información acerca de sus interacciones con los traductores. particularmente para la teoría y los algoritmos. Una referencia estándar completa acerca de los compiladores es Aho [1986]. 1. Un texto que ofrece muchas sugerencias útiles de implementación es Fischer y LeBlanc [199 1f. Yace en el capítulo 5.Notar y referencias 29 Dado el siguiente diagrama.9 Una alternativa al método de transportar un compilador descrito en la sección 1. una clase de código ensamblador para una miquina de pila "genérica" y un intérprete de código P que.

6 fueron introducidos por Bratman [1961]. algunas de las cuales pueden llegar a ser importantes técnicas generales en el futuro.2 se describe en Kernighan [197. donde también puede encontrarse una descripción del sistema P de Pascal (Nori [1981]).Una descripción de los primeros compiladores FORTRAN puede encontrarse en Backus [19. Los diagramas T de la sección 1. El preprocesador Ratfor mencionado en la sección 1. Este último contiene una descripción de la verificación de tipos de Hindley-Milner (mencionada en la seccidn t. la traducción de los lenguajes funcionales tales como ML y Haskell ha sido la fuente de muchas nuevas técnicas. . 1). Las descripciones de estas técnicas pueden encontrarse en Appel[1992]. Los compiladores de Pascal se describen en Barron [1981]. En particular. Es posible que se necesiten otras técnicas adicionales para la traducción eficaz de los lenguajes ajenos a la tradición principal de los lenguajes imperativos basados en Algol. Una descripción de un antiguo compilador Algo160 puede hallarse en Randell y Russell [1964]. Este texto se enfoca en las tkcnicas de traducción estándar útiles para la traducción de la mayoría de los lenguajes.57] y Backus 119811. Peyton Jones 119921 y Peyton Jones [19871.5].

los identificadores.Capítulo 2 Rastreo o análisis Iéxico 2.5 2.3 2. Los tokens son como las palabras de un lenguaje natural: cada token es una secuencia de caracteres que representa una unidad de infofmación en el programa fuente. y que comienzan con una letra. Dividiremos el estudio de las cuestiones del analizador Iéxico como sigue. compuestas por lo regular de letras y números. las cuales representan algoritmos que permiten reconocer los patrones de cadenas dados por las exprestones regulares.4 El proceso del análisis léxico Expresiones regulares Autómatas finitos Desde las expresiones regulares hasta los DFA 2. una notactón estándar para representar los patrones en cadenas que forman la estructura Iéxica de un lenguaje de programación. los símbolos especiales. Estos métodos son principalmente los de las expresiones regulares y los autómatas finitos. las cuales son cadenas fijas de letras. Como la tarea que realiza el analizador Iéxico es un caso especial de coincidencia de patrones. además de algunos símbolos compuestos de múltiples caracteres.1 2. Ejemplos típicos de token son las palabras reservadas. o ajusta desde el inicio de los caracteres de entrada restantes. Por lo tanto. el analizador Iéxico debe funcionar de manera tan eficiente como sea posible. también necesitamos poner mucha atención a los detalles prácticos de la estructura del analizador Iéxico.6 Implementación de un analizador léxico TlNY Uso de Lex para generar automáticamente un analizador Iéxico La fase de rastreo. En cada caso un token representa cierto patrón de caracteres que el analizador Iéxico reconoce. estudiaremos las expresiones regulares. un analizador Iéxico también es la parte del compilador que maneja la entrada del código fuente.2 2. que son cadenas definidas por el usuario. como los símbolos aritméticos + y *. necesitamos estudiar métodos de especificación y reconocimiento de patrones en la medida en que se aplican al proceso de análisis Iéxico. como if y w h i l e . de un compilador tiene la tarea de leer el programa fuente como un archivo de caracteres y dividirlo en tokens. Enseguida. daremos una perspectiva general de la función de un analizador Iéxico y de las estructuras y conceptos involucrados. Sin embargo. En primer lugar. También estudiaremos el proceso de construcción de los autómatas finitos fuera de las expresiones . o autómatas finitos. Continuaremos con el estudio de las máquinas de estados finitos. y puesto que esta entrada a menudo involucra un importante gasto de tiempo. o análisis Iéxico. tales como > = y <>.

una de ellas la constituyen las palabras reservadas. un token puede representar un número infinito de lexemas.ELSE. Por último. existen tokens que pueden representar cadenas de múltiples caracteres. el token de la palabra reservada I P se debe distinguir de la cadena de caracteres "if' que representa. como los símbolos aritméticos MÁs y MENOS.1 EL PROCESO DEL ANALISIS LÉXICO El trabajo del analizador Iéxico es leer los caracteres del código fuente y formarlos en unidades lógicas para que lo aborden las partes siguientes del compilador (generalmente el analizador sintáctico). como I F y THEN. . estudiaremos la manera en que se puede automatizar el proceso de producción de un programa analizador léxico por medio de un generador de analizador Iéxico. por ejemplo. Finalmente. m ocasiones .. están todos representados por el token siniple ID. i h t o s ntímcros comienzan en 256 p:ira evitar que sc cunfundan con los valores ASCll numéricos. Las unidades lógicas que genera el analizador Iéxico se denominan tokens. porque un compilador debe estar al tanto de ellos en una tabla de símbolos.THEN. Los tokens son entidades lógicas que por lo regular se definen corno un tipo enumerado.PLUS. No obstante. y repetiremos la implementación de un analizador léxico para TINY utilizando Lex.NüIú. los que se representan con los caracteres "+" y "-". TokenType. al estilo antiguo de C.) . Ejemplos de esto son NUM e ID. pero tienen muchos valores de cadena diferentes que representan sus nombres individuales. que representan las cadenas de caracteres "if' y "then".CAP. Por consiguiente. En un lenguaje sin tipos enumerados tendrfamos que definir los tokens directamente como valores numéricos sirnb6licos. que es un generador de analizador léxico estándar disponible para su uso en Unix y otros sistemas. un rastreador o analizador Iéxico también debe construir los valores de cadena de por lo menos algunos de los tokens.se vafa !o sigiirnte: #define IF 256 #de£ine THEN 257 #define ELSE 258 . Por ejemplo. Así. Algunos tokens tienen sólo un lexema: las palabras reservadas tienen esta propiedad. Una segunda a categoría es L de Los símbolos especiales. pueden definirse en C como' typedef enum (IF. Los tokens como entidades lógicas se deben distinguir claramente de las cadenas de caracteres que representan. Por ejemplo..1 Los tokens caen en diversas categorías.. 2 1 RASTREO O ANALISIS L~XICO regulares. 2. Para hacer clara la distinción. los cuales representan números e identificadores. Cualquier valor asociado a un token 1. En e5to se asemeja a la tarea del deletreo.. la cadena de caracteres representada por un token se denomina en ocasiones su valor de cadena o su lexema.MINUS. Estos nombres no se pueden pasar por alto. y formar caracteres en tokens es muy parecido a formar palabras a partir de caracteres cn una oración en un lenguaje natural como el inglés o cualquier otro y decidir lo que cada palabra significa.ID. Después volveremos a los métodos prácticos para escribir programas que implementen los procesos de reconocimiento representados por los autómatas finitos y estudiaremos una implementación completa de un analizador Iéxico para el lenguaje TINY. Los identificadores.

pocas veces el maliziidor hará todo esto de una vez. a menudo es útil recolectar todos los atributos en un solo tipo de datos estntcturados. En realidad. entonces se puede descartar su valor de cadena. o puede simplemente pasar el atributo a una fase posterior del compilador. Los to-kens también pueden tener otros atributos. el analizador Iéxico funcionará b?j« el control del analizador sintáctico. int numval. (lo que supone que el atributo de valor de cadena sólo es necesario para identificadores y el atributo de valor numérico sólo para números). pero no es necesario calcular de inmediato su valor numérico. devolviendo el siguiente token simple desde la entrada bajo demanda mediante una función que tendrá una tlecliirac i h similar a la declaración cn el lenguaje C TokenType getTokenívoid). Puesto que el analizador Iéxico posibleinente tendrá que calcular varios atributos para cada token. el sírnholo del token rnisnto se puede ver simplemente como otro atributo. En el caso de un token de símbolo especial conlo MÁs (PLUS). » posiblemente como una unión typedef struct { TokenType tokenva. mientras que el token se puede visiralizar corno la colección de todos sus atributos. typedef struct { TokenType tokenval. Por ejemplo. Por otro lado. Aunque la tarea del analizador Iéxico es convertir todo el programa fuente en una secuencia de tokens. int n m v a l . Un analizador léxico necesita calcular a1 menos tantos atributos de un token como sean necesarios para permitir el procesamiento siguiente. al que podríamos denominar como registro de token. Por ejemplo. que consta de cinco caracteres numéricos. ) attribute.El proceso del analirir lkxm 33 se denomina atributo del token. si se calcula su valor numérico.1. En realidad. se necesita calcular el valor de cadena de un token NUM. En ocasiones el mismo analizador Iéxico puede realizar las operaciones necesarias para registrar un atributo en el lugar apropiado. Un arreglo mis común es que el analizador Iéxico solamente devuelva el valor del token y coloque los otros atributos en variables donde se pueda tener acceso a ellos por otras partes del compilador. un token NUM puede tener un atributo de valor de cadena corno "32767". } TokenRecord. Por ejemplo. Un registro así se podría declarar en C como "+". pero también tendrá un atributo de valor numérico que consiste en el valor real de 32767 calculado a partir de su valor de cadena. un analizador Iéxico podría utilizar el valor de cadena de un identificador para introducirlo a la tabla de símbolos.no sólo se tiene el valor de cadena sino también la operacicín aritmética real -i-que estii asociada con él rnismo. 1 TokenRecord. union { char * stringval. o podría pasarlo para introducirlo en una etapa posterior. puesto que se puede calcular de su valor de cadena. . char * stringval. y el valor de cadena es un ejemplo de un atributo.

En ocasiones el conjunto será más general que el conjunto de caracteres ASCII. Este conjunto de símbolos legales se conoce como alfabeto y por lo general se representa mediante el símbolo griego 2 (sigma). el siguiente token desde la entrada. Volveremos ahora al estudio de métodos para definir y reconocer patrones en cadenas de caracteres. como el valor de cadena del token. pero esos caracteres tendrán un significado diferente: en una expresión regular todoo los símbolos indican putrottes. del conjunto de caracteres que se encuentra disponible. Este conjunto se denomina lenguaje generado por la expresión regular y se escribe como L(r). dejando el buffer de entrada como se aprecia a continuación: De esta manera.para definir "conjunto de cadenas" y no tiene (por lo menos en esta etapa) una relación específica con un lenguaje de programación. con el siguiente carácter de entrada indicado por la flecha: Una llamada a getToken necesitará ahora saltarse los siguientes cuatro espacios en blauco. cuando se le llame. En general. pero se conserva en un buffer (Localidad de memoria intermedia) o se proporciona mediante las facilidades de entrada del sistema. Una expresión regular r se encuentra completamente definida mediante el conjunto de cadenas con las que concuerda. Como ejemplo del funcionamiento de getToken considere la siguiente línea de código fuente en C. y devolver el valor de token I D como el token siguiente. y además calculará atributos adicionales. a es el carácter u usado corno patrón.2 EXPRESIONES REGULARES Las expresiones regulares representan patrones de cadenas de caracteres. La cadena de caracteres de entrada por lo regular no tiene un parámetro para esta función. una llamada posterior a getToken comen~ará nuevo el proceso de rede conocimiento con el carácter de corchete izquierdo. en primer lugar. en cuyo caso los elementos del conjunto se describirán como símbolos. estaremos hablando del conjunto de caracteres ASCII o de algún subconjunto del mismo. Aquí la palabra lenguaje se utiliza sólo. En este capítulo distinguiremos el uso de un carácter como patrón escribiendo todo los patrones en negritas. Una expresión regular r también contendrá caracteres del alfabeto. reconocer la cadena "a" compuesta del carácter único a como el token siguiente. Este lenguaje depende. 2 1 RASTREO O ANALISIS LÉXICO La función getToken declarada de esta manera devolverá.CAP. De este rnotlo. 2. que utilizamos como ejemplo en el capítulo 1: Suponga que esta línea de código se almacena en un buffer de entrada como qigue. .

S. cuyo lenguaje sea el conjunto vacío. la cadena que no contiene ningún carácter. es decir. Observe la diferencia entre ( ) y ( o } :el conjunto ( ) no contiene ninguna cadena. la cual se indica mediante el metacarácter *. Haremos esto en varias etapas. Advierta que los caracteres de escape. por lo que se debe utilizar una convención para diferenciar los dos usos posibles de un metararheter. Unos caracteres de escape comunes son la diagonal inversa y las comillas. como en el caso de 43 2. una expresión regular r puede contener caracteres que tengan significados especiales. Anaiizaremos cada una por turnol proporcionando la construcción del conjunto correspondiente para los lenguajes de cadenas concordantes. Después considerarenios extensiones a este conjunto mínimo. entonces r I s es una expresión regular que define cualquier cadena que concuerda con r o con . considere la expresión regular al b: ésta corresponde tanto al . + Operaciones de expresiones regulares Existen tres operaciones básicas en las expresiones regulares: 1) selección entre alternativas. Esto es similar a la manera en que se construyen las expresiones aritméticas: las expresiones aritméticas básicas son Los números. es decir. Selección entre alternativas Si r y s son gxpresiones regulares.También necesitaremos ocasionalmente ser capaces de describir un símbolo que corresponda a la ausencia de cadenas. ya que sólo contiene los metasímbolos y las operaciones esenciales.5 + 1. tales como 43 y 2. En términos de lenguajes. Comenzaremos por describir el conjunto de expresiones regulares básicas. los cuales se corresponden a sí mismos. y por lo general no pueden ser caracteres legales en el alfabeto.5 y 43 * 2.S) = L(r) u L(s).Existen otros dos símbolos que necesitaremos en situaciones especiales. Emplearemos para esto el símbolo y escribiremos L ( 4 ) = ( ). que se indica mediante yuxtaposición (sin un metacarácter). 2. las cuales se componen de símbolos individuales. el cual escribiremos como ( }.21 Definición de expresiones regulares Ahora estamos en posición de describir el significado de las expresiones regulares '11 esta. blecer cu.4.íies lenguajes genera cada patrón. cl lenguaje de rl . Dado cualquier carácter cr del alfabeto Z. Entonces las operaciones aritméticas.Expresiones regulares 35 Por último. Corno un cjemplo simple. Sin embargo.Y es la unión de los lenguajes de r y . El grupo de expresiones regulares que describiremos aquí es mínimo. En tnuchas situaciones esto se realiza mediante el uso de un carácter de escape que "desactiva" el significado especial de un metacarácter. Continuaremos con la descripción de las operaciones que generan nuevas expresiones regulares a partir de las ya existentes. la cual se indica mediante el metacarácter I (barra vertical): 2 ) concatenación. o L(rI . como la suma y la multiplicación.5.Y. si también son caracteres legales en el alfabeto. Este tipo de caracteres se llaman metacaracteres o metasímbolos. se pueden utilizar para formar nuevas expresiones a partir de las existentes. son por sí niismos metacaracteres. Necesitamos poder indicar una concordancia con la cadena vacía. a nienudo no es posible requerir tal exclusión. Expresiones regulares bdricar Éstas son precisamente los caracteres simples del alfabeto. indicamos que la expresión regular a corresponde al crirácter u escribiendo L(a) = {a]. mientras que el conjunto ( E 1 contiene la cadena simple que no se compone de ningún carácter. Utilizaremos el símbolo E (épsilon) para denotar la cadena vacía. porque no podríamos distinguir su uso como metacaracteres de su uso como miembros del alfabeto. y 3) repetición o "cerradura". y definiremos el metasímbolo E (e en negritas) estableciendo que L(E) = ( E J.

bb). bbb). L(a 1 b) = L(a) U L(b) = { a ) U ( b ) = {a. . r. por ejemplo.) = L(rl)L(r2) .S es la concatenación de S por n veces. se escribe r*. b ) . . Concatenación La concatenación de dos expresiones regulares r y s se escribe como rs. En otras palabras. Por ejemplo. L(r. E ) .carácter a como al carácter b. . Dado un conjunto S de cadenas.) Podemos definir la operación de repetición en términos de lenguajes generados definiendo. .. a* corresponde a las cadenas E. aabb. L(r. y corresponde a cualquier cadena que sea la concatenación de dos cadenas. cada una de las cuales corresponde a r. ta expresión regular ab corresponde sólo a la cadena lb. y S2. Dados dos conjuntos de cadenas S. b. (So = ( E ] . Corno segundo ejemplo. (Concuerda con E porque E es la concatenación de ninguna cadena concordante con a. . (El uso de los paréntesis como metacaracteres en esta expresión regular se explicará en breve. con la primera de ellas correspondiendo a r y la \egunda correspondiendo a s.b ) y S2 = {a. La concatenación también se puede extender a más de dos expresiones regulares: L(rl r. una operación similar * para conjuntos de cadenas. .) = el conjunto de cadenas formado al concatenar todas Las cadenas de cada una de las L(rl). que corresponde a cualquiera de las letras minhcunes con puntos. De esta manera (utilizando nuestro ejemplo anterior). es decir. donde r e s una expresión regular. La selección se puede extender a más de una alternativa.). pero cada uno de sus elementos es una concatenación finita de cadenas de S. . . si S. .) Ahora podemos definir la operación de repetición para expresiones regulares como sigue: . denominaúa también en ocasiones cerradura (de Kleene). de manera que. como en a I b I las de la a a la z. L(a I E ) = {a. a. b } { c ) = {ac. . Ahora la operación de concatenación para expresiones regulares se puede definir como sigue: L(rs) = L(r)L(s). En ocasiones el conjunto S* se escribe como sigue: donde S" = S. . Repetición La operación de repetición de una expresión reguIar. En ocasiones también escribiremos largas secuencias de seleccioI z. . sea S* = (E) u S u SS U SSS u Ésta es una unión de conjuntos infinita. . entonces S1S2= {aaa. a su vez. la expresión regular a l E corresponde tanto al carácter simple a como a la cadena vacía (que no está compuesta por ningún carácter). . Por ejemplo. La expresión regular r* corresponde a cualquier concatenación finita de cadenas. aaa.. Por ejemplo. bc}. L(a l b I c 1 d) = {a. L ( í a I b ) c ) = L(aI b)L(c) = (a. . . el conjunto concatenado de cadenas SlS2es el conjunto de cadenas de SI complementado con todas las cadenas de S2. aa. c d ) . mientras que la expresión regular í a I b) c corresponde a las cadenas ac y bc.) Podemos describir el efecto de la concatenación en términos de lenguajes generados al definir la concatenación de dos conjuntos de cadenas. ha. = {m.

En términos de lenguajes. . a i be* se interpreta como a I ( b ( c * ) . (al b b ) * se interpretaría sin los paréntesis como al bb*. cmhb. bhriu.19 es una definición regular del nombre digi to. hb. hh) * = ( e . ? (lb. . trbh. por ejemplo.). iicihh y así sucesivaniente. hbhh. hh. . hh. abbbh. . pero introduce la complicación agregada de que el nombre mismo se convierta en un metasímbolo y se deba encontrar un significado para distinguirlo de la concatenación de sus caracteres. hhn. pero 3 + 4 * 5 = 23. En realidad. b. mientras que L(a I (b*) ) = ( e . 11. b. Nombres para expresiones regulares A menudo es útil como una forma de simplificar la notación proporcionar un nombre para una expresión regular larga. 11. Cuando deseemos indicar una precedencia diferente. . u. uciu. puesto que L( ( aI b ) *) = (e. ha. lo que corresponde a a. cihbci. . ya que se supone que * tiene precedencia más alta que +. ya que de otro modo a I bc se interpretaría como si correspondiera tanto a a como a bc. .) Esta expresión regular corresponde a cualquiera de las cadenas siguientes: E . c~bb. por lo tanto. j . hbu. Advierta que no se debe emplear el nombre del término en su propia definición (es decir. Los paréntesis aquí se usan igual que en aritmética.Expresiones regulares 37 Considere como ejemplo la expresión regulaf ( aI b b ) *. Antes de considerar una serie de ejemplos para elaborar nuestra definición de expresiones regulares. a la e concatenación se le da la precedencia que sigue y a la I se le otorga la precedencia más baja. hh. Por ejemplo. En nuestro caso hicimos esa distinción al utilizar letra cursiva para el nombre. aa. la segunda interpretación es la correcta. . Precedencia de operaciones y el uso de los paréntesis La descripción precedente no toma en cuenta la cuestión de la precedencia de las operaciones de elección. . entonces escribisíamos o podríamos escribir dígi t o dígito* donde dígito = 011121. de modo que no tengamos que escribir la expresión misma cada vez que deseemos utilizarla. reuniremos todas las piezas de la definición de una expresión regular.) La convención estándar es que la repetición debería tener mayor precedencia. si deseáramos desarrollar una expresión regular para una secuencia de uno o más dígitos numéricos. . dada la expresión regular a 1 b*. uu. mientras que ab I c*d se interpreta como ( a b )I ( ( c * )d ) . . donde ( 3 + 4) * 5 = 35. debemos usar paréntesis para hacerlo. bhb. entre las tres operaciones. Ésta es la razón por la que tuvimos que escribir ( aI b )c para indicar que la operación de elección debería tener mayor precedencia que la concatenación. bb. Por ejemplo. (De nueva cuenta. de manera recursiva): debemos poder eliminar nombres reemplazándolos sucesivamente con las expresiones regulares para las que se establecieron. a. uu. se L da al * la precedencia tnhs alta. L((a1 b b ) *) = L(a I bb)"= ((1. h.). ma. .. El uso de una definición regular es muy conveniente.. ¿deberíamos interpretar esto como ( aI b ) * o como a I ( b * ) (Existe una diferencia importante. la razón (te tener paréntesis como metacaracteres se explicará más adelante. concatenación y repetición. . . hbbb. De manera similar. De este modo. bbh. .

Éstos son algo artificiales.tercero. ccbucci. En este caso. donde r y s son expresiones regulares. en el. L(r*) = L(r)*. También el conjunto de todas las cadenas en este alfabeto que contengan exactamente una h. los paréntesis no cambian el lenguaje.Definición Una expresión regular es una de las siguientes: l. En los ejemplos siguientes por lo regular se incluye una descripción en idionia coloquial de las cadenas que se harán corresponder. En la sección 2. . En ocasiones puede ser necesario invertir la dirección. 3. btrcincic. es decir. en la que un manual de lenguaje contiene descripciones de los tokens. donde r es una expresión regular.2. Esta situación. 2. aunque b aparece en el centro de la expresión regular. en esta definición. E.3 consideraremos algunas expresiones regulares comunes que aparecen con frecuencia como tokens en lenguajes de programación. cibnS m . y ésta tiene una precedencia más baja que el asterisco *. c j. 1. l>*. Este conjunto e5 generado por la expresión regular íalc)*b(alc)* Advierta que. o el metacarácter En el primer caso. Una expresión de la forma ( r ) . donde cr proviene de un alfabeto 2 de caracteres legales. Una expresión de la forma rs. Advertimos que. Una expresión de la forma r 1 S. Una expresión regular básica constituida por un solo carácter a . (. (3) y (4) está en el orden inverso de su enumeración. la letra h no necesita estar en el centro de la cadena que se desea definir. Una expresión de la forma r*. chc.1 Consideremos el alfabeto simple constituido por sólo tres caracteres alfabéticos: 2 = ( a . 4. En este caso. L ( ( r ) ) = L(r). En lo que resta de esta sección consideraremos una serie de ejemplos diseñados para explicar con más detalles la definición que acabamos de dar. de modo que también incluiremos algunos ejercicios de esta clase. es decir. donde r y s son expresiones regulares. h. L(+) = ( 1. donde r es una expresión regular. la repeticibn de n o c antes y después de la b puede presentarse en diferentes números de veces. L(rs) = L(r)L(s). es la que con más frecuencia enfrentan quienes escriben compiladores. la precedencia de las operaciones en (2). Por consiguiente. Ejemplo 2. También advertimos que esta definición proporciona un significado de metacaricter a los seis símbolos 4. En realidad. desplazarse de una expresión regular hacia una descripción en lenguaje coloquial. todas las cadenas siguientes estrín generadas mediante la expresión regular anterior: b. crcccch. +. L ( a ) = { a ) . y la tarea es traducir la descripción a una expresi6n regular. 5. sólo se utilizan para ajustar la precedencia de las operaciones. el metacaricter E. I tiene precedencia más baja que la concatenación. ya que por lo general no aparecen como descripciones de token en un lenguaje de programación. L(E)= ( E ) . En este caso.en el segundo. En este caso. L ( r l S) = Lfr) u L(s). De este modo.

Este conjunto no se puede describir mediante una expresión regular. entre cualcsquicra dos 17. requeriríamos utilizar un famoso teorema acerca de las expresiones regulares conocido como lema de 1a. c J que no coritierieii dos h co~isecutivas. para proporcionar una demostración niatenxítica de este hecho. czuban. Existen dos ruones para esto. La razón es que la única operación de repe~ición tenemos es la operación de cerradura *. .) = (a"hanln # 0) .Exprer~onerregulares 39 Con el mismo alhbeto que antes. De este niodo. 5 Ejemplo 2. no hay garantía de que el número de u antes y después de la h será el mismo.3 Consideremos el conjunto de cadenas S sobre el alfabeto 2: = {o. intentamos encontrar una expresión regular tan simple conio sea posible para describir un conjunto de cadenas.4 Considerenios las cadenas en el alfabeto 5 = { r i . Clonstniirenios .. De cunndo en cuando aparecen conjuntos no regulares como cadenas en lenguajes de programación que necesitan ser reconocidos por un analizador léxico. los cuales por 10 regular son abortados cuando surgen. que se estudia en la teoría de autómatas. De modo que si escribimos la expresión a*ba* (lo más cercano que podemos obtener en el caso de una expresión regular para S). donde por lo regular existe una solución estandar "más simple".: Este ejemplo plantea un punto importante acerca de las expresiones regulares: el mismo lenguaje se puede generar mediante muchas expresiones regulares diferentes. La primera es que raramente se presenta en situaciones prácticas. aunque nunca intentaremos demostrar que encontramos. c~c~ahuarz. Por consiguiente. De modo que. b J compuesto de una h siniple rodeada por el mismo número de u: S = ( h . La segunda es que cuando estudiemos métodos para reconocer expresiones regulares. h. un conjunto de cadenas que es el lenguaje para una expresión regular se distingue de otros conjuntos al denominarlo conjunto regular. de hecho. nhu. Retotnarem«s este tema de iilaiiera breve en la sección en que se abordan las consideraciones para el analizador léxico prjctico. la "más simple": la más breve por ejemplo. la cual permite cualque quier número de repeticiones. Por lo general. los algoritmos tendrán que poder simplificar el proceso de reconocimiento si11 molestme en simplificar primero la expresión regular.estracción (pumping Ienima). Una expresión regular para este conjunto se puede obtener utili~ando la soli~ción ejemplo :iriierior conio una alternativa (definiendo aquellas cadenas con exacal tarnente una h) y la expresión regular (aI c ) * corno la otra alternativa (riefinietitlo los casos sin h en todo). Es evidente que no todos los conjuntos de cadenas que podemos describir en términos simples se pueden generar mediante expresiones regulares. § jemplo 2. pero que aquí ya no volverenios a mencionar. Sin embargo. debe haber por lo tnenos una (i o una c. tenernos la soli1ci6nsiguiente: Una solución alternativa sería permitir que b o la cadena vacía apareciera entre las dos repeticiones de i r o <. considere el conjunto de todas las cadenas que contienen como rnáxitno una h. Expresanios esto al decir que "las expresiones regulares no pueden contar".

De hecho. que no haya dos b consecutivas (pero aún no es lo bastante correcta). Por lo tanto. ac. s contiene más de una de las cadenas no-E anteriormente enumeradas. y sea s una cadena en el lenguaje con longitud n > 2. be. esta solución es preferible en los casos en que el alfabeto es grande. de modo que s = sls2. advirtiendo que (r* I S * ) * corresponde con las mismas cadenas que (r I S)* (icuidado! Ésta todavía no es la respuesta correcta. En ocasiones podremos demostrar tales aseveraciones. como s2 no tienen dos b consecutivas.) El lenguaje generado por esta expresión regular tiene. ea. 2 1 RASTREO O AN~LISISLÉXICO una expresión regular para este conjunto en varias etapas. la propiedad que buscamos. que ninguna cadena generada mediante la expresión regular anterior puede finalizar con una b) también muestra por qué nuestra solución todavía no es bastante correcta: no genera las cadenas b. . Ahora supongamos que es verdadero para todas las cadenas en el lenguaje con una longitud de i < n. porque ninguna cadena en el lenguaje puede finalizar con una b. ub y cb. la cual define cadenas que no tienen b alguna. c. Pero esto es imposible. que no contienen dos b consecutivas. mediante la bipótesis de inducción. finalizara con una b y que comenzlira con una b. Por consiguiente.CAP. au. CC. bu. puesto que la definición de notb se puede ajustar para incluir todos los caracteres. podemos obligar a que una a o c se presenten después de cualquier b al escnbir ( b ( a l c )) * Podemos combinar esto con la expresión ( a 1 c ) *. excepto b. Es evidente que esto es verdadero para todas las cadenas de longitud O. sin corn9 plicar la expresión original.donde S . Arreglaremos esto agregando una b opcional rezagada de la manera siguiente: Advierta que la imagen especular de esta expresión regular también genera el lenguaje dado (ble)(alclablcb)* También podríamos generar este mismo lenguaje escribiendo (notblb notb)*(bIe) donde notb = a I c: Éste es un ejemplo del uso de un nombre para una subexpresión. la única manera de que s misma pudiera tener dos b consecutivas sería que s. 1 o 2: estas cadenas son precisamente las cadenas E . a saber. u. Entonces. en realidad. así que esbozaremos una demostración de que todas las cadenas en L( (al c Iba I bc) *) no contienen dos b consecutivas. y s2 también se encuentran en el lenguaje y no son E . el número de caracteres en la cadena). Este último hecho que utilizamos en el esbozo de la demostración (o sea. tanto S . En primer lugar. La demostración es por inducción sobre la longitud de la cadena (es decir. y escribimos ((alc)*l(b(alc))*)* o.

Para ver esto considere la expresión dentro de la repetición exterior kzquierda: Esto genera aquellas cadenas que finalizan en li y que contienen exactatnente dos a (cualquier núniero de h y de c puede aparecer antes o entre las dos u). se puede emplear una notación diferente. Además. e incluso aunque l o hicieran. Sin embargo. Por ejemplo. y no permite la cadena vacía c. ayudaría tener una expresión regular para una gama de caracteres y una expresión regular para todos los caracteres excepto uno. N o obstante. muchas de las situaciones que describiremos aparecerán de nuevo en nuestra descripción de Lex.h. podríamos escribir ( O I 1)*.. pero esto tnmhién . donde queremos una secuencia de dígitos.je de todas las cadenas que contengan un número par de a. La repetición de estas codenas nos da todas las cadenas que finalizan en u cuyo número de a es un múltiplo de 2 (es decir. y podríamos limitarnos a utilizar sólo las tres operaciones básicas (junto con paréntesis) en todos nuestros ejemplos. Esto permite que r se repita O o más veces. En la mayoría de estos casos no existe una terminología común. ya que se crean expresiones regulares que son más complicadas de lo que serían s i se dispusiera de un conjunto de operaciones más expresivo. E n realidad. par).Exprerioner regulares 41 En este ejemplo proporcionamos la expresión regular y solicitamos deterniinar una descripción concisa en español del lenguaje que genera. c ) y la expresión regular Esto genera el 1engua. de modo que utilizaremos una notación similar a la empleada por el generador de analizadores léxicos Lex. UNA O MAS REPETICIONES Dada una expresión regular p. si tlescamos definir números binarios. Ahora pasaremos a nuestra lista de nuevas operaciones. ya vimos en los ejemplos anteriores que escribir expresiones regulares utilizando svlo estos operadores. que se escribe r*. A l añadir la repetición (bI c) * al final (como en el ejemplo anterior) obtenemos el resultado deseado. con los correspondientes metasíinbolos nuevos. l o que garantiza que aparece por lo tnenos una cadena correspondiente a r. Advertimos que esta expresión regular también se podría escribir como (nota* a nota* a)* nota* 222 Extensiones para las expresiones regulares Formulamos una definición para las expresiones regulares que emplean un conjunto mínimo de operaciones comunes a todas las aplicaciones.en ocasiones es poco manejable. En los párrafos siguientes describiremos algunas extensiones a las expresiones regulares estándar ya analizadas. Considere el alfabeto C = { a . el cual se describe niás adelante en este capítulo. Un ejemplo cs el de un número natural. pero deseamos que por lo menos aparezca uno. sería útil tener una notación para una coincidencia de cualquier carácter (ahora tenemos que enumerar todos los caracteres en el alfabeto como una larga alternativa). Por ejemplo. Una situación típica que surge es la necesidad de trrrci o más repeticiones en lugar de ninguna. la repetición de r se describe utilizando la operación de cerradura estándar. que cubren éstas y situaciones comunes semejantes. no todas las aplicaciones que utilizan expresiones regubares incluirán estas operaciones.

Advierta que esta notación puede depender del orden subyacente del conjunto de caracteres. C. un carácter estándar que representa la negación en lógica es la "tilde" -. nuestra expresión regular anterior para números biiiarios puede escribirse ahora como CUALQUIER CARACTER Una situación común es la necesidad de generar cualquier carácter en el alfabeto. Esta notación general se conoce como clases de caracteres.. UN INTERVALO DE CARACTERES A menudo necesitamos escribir un intervalo de caracteres.juntocon las clases tlc caracteres que acabarnos tic dcscrihir p x a la li~rrnaci6ii (le . al escribir [A-z]se supone que los caracteres R. caracteres por generar. 2 1 PgiSTREO O ANALISIS LEXICO coincidirá con la cadena vacía.CAP.. . ni b. de manera que [a-zA-Z]representa todas las letras minúsculas y mayúsculas. Hasta ahora hemos hecho esto utilizando la notación a I b I I z para las letras minúsculas o O I 1 I 1 9 para los dígitos. Sin embargo. ]a cual no es un número. y podríamos escribir una expresión regular para un carácter en el alfabeto que no sea a como -a y un carácter que no sea a. .. Por supuesto. y los demás vienen entre los caracteres A y Z (una suposición razonable) y que sólo 109 caracteres en mayúsculas ertán entre A y Z (algo que e i verdadero para el conjunto de caracteres ASCII). De este modo. como en [a-z1 para las letras minúsculas y [ 0 . a menudo es de utilidad poder excluir un carácter simple del conjunto de. Sin una operación especial esto requiere que todo carácter en el alfabeto sea enumerado en una alternativa. como el de todas las letras minúsculas o el de todos los dígitos.9 1 para los dígitos. Con este metacarácter pode~nos escribir una expresión regular para todas las cadenas que contengan al menos una h como se muestra a continuación: ". que indica una o más repeticiones de r. Esto también se puede emplear para alternativas individuales. Por ejemplo. como Uila alternativa para esta noracih se emplea en Lex. podríamos escribir pero esta situación se presenta con tanta frecuencia que se desarrolló para ella una notación relativamente estándar en la que se utiliza + en lugar de *: rc. Esto se puede conseguir al diseñar un metacarictcr para indicar la operación de negación ("not") o complementaria sobre un conjunto de aIternativas. También se pueden incluir los intervalos múltiples. Un metacarácter típico que se utiliza para expresar una concordancia el cual no requiere que el alfabeto se escriba de cualquier carácter es el punto realmente en forma extendida. CUALQUIER CARÁCTER QUE NO ESTÉ EN UN CONJUNTO DADO Como hemos visto. bir [A-z] no se definirán los mismos caracteres que para [A-Za-z] incluso en el caso del conjunto de caracteres ASCII. ni c.. Una alternativa es tener una notación especial para esta situación. y una que es común es ta de emplear corchetes y un guión.". de modo que a I b I c puede escribirse como [abcl. al eicri.Por ejemplo. donde el carácter "cxat" A se utilim en co11.

literales de cadena como "hola. Éstos pueden ser un carácter simple.Expresiones regulares 43 complementos. tal como =. cualquier carácter que no sea u se escribe corno ["a]. Podemos emplear alternativas para expresar esto como en las definiciones regulares natural = [O-91+ naturalconSigno = natural I + natural l . o números decimales. De este modo.) ? nat número = natconSigno(" " nat)? ( E natconsigno)? . un suceso que se presenta comúnmente es el de cadenas que contienen pzirtes opcionales que pueden o no aparecer en ctialquier cadena en particular.14159. Una tercera categoría se compone de los identificadores. Aquí describiremos algunas de estas expresiones regulares típicas y analizaremos algunas otras cuestiones relacionadas con el reconocimiento de tokens. Por ejeniplo. Una categoría final se compone de literales o constantes. que incluyen operadores aritméticos.71E-2 representa el número .027 1. de asignación y de igualdad. el ejemplo del signo inicial se convierie en natural = [O-91+ naturalconsigno = ( + 1-1 ? natural 22. en ocasiones también conocidas como palabras clave. que son cadenas fijas de caracteres alfabéticos que tienen un significado especial en el lenguaje. un número puede o no tener un signo inicial. . tales como := o ++. Por ejemplo. ni h ni c se escribe como SUBEXPRESIONES OPCIONALES Por último. Los ejemplos incluyen i f . Otra categoría se compone de los símbolos especiales. 2. o números con un exponente (indicado mediante una "e" o " E ) . mientras que cualquier carácter que no sea a. w h i l e y do en lenguajes como Pascal. que incluyen constantes numéricas tales como 42 y 3. Números Los números pueden ser súlo secuencias de dígitos (números naturales). Más detalles acerca de las cuestiones de reconocimiento práctico aparecen posteriormente en el capítulo.natural Esto se puede convertir rápi&amente en algo voluminoso. o múltiples caracteres o compuestos. que por lo común se definen como secuencias de letras y dígitos que comienzan con una letra. mundo" y caracteres tales como "a" y "b.3 Expresiones regulares para tokens de lenguajes de programación Los tokens de lenguajes de programación tienden a caer dentro de varias categorías limitadas que son basta~ite estandarizadas a través de muchos lenguajes de programaciún diferentes. Por ejemplo. Una categoría es la de las palabras reservadas. C y Ada. e iiitrodiiciremos e1 n i e ~ c a rácter de signo de interrogación r? para indicar que las cademas que coincidan con r son opcionales (o que están presentes O o I copias de r). tal como + o -. Po(lemos escribir definiciones regttlares para esos números como se ve a contin~raciún: nat = [O-91+ natconSigno = í + I.

) De manera aimilar. En ocasiones pueden contener directivas de compilador. Podemos expresar esto en términos de definiciones regulares como sigue: letra = [a-?. Si quisiéramos recolectar todas las palabras reservadas en una definición. la cual analizaremos más adelante en este capítulo.. Los comentarios pueden tener varias formas diferentes. aun cuando un analizador Iéxico pueda no tener un token constante explícito (podríamos llamar a estos pseudotokens). tal como el comentario de Pascai. (Una expresión diferente debe escribirse para Lex. Identificadores y palabras reservadas Las palabras reservadas son las más simples de escribir como expresiones regulares: están representadas por sus secuencias fijas de caracteres. con una letra y contener sólo letras y dígitos. 2 1 RASTREO O ANALISIS LÉXICO Aquí escribimos el punto decimal entre comillas para enfatizar que debería ser generado directamente y no interpretado como un metacarácter.. escribiríamos algo como reservada = if I while I do 1 . Un identificador debe comenzar. para indicar "not }" y donde partimos del supuesto de que el carácter } no tiene significado como un metacarácter.' No obstante. por otra parte. un comentario en Ada se puede hacer coincidir mediante la expresión regular - -. por lo común. No es difícil escribir una expresión regular para comentarios que tenga delimitadores de carácter simple. Los identificadores. . el caso di1 comentario de Pascal puede escribirse como i(-))*l donde escribimos 1. como en éste es un comentario de Scheme -. Por consiguiente. necesitaremos escribir expresiones regulares para comentarios. son cadenas de caracteres que no son fijas.CAP.1-nuevalínea)* 2. un analizador léxico debe reconocer los comentarios y descartarlos. Suelen ser de formato libre o estar rodeados de delimitadores tales como {bate es un comentario de Pasc'al) / * éste es un comentario de C */ o comenzar con un carácter o caracteres especificados y continuar hasta el final de la línea.éste es un comentario de Ada .A-ZI dígito = [O-91 identificad01 = letra(1etraldígito)* o Comentarios Los comentarios por L regular se ignoran durante el proceso del análisis Iéxico. Por ejemplo. o para aquellos que partan de algún(os) earáetedes) especificado(s) hasta el final de la línea.

1) que las expresiones regulares no pueden expresar operaciones de conteo. peco esto es no trivial. En realidad. Podemos intentar escribir una definición para íab) utilizando -a. de manera que lo que sigue no es un comentario legal de Modula-2: (* esto es ( * ilegal en Modula-2 *)) La anidación de los comentarios requiere que el analizador léxico cuente el número de los delimitadores. una expresión regular para los comentaios de C es tan complicada que casi nunca se escribe en la práctica. espacios en blanco y búsqueda hacia delante A menudo en la descripc~ón los tokens de de lenguajes de programación utilizando expresiones regulares. . .2. En la practica utilizamos un esquema de contador simple como una solución adecuada para este problema (véanse los ejercicios). La segunda establece que. la cadena <> se podría interpretar como la representación de dos tokens ("menor que" y "mayor que") o como un token simple ("no es igual a"). Para ver esto considere el conjunto de cadenas bu. De este modo.algunas cadenas se pueden definir mediante varias expresiones regulares diferentes. (Veremos c6mo escribir esto en [. (ausencia de apariciones de uh). .cih (utilizamos ha. se prefiere por lo general la inte~pretación corno palabra clave. en algunos lenguajes de programación. No podemos escribir simplemente porque el operador "not" por lo regular está restringido a caracteres simples en lugar de cadenas de caracteres. Ambigüedad. tal como los comentarios de C. Por ejemplo. ya que el asterisco' y cn ocasiones la diagonal.Exprer~oner regulares 45 en la cual suponeinos que nuevalínea corresponde al final de una línea (lo que se escrihe como \n en muchos sistemas). cuando una cadena puede ser un identificador o una palabra clave. De hecho.ex en la sección 2.6. . Una solución es - - pero es difícil de leer (y de demostrar correctamente). y. lo que veremos rnás adelante en este capítulo. los comentarios pueden estar anidados.que el final de la línea no está incluido en el comentario mismo.) Es mucho más difícil cscribir una expresión reguiar para cl Caso de los deliniitadores que tienen más de un carácter de longitud.ub en lugar de los delimitadores de C l*.-b y (aI b) . cuando una . el uso del térniino palabra reservada. . Una definición de lenguaje de programación debe establecer cuál interpretación se observará. que el carácter "-" no tiene significado como un metacarácter. Esto se da a entender medianti. una definición de lenguaje debe proporcionar reglas de no ambigüedad que iniplicarán cuál significado es el conveniente para cada uno de tales casos. Modula-2 permite comentarios de la forma (* esto es (* u n comentario de *) en Modula-2 * ) Los delimitadores de comentario deben estar exactamente pareados en dichos comentarios anidados. cadenas tales como if y w h i l e podrían ser identificadores o palabras clave. otra complicación en el reconocimiento de los comentarios es que. Por último. De manera semejante. Pero advertimos en el ejemplo 2. Dos reglas típicas que manejan los ejemplos que se acaban de dar son las siguientes. La primera establece que. Por ejemplo."l. este caso se suele manejar mediante métodos ex profeso en los analizadores léxicos reales. y las expresiones regulares por sí mismas no pueden hacer esto. lo que quiere decir que es simplemente una palahra clave que no puede ser también un identificador. . . es un inetacarácter que requiere de un manejo especial). .3 (sección 2.

los retornos de línea y los caracteres de tabulación se intergeneralmente ianibién se asumen como delimitadores de token: while x preta entonces como compuesto de dos tokens que representan la palabra reservada while y el identificador de nombre x. que sólo sirve al analizador léxico de manera interna para distinguir otros tokens. Un analizador Iéxico para un lenguaje de formato libre debe descartar el espacio en blanco después de verificar cualquier efecto de delimitación del token. Advierta también que es posible que no se necesite la búsqueda hacia delante para reconocer un token.) 3. además de actuar como un delimitador de token. Por ejemplo. Por ejemplo.. similar al pseudotoken de comentaio. En ese caso. en la cadena xtemg=ytemg. y el analizador Iéxico debe estar preparado para respaldar posiblemente de nianera arbitraria muchos caracteres. el signo de igualdad puede ser el único tokeu que comienza con el carácter =.. ya que representa el siguiente token a reconocer. o caracteres que implican que una cadena más larga en el punto donde aparecen no puede representar un token. el alinacenamiento en memoria intermedia de los carocteres de entrada y la marca de lugares para un retroseguimiento se convierten en el diseño de un analizador Iéxico. Advierta que. Por ejemplo. En realidad.el signo de igualdad delimita el identificador xtemg. un analizador Iéxico se debe ocupar del problema de L búsqueda haa cia delante: cuando encuentra un delimitador debe arreglar que éste no se elimine del resto de la entrada. Esta preferencia se conoce a menudo como el principio de la subcadena más larga: la cadena más Larga de caracteres que podrían constituir un token simple en cualquier punto se supone que representa el siguiente token: 3 Una cuestión que surge con el uso del principio de la subcadena más larga es la cuestión de los delimitadores de token. representa las dos palabras reservadas do e if más que el identificador doif. en cuyo caso se puede reconocer de inmediato sin consultar el carácter siguiente. el fragmento de código en lenguaje C . los comentarios mismos por lo regular sirven corno delimitadores. Los caracteres que son parte no anibigua de otros tokens son delimitadores. Los lenguajes que especifican este comportamiento se denominan de formato libre. y el signo = debe permanecer en la entrada. ya sea devolviéndolo i la cadena de entrada ("respaldándolo") o mirando hacia delante antes de eliminar el carácter de la entrada.porque = no puede aparecer como parte de un identificador. por ejemplo. por lo común se prefiere la interpretación del token sin@. Los delimitadores terminan las cadenas que forman el token pero no son pase del token mismo. en el la cadena xtemg=ytemg. tal como la regla de fuera de lugar (véase la sección de notas y referencias). (Algunas de estas preguntas se aborcian tnás adelante en este capítulo. Los espacios en blanco. De este modo. el espacio en blanco por lo regular no se tonla en cuenta. En ocüsiones esto se denomina principio del "miximo bocado" . de manera que. En esta situación a menudo es útil definir un pseudotoken de espacio en blanco. Una definición típica del pseudotoken de espacio en blanco en un lengucije de programación es donde los identificadores a la derecha representan los caracteres o cadenas apropiados. final del identificador xtemg se encuentra cuando se halla el =. En ocasiones un lenguaje puede requerir más que la búsqueda hacia delante de carácter simple.CAP. En la mayoría de los casos sólo se necesita hacer esto para un carácter simple ("búsqueda hacia delante de carácter simple").puesto que un espacio en blanco separa las dos cadenas de caracteres. Las altemrttivas al formato libre incluyen el formato fijo de unos cuantos lenguajes como FORTRAN y diversos usos de la sangría en el texto. 2 1 RASTREO O AN~LISIS LÉxlca cadena puede ser un token simple o una secuencia de varios tokens.

.t clave hasta qtie alcance el signo de coma (o el punto). O ) THE N de i-nniiern que cl espacio en hlanco ya ii0 I'unciona como un deliniit. Tuti1pocu hay p. . Por consiguiente. Por supuesto. y de este modo se pueden utilizar para construir analizadores léxicos.. embargo..tlabra\ itstc\aclas cn FORTRAN. ontcs (le conieiimr nuestro estudio de los autóniatas fiiiiios de niimI-21 apropiada. ser idciitificadures. la siguiente línea de cfidigo cs perfeck~~iiente correcta en FORTRAN: Los primeros IF y THEN son palabras clave. 2.Áutómatar finitos 47 FORTRAN cs im buen ejerirplo de un lengutje que viola inuchos de los principios que :icabr~mosde anitliiar. Este es rlri lenguaje de kmnato Wo en el cual el espacio en blanco se elimina por nicdio de u11 prcprocesodor antes que comience la traducción. Por ejemplo. o tnáquinas de estados finitos. al canihiar I. EQ. en cuyo caso puede ser obligado a retroceder hacia el principio de la Iínea y comenzar de nuevo.tdm. tatiihién existe una fuerte relación entre los autóniatas finitos y las expresiolies regulares. De este modo. I o la variable con O nombre D099I. y veremos en I.i cotila por un pu~ito cambia el significado del código por completo: esto asigna el valor l . El patrón para itlentificaclores conio se define co~níininentcen 10s lenguajes de prograniación es15 dado por la sipicntc det'inicifin regular (si~porttlrernt~s l e t r a y dígito que ya sc definieron): i d e n t i f i c a d o r = l e t r a ( l e t r a 1d í g i t o ) * .. son una rriniiera rnateinrítica para deslos crihir clases particuliires de algoritmos ( o "niáquinas"). autóriiatas finitos se pueden utilizar para describir cl proceso de reconocimiento de patrones en cadenas de entr:ida. mietitr¿is que los segundos I F y THEN son identificadores que representan variables. En partic~ilar. de modo que todas las palabras clave títmbi5n piteden .i sección siguiente citrno constr~~ir autfimata finito 3 partir de una expresión un Sin regt~lar. con el mismo efecto que el Pascal for i := 1 t o 1 0 . Considerenios. Por otra pa1-te. la línea en E'ORTRAN 1 F ( X 2 . u n analimdor léxico no puede concluir que cl D inicial es un. en coticreto. El efecto de esto es que un rinalizüdor léxico de FORTRAN debe poder retroceder a posicio~iesxhirrarias dentro de utia Iínea de código. el siguiente bien conocido ejenipl«: C I ~ ~ es Esto inicia un ciclo que C ~ I I I ~ I ~ el Icódigo subsiguiente hasta la Iínea cuyo nún~ero 99.3 AUTOMATAS FINITOS Los autóruatas finitos.. .t p:tl:ihr. y la posición de la cadena de caracteres e11 cada línea de entrada es irnportarite parü determinar el tokcn que ser5 reconticiOo. . corisitlerarenios u n -jcmplo explicativo.

cualquier secuencia de letras y dígitos subsiguiente (incluyendo la ausencia de todas) representa un identificador legal. cualquier número de letras y10 dígitos se puede ver. lo cual indica que. y una coincidencia de éstos nos regresa al estado 2. Puede haber más de uno de éstos. 2 2 . Por ejemplo. y se indican dibujando un borde con Iínea doble alrededor del estado en el diagrama. . El proceso de reconocimiento de una cadena de caracteres real como un identificador ahora se puede indicar al enumerar la secuencia de estados y transiciones en el diagrama que se utiliza en el proceso de reconocimiento. El proceso de reconocer una cadena así se puede describir mediante el diagrama de la Figura 2. no necesitaremos una visidn tan abstracta como ésta.Una vez en el estado 2. Por convención. En el diagrama de muestra. Figura 2. 2. en ocasiones es necesario tener una descripción más formal de un autómata finito. Otras descripciones de autómatas finitos también son posibles. particularmente las tablas. Las describiremos a medida que surja la necesidad de utilizarlas.Esto representa una cadena que comienza con una letra y continúa con cualquier secuencia de letras y10 dígitos. y por ello procederemos ahora a proporcionar una definición matemática. que son localidades en el proceso de reconocimiento que registran cuánto del patrón ya se ha visto. y describiremos la mayoría de los ejemplos en términos del diagrama solo.1 Definición de los autómatas finitos deterministicos Los diagramas como el que analizamos son descripciones útiles de los autómatas finitos porque nos permiten visualizar fácilmente las acciones del algoritmo. se denominan estados de aceptación.3. Sin embargo. el proceso de reconocer xtemg como un identificador se puede indicar como sigue: . o el estado en el que comienza el proceso de reconocimiento. 2 A 2 2 En este diagrama etiquetamos cada transición mediante la ietra que se iguala en cada paso. el estado de inicio se indica dibujando una línea con flechas sin etiqueta que proviene de "de ninguna parte". Las líneas con flechas representan transiciones que registran un cambio de un estado a otro en una coincidencia del carácter o caracteres mediante los cuales son etiquetados.+ l X 2 2 t. El estado 2 representa el punto en el cial se ha igualado una sola letra (lo que se indica mediante la transición del estado l al estado 2 etiquetada con l e t r a ) . En el diagrama de muestra el estado 2 es un estado de aceptación. después que cede una letra. y éstas serán útiles para convertir los algoritmos en código de trabajo.1. en los cuales podemos declarar un éxito.1 Un autbmata finito para identificadores - letra letra d í g i to En este diagrama los círculos numerados 1 y 2 representan estados. el estado 1 es el estado de inicio. Los estados que representan el fin del proceso de reconocimiento. La mayor paae del tiempo. no obstante.

.

la definición de otro proveniente desde el estado de inicio es y la definición de otro proveniente d e d e el estado entrada-id es otro = -(letraIdígito) 4. el diagrama para un identificador tendría el aspecto que se ilustra en la figura 2. . Una tercera diferencia fundamental entre la definición y nuestro diagrama es que la definición representa las transiciones como una función T: S X C -+ S. Pero en el diagrama tenemos Tiinicio. C ) debe tener un valor para cada s y c.Por convención. 0 que no hemos encontrado un delimirador que finalice el reconocimiento de un identificador (si estamos en iiri estado de aceptación).2. Figura 2. ya que seria abruniador dibujar 52 trmsiciones por separado. c) está definida sólo si c es una letra o un dígito. y etiquetamos tas transiciones de error como otro. Esto significa que T(s. y T(entrada-id. Más ndelante en esta scccidn veranos c6mo manejar estos casos. Si las dibujáramos. Por consiguiente.2 Un automata fintto para idenoficadares con tranrlciones de errar letra dígi to error cualquiera En esta figura etiquetamos el nuevo estado error (porque representa una ocurrencia errónea).Por ejemplo. ¿Dónde están las transiciones perdidas? La respuesta es que representan errores: es dccir. el nombre letra representa cualquier letra del alfabeto de acuerdo con la siguiente definición regular: l e t r a = [a-zA-Zl Ésta es una extensión conveniente de la definición. c) definida sólo si c es una letra. en el reconocimiento de un identificador no podemos aceptar cualquier otro carácter aparte de letras del estado de inicio y letras o números después de éste. una para cada letra minúscula y una para cada letra mayúsctila. estos caracteres no alianuméricos significan que no hay un identificador cn todo (si cstarnos en el estado de inicio). En realidad. otro representa cualquier carácter que no aparezca en ninguna otra transición desde el estado donde se origina.%a convención es que estas transiciones de error no se dibujan en el diagrama sino que simplemente se supone que siempre existen. Continuaremos empleando esta extensión de la definición en el resto del capítulo.

.

los comentarios encerrados entre llaves son aceptados por el siguiente DFA: . y entonces escribir el siguiente DFA: También es fácil agregar L parte fraccional opcional como se aprecia a continuación: a dígito dígito dígito Observe que conservamos anibos estados de aceptación para reflejar el hecho de que la parte fracciona1 es opcional. Para hacer esto observamos que la parte exponencial debe comenzar con la letra E y puede presentarse sólo después de que hayamos alcanzado cualquiera de los estados de aceptación anteriores. Figura 2. necesitamos agregar la parte exponencial opcional.J Un autómata finito para números de punto flotante dígi to dígito Ejemplo 2. El diagrama final se ofrece en la figura 2.3. podemos observar que un naturalconsigno comienza con un dígito o con un signo y un dígito.9 Los comentarios no anidados se pueden describir utilizando DFA.Es sencillo escribir un DFA para nat como sigue (recuerde que a+ = a a * para cualquier a): Un naturalconsigno es un poco más difícil debido al signo opcional. Sin embargo. Por último. Por ejemplo.

excepto el "asterisco" :* y la "diagonal" f. porque en algún momento tendremos que convertirlos en el código para un analizador léxico.4. - .Autómatas finitos 53 En este caso.' ~ L I Iescribir un DFA que acepte tales comentarios que escribir una expresión regular para ellos.En realidad es inás f . lnvestigaremos esta relación en la siguiente sección. salida-comentario (4) y final (5). entrada-comentario (3). Este DFA corresponde a la expresión regular (: ( } ) * 1 . Pero primero necesitamos estudiar más detalladamente . 5 2. y que aquellas transiciones que dan errores como resultado por lo regular se dejan fuera del diagrama para el DFA.3. Pero incluso la definición matemática no describe todos los aspectos del comportamiento de un algoritmo de DFA. sino que s6lo proporciona un esbozo de su funcionamiento. excepto el de la llave derecha. pero podríamos haberles dado nombres más significativos. En realidad. Observamos en esa sección que era difícil escribir una expresión regul:ir prni comentarios que estuvieran delimitados mediante una secuencia de dos caracteres. Un DFA para esos comentarios en C se proporciona en la figura 2. Por ejemplo. excepto el asterisco *. que son de la forma / * . Numeramos los estados en este diagrama por simplicidad. Nuestro ejemplo original de u n token de identificador muestra gran parte del comportaiiiizuto que clesel~mos describir aquí. preparar-comentario (2). existe una fuerte relación entre una expresión regular para un patrón y un DFA que acepta cadenas de acuerdo con el patrón. como los siguientes (con los números correspondientes entre paréntesis): inicio (1). el cual escribimos previamente en la sección 2. junto con cualquier atributo asociado. El DFA .2. Figura 2. . o incluso cuando iguale un carácter durante una transición. Una acción típica cuando se alcanza un estado de aceptación es devolver el tokcn que se acaba de recotiocer. Ya advertimos que el diagrama de un DFA no representa todo 10 que riecesita un DFA.1. vimos que la definición matemática implica que un DFA debe tener una transición para cada estado y carácter.los algoritmos precisos que representan los DFA. Una acción típic:t'cuand« se alcanza un estado de error es retroceder hacia la entrada (rctrnseguimiento) o generar un token de error. o t r o significa todos los caracteres.2 Búsqueda hacia delante. Como tal vez el lector ya haya adivinado. Tanipoco especifica la acción que tomará un programa al alcanzar un estado de aceptación.. no especifica lo que ocurre cuando se presenta un error. mientras que la transición o t r o del estado 4 al estado 3 se permite para todos los caracteres. retroseguimiento y autómatas no deterministicos Estudiamos los DFA como una manera de representar algoritmos que aceptan cadenas de caracteres de acuerdo con un patrón.4.(no * / S ) . Una acción típica que ocurre cuando se hace una transición es mover el carácter de la cadena de entrada a una cadena que acumula los caracteres hasta convertirse en un token simple (el valor de cadena del token o Iexema del token). como ocurre con los comentarios en C.. * l . y así regresaremos al diagrama de la figura 1.1 Un autómata finito para comentarios tipo C otro En esta tigura la transición o t r o desde el estado 3 hacia sí mismo se permite para todos los caracteres.

En contraste. es decir. Supongalnos por el momento (lo que de hecho es el comportamiento correcto) que existen otras transiciones representando las transiciones sin letra desde el estado de inicio. que debería ser devuelto a la cadena de entrada y no consumido.5: Un autómata finito para un identifitador ton valor de retorno y delimitador ID de retorno En el diagrama encerramos la transición o t r o entre corchetes para i~idicar el carácque ter delimitados se debería considerar como búsqueda hacia delante. Cada uno de éstos es una cadena fija. y cada token será reconocido por su propio DFA. Si cada uno de estos tokens comienza con un carieter diferente. o bien. En primer lugar. el estado de error se ha convertido en el estado de aceptación en este diagrama y no hay transiciones fuera del estado de aceptación. entonces es ficil conjuntarlos uniendo simplemente todos sus estados de inicio en un solo estado de inicio. pero representa el hecho de que un identificador no va a ser reconocido (si veninlos desde el estado de inicio). y que debería generarse un token identificador niediante el diagrama de la figura 2. el estado de error no es en realidad un error en absoluto. Este nuevo diagrama también expresa cl principio de la subcadena m& larga descrito en la sección 2. puesto que el analizador léxico debería reconocer un token a la vez y corneniar de nuevo en su estado de inicio después de reconocer cada token. <= e =. que se ha detectado un tleliinitador y ahora deberímos aceptar y generar un token de identificador. 2 1 RASTREO O AN~LISIS L~XICO de esa figura no muestra el comportalniento que queremos de un analizador léxico por varias razones. algo que desde luego no queremos que ocurra. y los DFA para ellos se pueden escribir conlo sigue: - A S S I G N ('asignación" ) de retorno Corno cada uno de estos tokrns comienza con u n carácter diferente. potlerrios d o idcntificitr SUS rstittl~s iiticiti para ol>tcnerel siguiente DFA: (le . Entonces podemos indicar que se ha detectado un delimitador desde el estado entrada-id.CAP. Por ejemplo.2. considere los tokens dados por las cadenas :=. Ahora volvamos nuestra atención a la cuestión de cómo llegar al estado de inicio en primer lugar. En un lenguaje de programación típico existen muchos tokens. Además.4: el DFA continúa igualando letras y dígitos (en estado entrada-id) hasta que se encuentra un delimitador. Esto es lo que querernos. el diagrama antiguo permitiría aceptar al DFA en cualquier punto mientras se lee una cadena de identificador.

Ahora no podemos limitarnos a escribir el diagrama si= guiente. especialmente si se hace tlc una nianera no sistcmiiicii. tales como <. i y <>. tal como en el diagrama siguiente: LE ("menor o igual que") de retorno - NE ("no igual quew) de retorno 0 ("menor queu) de retorna En principio. deberíamos poder coinbiriar totlos los tokens en un DFA gigante de esta manera. puesto que no es un DFA (dado un estado y un carácter.Autómatar finitos ASSIGN ( "asignaci6n") de retorno LE ("menor O igual que") de retorno EQ ("igualdad") de retorno Sin embargo. la complejidad tic una tarea así se vuelve enorme. supongamos que teníamos varios tokens que cotnenzaban con el mismo carácter. No obstante. . siempre debe haber una trrinsicih única h ~ c i a nuevo estado único): un LE ("menor o igual que") de retorno NE ("no igual que") de retorno LT ("menor que") de retorno En vez de eso debemos arreglarlo de manera que sólo quede una transición por hacerse en cada estado.

<= e = se puede expresar al combinar los autómatas para cada token como se muestra a continuación: Esto tiene la ventaja de que mantiene intacto al autómata original y sólo agrega un nuevo estado de inicio para conectarlos. Se puede ver como una "igualación" de la cadena vacía. sin búsqueda hacia delante y sin modificación de la cadena de entrada. porque pueden ocurrir "espontáneamente". La segunda ventaja de las transiciones E es que pueden describir explícitamente una coincidencia de la cadena vacía: . Antes de definirlo necesitamos otra generalización que será útil en la aplicación de los autómatas finitos a los analizadores Léxicos: el concepto de la transición E. En un diagrama una transición E se describe como si e fuera en realidad un carácter: Esto no debe confundirse con una correspondencia del carácter E en la entrada: si el alfabeto incluye este carácter. mientras que al mismo tiempo se desanolla un algoritmo para convertir sistemáticamente estos nuevos autómatas finitos generalizados en DFA. 2 1 RASTREO O ANALISIS LÉXICO Una solución a este problema es ampliar la definición de un autómata finito para incluir el caso en el que pueda existir más de una transición pma un carácter pa~ticular. Por ejemplo. pero son Útiles de dos maneras.CAP. es decir. la cual antes describimos como E. mientras que pospondremos la descripción del algoritmo de traducción hasta la siguiente sección. Aquí describiremos estos autómatas generalizados. Las transiciones E Son hasta cierto punto contraintuitivas. la selección de los tokens :=. La nueva clase de autómatas finitos se denomina NFA por las siglas del tkrmino autómata finito no determinístico en inglés. Una es porque permiten expresar una selección de alternativas de una manera que no involucre la combinación de estados. se debe distinguir del carácter e que se usa como metacarácter para representar una transición E . Una transición E es una transición que puede ocumr sin consultar la cadena de entrada (y sin consumir ningún carácter).

.s.Autómatas finitos 57 Iii Por supuesto.S. 3 ) . En otras palabras. pero en este caso. el cual tiene una definición muy siniilar a la de un DFA. cori la E eliniinada (porque la coircatcnacih <le. y T se convierte en una función que mapea pares estado/símbolo hacia cortjunros de estarlos.co. donde antes utilizainos 2 (esto supone que E no es originalmente un miembro de 2).c. c.sl. .corno un elemento de A.. y Iü cadena que en realidad es aceptada es la cadena t. el rango de Tes el conjunto de potencia del conjunto S de estados (el conjunto de todos los subconjuntos de S).s2 en . Definición Un NFA (por las siglas del término autómata finito no determinístico en inglés) M consta de un alfabeto 2.... de i u ( e ] tal que existen estados s i en T(. . <) = (2. De nuevo necesitamos advertir . se define como el conjunto de cadenas de caracteres cIc> . el cud expresa que rriría sin hacer coincidir ningún carácter: accptaci6n ocu- Pero es útil tener la notaci6n explícita anterior.. . .También necesitainos ampliar la definición de T (la función de transición) de modo que cada caricter pueda conducir a más de un estacto.).S con e es s rilis~n:~). El lenguaje aceptado por M.. I . Haremos esto escribiendo 2: u { E ) (la r i n i h de 2 y E). .curi S. escribimos esto como p(S) (p manuscrita de S). Por consiguiente. c I ) . así como de un estado de inicio sode S y un conjunto de estados de aceptación A de S. . . Ahora estableceremos la definición. en c. esto es equivalente al siguiente DFA.cl. la catlena Por . dado el diagrama teneinos T( 1..ilgunas cosas sobre esta definicih. .. lo tanto. Ahora definirenios qué es un autómata no determinístico. escrito como L(IM).. necesitanios ainpliar el alfabeto X para incluir a E. según el . un conjunto de estados S y una Funcicín de transición T: S X ( 2 u ( E ] )-+ pis).. con cada c.en T(s.). . desde el estado 1 podemos moverlos a cualesquiera de los estados 2 o 3 en el carácter de entrada <. puede ser E .c. Cualquiera de las c. Por ejemplo. .inilisis anterior.c. Hacemos esto permitiendo que el valor de T sea un conjurrro de estados en lugar de un estado simple.c. T(.

El siguiente DFA también acepta este lenguaje: . . . siguiendo la transición E desde el estado 1 hasta el estado 4 se permite la aceptación de todas las cadenas coincidentes con b*.c. Además. siempre estará determinada de manera única. . la secuencia de estados $ 1 . . De este modo. T ( S . Ejemplo 2.Una expresión regular más \imple que genera el mismo lenguaje es (a1 & ) b*. Ésta. y del estado 2 al estado 1en b. La cadena abb puede ser aceptada por cualquiera de las siguientes secuencias de transiciones: En realidad las transiciones del estado 1 al e~tado en a... una NFA no representa un algoritmo. per2 miten que la máquina acepte la cadena nb. No obstante.. Finalmente. se puede simular mediante un algoritmo que haga un retroseguimiento a través de cada elección no determinística. En realidad. . las transiciones del estado 1 al estado 3 en a. se elige de los conjuntos de estados T(s. y esta elección no . y entonces.. De manera similar. todas las cadenas igualan la expresión regular ab+.S. 2 1 RASTREO O AN~LISISLÉXICO clc.. Sin embargo. . permiten la aceptación de todas las cadenas que coinciden con ab*.c. puede en realidad contener menos de n caracteres.). . .CAP..). utilizando la transición E del estado 4 al estado 2. de hecho. De este modo. como veremos más adelante en esta sección. pueden introducirse números arbitrarios de E en cualquier punto de la cadena.c.1 O Considere el siguiente diagrama de un NFA. . . y del estado 3 al estado 4 en E. este NFA acepta el mismo lenguaje que la expresión regular ab+ 1 ab* 1 b*. consideremos primero un par de ejemplos de NFA. es la razítn por la que los autómatas se denominan no determinísjicos: la secuencia de transiciones que acepta una cadena particular no está determinada en cada paso por el estado y el siguiente carácter de entrada. . ~ . los cuales corresponden a cualquier número de transiciones E en la NFA.

3 lmplementación de autómatas finitos en código Existen diversas inaneras de traducir un DFA o un NFA en código.a primera y más sencilla rilaneln tlc simu1. no es difícil ver que este NFA acepta el mismo lenguaje que el generado por la expresih regular (al c)b*. y en las últiinrts dos secciones de este capítulo se demostrarán los aspectos de la codificación apropiada para analizactores léxicos con niiís deidle. en su fortna de corregida que incluye búsqueda hacia delante y el principio de la subcadena rnás larga (véase la figura 2. Considere de nueva cuenta nuestro ejemplo original de un DFA que acepta identificadores co~npuestos una letra seguida por una secuencia de letras y/« dígitos. no todos estos métodos seriín útiles para un analizador Iixico de coinpilador.% .5): 1.4 & 7 & 2 .. 9 2.ir este ¡>FA es cscrihic el cddigo de la niitncra higuiente: . y las analizaremos en esta secciítn.5 & 6 -1 De hecho.Autómatas h t o s Considere el 5iguiente NFA: Este acepta la cadena cictlh al efectuar las transiciones siguientes: a E.2 & 3 . Sin embargo.3.

avanza en la entrada. end while. end if. IJn método de implementación sustancialmente mejor se obtiene al utilizar una variable para mantener el estado actual y escribir las transiciones como una sentencia case doblcrnen¡e anidada dentro de una iteración. Como un ejemplo simple de estos problemas. ( estado 2 ) if el siguiente carácter es "*" then adelantar la entrczda. donde la primera sentencia case prueba el estado actual y el segundo nivel anidado prueba el ceráctr de entrada. La primera es que es de propósito específico.6. Por ejcmplo. cada DFA se tiene que tratar de manera un poco diferente. end while. el cual se podría implementar por medio de código de la forma siguiente: ( estado 1 ) if el siguiente curácter es "/'then uvunzun en la entraáu. más específicamente. como indicamos mediante los comentarios. consideremos el DFA del ejemplo 2. aceptar. el DFA anterior p a identiticadores se puede traducir en el esquema de c6rligo dc la figura 2. Esto es razonable si no hay demasiados estados (lo que requeriría muchos niveles de anidación).4 (página 53) que acepta comentarios en C. if el siguiente carácter de entrarla es "7"then hecho := true. ( estado 5 ] else ( otro procesamiento ] end if. Pero existen dos desventajas con este método. [ estado 4 ) while el siguienre carácter de entratiu es "*" do uvarzza en la entra&. else { error u otros casos 1 end if. y es difícil establecer un algoritmo que traduzca cada DFA a código de esta manera. La segunda es que la complejidad del código aumenta de manera dramática a medida que el número de estados se eleva o. Un código como éste se ha utilizado para escribir pequeños analizadores léxicos.avcinza en la entrada. Advierta el considerable incremento en complejidad. while not hecho do while el siguiente carúcter de entrado no es ""do uvonscr en la entrada. es decir. else { otro procesamiento ) end if. { ir al estado 3 sin avanzar en la entrada ) uceptar. . Tales códigos utilizan la posición en el código (anidado dentro de pruebas) para mantener el estado implícitamente. avanza en la entrada. ( estado 3 ) hecho := false. ( permanece en el estado 2 ) end while. lo que da el estado.9 como se dio en la figura 2. end while. a medida que se incrementa el número de estados diferentes a lo largo de trayectorias arbitrarias. y la necesidad de lidiar con 13 i t m ción que involucra los estados 3 y 4 mediante el uso de la variable booleana hecho. y si las iteraciones en el DFA son pequeños.

. while estado = 1 o 2 do case estado of 1: case carácter de entrfuía of letra: avmza en la entrada. Ahora el DFA para los con~cntarios C (figura 2. En los ejemplos que acabamos de ver.7. el DFA para ~dentiftcadores puede representar como la siguiente tabla de se tran\ic~úri: \\ estado z%tEda ' \ letra dígito otro I 2 3 7 ? 2 3 . end whiie. estado := 2. Observe cómo este código refleja el DFA de inmera directa: las transiciones c«rrespon<lena la asignación de un nuevo estado a la variable estado y avanzan en la entrada (excepto en el caso de la transición "que no consume" del e s t í o 2 al estado 3). if estado = 3 then aceptar else error . end case. Una estructura de datos simple qne es adecuada para este propósito es una tabla de transición. ( error u owo . indizado por estado y carácter de entrada que expresa los valores de la Función de transiciún T: / Estados Caracteres en el alfabeto c Ewdos representando transiciones T(s.4) se puede tr~tlucir el esqrienia en en de cúdigo mris legible de la figura 2. 2: case carácter de entrada of letra. el DFA se "conectó" directamente dentro del código. También es posible expresar el DFA como una estructura de datos y entonces escribir un código "genérico" que tomará sus acciones de la estructura de datos. end case. else estado := . Una alternativa para esta organizaciítn cs tener e l case exterior basado en el carácter de entrada y los case internos basados en el estado actual (véanse los ejercicios). dbgito: avanza en la enir&&. c) 1 Como ejemplo. estado := 2. ( realmente innecesario } efse estado := 3.Autómatas finitos Figura 2 6 ~~~lementación del ldentfficadorDFA utilizando una variable de estado y pebs tase anidadas estado :== ( inicio } 1. end case. o arreglo bidimensional. 1.

62
F i q ~ r d2.1

CAP. 2 1 RASTREO O

ANALISIS

LEXICO

Implementación del DFA de la figura 2.4

En esta tabla las entradas en blanco representan transiciones que no se muestran en el diagrama DFA (es decir, representan transiciones a estados de error u otros procesamientos). También suponemos que el primer estado que se muestra es el estado de inicio. Sin en?bargo, esta tabla no indica cuáles estados aceptados y cuiies transiciones no consumen sus entradas. Esta información puede conservarse en la misma estructura de datos que representa la tabla o en una estructura de datos por separado. Si agregamos esta información a la tabla de transición anterior (utilizando una columna por separado para indicar estados de aceptaci6n y corchetes para señalar transiciones "no consumidoras de entrada"), obtenemos la tabla siguiente:
letra
I
digito

otro

Aceptación

2

110

Autómatas ffnitor

63

Como un segurttlo t+xiipl« de una tabla de transición. presentamos I:i tabla IiarLi el : los comeiiiarios en C (miestro segundo ejcriiplo presentado con i anterioridad):

DFA corresporidicnie
carácter

1

:#

otro

Aceptación

I

7

110

2
3
3

3
4

110 -

3 3

110

4

5-

1

$10

-

5

Ahora podemos escribir del código en una Sorvtia que iinplementüri cualquier DFA, dadas las entradas y cstriictiiras de datos apropiadas. El siguiente esquema de c6digo supone que las tr;insiciones se niantieneri en iin arreglo de traiisici6n 7 indimdo por esrüdos ' y carácter de entrada; que las transiciones que avanzan en la entriida (es decir, aquellas que no estin marcadas con corchetes en la tabla! están dadas por el arreglo hn(ilean« Aivin:clr. indizado también por estados y caracteres de entrada; y que los estados (le aceptación estin tlndos por el arreglo booleano Acepttrr, indizado por estados. El siguiente es el esquema de ccídigv: esttrclo := I ; 1.h := .si,q~~if~r~/e clc. rrrlrcrd(l: ctrr~ícrer while not Acc.l,fcrr[estad~ not rrror(c~,stirtio) and do iiurivestcitlo : = T[c~.sf~ldo,th]; if i\iv~rz:,cir[c~rt~r~l~~,cl~I : = .si,quierr/eccr,iictrr de rrrtrcirkr: then 01 rstcrdo := I I I ( C I L ' ~ ~ ~ S / ~ I ~ ~ O ;

end while;
if Ac~~[~ttrr[r.srtrt/o iicc~pttrr: then
Los métodos algorítmicos coino los que acabamos de describir se denominan controlados por tabla porq~ieemplean tablas para dirigir el progreso del algoritnio. Los niétoitos coritrolatlos por tabla tiene11ciertas ventajas: el titrnaño del código se reduce, el mismo ccidigo fiiiicioiiará paln inuchos problemas diferentes y el ccidigo es mis ficil de niodificar (mantener). La desventaja es que las tablas pueden volverse muy grandes y provocar un irnportante aumento en el espacio utilizado por el programa. En realiclod. gran parte del espacio en los arreglos que acabUtnos de describir se desperciicia. Por lo tanto, los ~nétodos controlados por tabla a menudo dependen de tnétodos de coinpresión de tablas. tales coirio representaciones de arreglos dispersos, aunque por lo regular existe una penalización en tiempo que se debe p q n r por dicha compresión, ya que la húsqucda eii tiihlas se vuelve mis lenta. Como los analizadores léxicos deben ser eficientes. rara vez se utilizan estos rnétoclcts para ellos, aunque se ptiedeii emplear en programas generadores de ;tnalizadores léxicos t:iles como Lex, N0 los cstiitliaremos mlís aquí. IOY Por último, advertirno.; qi~e NF/\ se pueden iniplcnicntíir de tr1aner;is siniilares a los I X A . excepto que co~rio Nt'A son no determinísticos, tienen muchas sccueiicias dit'ercnlos [es de tmisiciones etl potetici:~que ic deben probar. Por coiisiguientc, un progran1:i que h ~ i l l u n NI'A tlehe nlni;icunnr tr:insicionc\ tpe t~(liiví:i se hm probado y ~.cdilar res tto cl ti-iwguimiento :i ellas i.11 c:iw ilc l i l l n . Esto e\ I I W ~ si~nilar los algctritnios que irrteiitiiri e11a LY1nti-:ir t~-~lycc~ori~ls en griI'iG15 dil-igid:i>,s d o y e 121 cL1~lcna c~1t~-:id:l lil l>lí.;i~lic,lLl. de g11í:1

Como los algoritmos que realizan una gran cantidad de retroseguimientos tienden a ser poco eficientes, y un analizador léxico debe ser tan eficiente como sea posible, ya no describiremos tales algoritmos. En su lugar, se puede resolver el problema de simular un NFA por medio del método que estudiaremos en la siguiente sección, el cual convierte un NFA en un DFA. Así que pasaremos a esta sección, donde regresaremos brevemente al problema de simular un NFA.

2.4 DESDE LAS EXPRESIONES REGULARES HASTA LOS DFA
En esta sección estudiaremos un algoritmo paca traducir una expresión regular en un DFA. También existe un algoritmo para traducir un DFA en una expresión regular, de manera que las dos nociones son equivalentes. Sin embargo, debido a lo compacto de las expresiones regulares, se suele preferir a los DFA como descripciones de token, y de este modo la generación del analizador Iéxico comienza comúnmente con expresiones regulares y se sigue a través de la construcción de un DFA hasta un programa de analizador léxico final. Por esta razón, nuestro interés se enfocará sólo en un algoritmo que realice esta dirección de la equivalencia. El algoritmo más simple para traducir una expresión regular en un DFA pasa por una construcción intermedia, en la cual se deriva un NFA de la expresión regular, y posteriormente se emplea para construir un DFA equivalente. Existen algoritmos que pueden traducir tina expresión regular de manera directa en un DFA, pero son más complejos y la construcción intermedia también es de cierto interés. Así que nos concentraremos en la descripción de dos algoritmos, uno que traduce una expresión regular en un NFA y el segundo que traduce un NFA en un DFA. Combinado con uno de los algoritmos para traducir un DFA en un programa descrito en la sección anterior, el proceso de la construcción de un analizador léxico se puede automatizar en tres pasos, como se ilustra mediante la figura que se ve a continuación:

2.41 Desde una expresión regular hasta un NFA
La construcción que describiremos re conoce como la construcción de Thompson, en honor a su inveittor. Utili~a transiciones E para "pegar" las máquinas de cada segmento de una expresión regular con el fin de formar una máquina que corresponde a la expresión completa. De este modo, la construcción es inductiva, y sigue la estructura de la definición de una expresión regular: mostramos un NFA para cada expresión regular básica y posteriormente moitramos cómo se puede conseguir cada operación de expresión regular al conectar entre sí los NFA de las subexpresiones (suponiendo que éstas ya se han construido).

Expresiones regulares básicas Una expresión regular básica es de la forma a, E o donde a representa una correspondencia con un carácter simple del alfabeto, E representa una coincidencia con la cadena vacía y I$ representa la correspondencia con ninguna cadena. Un NFA que es equivalente a la expresión regular a (es decir, que acepta precisamente aquellas cadenas en su lenguaje) es

+,

Derde lar exprerlones regulares hasta los DFA

De manera wnil,ii, un N F A que

e5

equivalente a E e\

E l caso de la expresiún regular ,ja como ejercicio.

+ (que nunca ocurre en la prcictica en

1111coinpilador)

se de-

Concatenación Deseamos construir un N F A equivalente a la expresicín regular m, doride r y s soii expresiones regulares. Suponernos (de manera inductiva) que los NFA equivalentes : I r y s ya se construyeron. Expresamos esto al escribir

para el N F A correspondiente a r. y de nianera similar para .s. En este dibujo, el círculo a lii iLquierda dentro del rectángulo redoiidendo indica el estado tic inicio, cl círculo doble ; la deI recha indica el estado de aceptaci6n y los tres pnnlos indican los estados y trnnsicioiies (Icntro del N F A que no se mnestrao. Esta figura supone que el NFA correspondiente a r. tiene súlo Lin estado de aceptacicín. Esta stiposici6n se justificará s i todo NFA que constrrtyanios tiene rcn estado de aceptación. Esto es verd;t(lero para los N F A de expresiones regulares bcisicas, y será cierto para cada una de las construcciones siguientes. Ahora podemos construir un NFA correspondiente a rs de la manera siguiente:

Conectamos el estado de aceptacicín de la rnáquiiia de r a1 estado de inicio de 1 máquina de 3 La nueva máquina tiene el estatío de inicio de la lniquintl tle r como hti estado de inicio y el estado de ;iceptaci<in de la m k p i n a de s c r m o su estado tle . y aceptaciún. Evidentemente, esta máquina acepta Lics) = L(r)L(.s) de cste rnotlo corresponi de ; la expresiún regular cs.
s mediante una tra~isiciún 6:.

Seleccih entre akernafivas I)exmios coristi-uir LII N F h ct>rrespondici,te a I- I .S b-jo la\ mi\m;is I w[xisicio~ies que :iillcs. I!;icc~nos esto como he bc ; contiriii:ici6ir:

C 2 1 RASTREO O k

ANALISIS

LÉXICO

Agregamos un nuevo estado de inicio y un nuevo estado de aceptación y los conectamos como se muestra utilizando transiciones E. Evidentemente, esta máquina acepta el lenguaje L(r I S ) = L(r) u L(s).

Repetición Queremos construir una máquina que corre~pondaa r*, dada una máquina que corresponda a r. Haremos eito como se muestra enseguida:

Aquí nuevamente agregamos dos nuevos estados, un estado de inicio y un estado de aceptación. La repetición en esta máquina la proporciona L nueva transición E del estado de a aceptación de la máquina de r a su estado de inicio. Esto permite a la máquina de r ser atravesada una o más veces. Para asegurar que la cadena vacía también es aceptada (correspondiente a las cero repeticiones de r), también debemos trazar una transición E desde el nuevo estado de inicio hasta el nuevo estado de aceptación. Esto completa la descripción de la construcción de Thompson. Advertimos que esta construcción no es única. En particular, otras constnicciones son posibles cuando se traducen operaciones de expresión regular en NFA. Por ejemplo. al expresar la concatenación rs, podríamos haber eliminado la transición E entre las máquinas de r y s e identificar en su lugar el estado de aceptación de la ináq~iifia r con el estado de inicio de la máquina de S , como de se ilustra a continuación:

Desde lar expresiones regulares harta los DFA

67

(Sin embargo, esta simplificación depende del hecho que en las otras construcciones el estado de aceptación no tiene transiciones desde ella misma hacia otros estados: véanse los ejercicios.) Otras simplificaciones son posibles en los otros casos. La razón por la que expresamos las traducciones a medida que las tenemos es que las máquinas se construyen según reglas muy simples. En primer lugar, cada estado tiene con10 máximo dos transiciones, y si hay dos. ambas deben ser transiciones F . En segundo lugar, ningún estado se elimina una vez que se construye, y ninguna hansicicín se modifica. excepto para la adición de transiciones desde el estado de aceptación. Estas propiedades hacen muy Picil automatizar el proceso. Concluimos el málisis de IU construcción de Thornpson con algunos ejemplos.

Ejemplo 2.12

Traduciremos la expresión regular ab l a en un NFA de acuerdo con la construcción de Thompson. Primero formamos las máquinas para las expresiones regulares básicas a y b:

Entonces formamos la máquina para la concatenación ab:

Ahora formamos otra copia de la máquina para a y utili7arnos la constmcción para selección con el fin de obtener el NFA completo para ab I a que se muestra en la figura 2.8.

Figura 2.8
NFA para la expresión regular ab l a utilizando la construcción de Thompron

Ejemplo 2.13

Formamos el NFA de la construcción de Thompson para la expresión regular l e t r a ( l e t r a I dígi to)*. Conlo en el ejemplo anterior, formamos las máquinas para las expresiones regulares l e t r a y dígi to:

CAP. 2 1 RASTREO O AN~LISISL~XICO
Después formamos la máquina p r la selección 1 t r a I d í g i to: aa e

Ahora formamos la NFA para la repetición ( l e t r a 1d í g i t o ) * como se ve a continuación:

Por último construimos la máquina para la concatenación de l e t r a con ( l e t r a I d í g i t o ) * para obtener el NFA completo, como se ilustra en la figura 2.9.

Figura 2.9
NIA para la expres~ón regular letra(letraldígito)*

E

utilizando la conrtrucc16n de Thompron

letra

Desde lar expresiones regulares hasta los DfA

69

.Como ejemplo final, observanios que el NFA del ejemplo 2.1 I (scccicin 2.3.2) corresponde exactaniente a la expresión regular ( a 1 c ) *b hajo la construcciih de Thornpson.

2.4.2 Desde un NFA hasta un DFA
Ahora deseamos dcscrihir un algoritnio que, dado un NFA arbitrario, coristniirá un DFA equivalente (es decir, uno que acepte precisamente las mismas cadenas). Pitra hacerlo necesitaremos algún método con el que se eliminen tanto las transiciones i conlo las transiciones inúltiples de un estado en un carácter de entrada simple. La eliminación de las transiciones E implica el construir cerraduras E, las cuales son el conjunto (le todos los estados qite pueden alc~inzar transiciones E desde un estado o estados. La eliminación de transiciones múltilas ples en un carácter de entra& simple implica mantenerse al tanto del conjunto de estados que son alcatizables al igualar un carácter simple. Ambos procesos nos conducen a considerar conjuntos de estados en lugar de estados simples. De este modo, no es ninguna sorpresa que el DFA que construimos tenga como sus estados los conjitraros de estados del NFA original. En consecuencia, este algoritmo se denomina construcción de subconjuntos. Primero analizaremos la cerradura e con un poco más de detalle y posteriormente continuaremos con una descripción de la constmcción de subconjuntos.

La cerradura E de un conjunto de estados Definimos la cerradura i un estado simple .r de como el conjunto de estados alcanzables por una serie de cero o más tmnsicinnes e, y escribirnos este conjunto como F. Dejürernos una afirmación más matenxítica de esta definición para un ejercicio y continuaremos directamente con un ejemplo. Siu embargo, advierta que la cerradura E de un estado siempre contiene al estado mismo.

Ejemplo 2.14

Considere el NFA siguiente que corresponde a la expresih regular a* bajo la construcción de Thornpson:

Ahora definiremos la cerradura e de un conjunto de estados corno la urti6n de las cerraduras i cada cstado individual. En símbolos, s i S cs un conjunto de estados. entonces tede nernos que

CAP. 2 i RASTREO O AN~LISIS ~ÉXICO

Por ejemplo, en el NFA del ejemplo 2.14, ( 1 , 2,3, 4).

Tí;S) = 7u 3 =

( 1 , 2, 4 ) u {2, 3, 4 ) =

algoritmo para la La construccian del subconjunto Ahora estamos en posiciún de describir el . construcción de un DFA a partir de un NFA M dado, al que denominarenlos M. Primero calcul m s la cerradura S del estado de inicio de M. esto se convierte en el estado de inicio de M. Para este conjunto, y para cada conjunto subsiguiente, calculamos las transiciones en los caracteres ca como sigue. Dado un conjunto S de estados y un carácter u en el alfabeto, calcularnos el conjunto S: = [ t / para alguna s en S existe una transición de s a r en u ). Después calcutamos S:, la cerradura S de S:. Esto define un nuevo estado en 1.a construcción del subconjunto, junto con una nueva transición S A S:. Continuamos con este proceso hasta que no se crean nuevos estados o transiciones. Marcamos conio de aceptación aquellos estados construidos de esta manera que contengan un estado de aceptación de M. Éste es el DFA No contiene transiciones E debido a que todo estado es construido como una cerradura E. Contiene como máximo una transición desde un estado en un carácter a porque cada nuevo estado se construye de todos los estados de M alcanzables por transiciones desde un estado en un carjcter simple a. Ilustraremos la construcción del subconjunto con varios ejemplos.

Ejemplo 2.15

Considere el NFA del ejemplo 2.14. El estado de inicio del DFA correspondiente es i = ( 1 , 2, 4 ) . Existe una transición desde el estado 2 hasta el estado 3 en a, y no hay transiciones desde los estados 1 o 4 en a, de manera que hay una transición en a desde ( 1, 2, 4 ) hasta (1, 2, 4 ), = ( 3 ) = (2, 3, 4 ) . Como no hay hansiciones adicionales en un carácter desde cualquiera de los esiados 1, 2 o 4, volveremos nuestra atención hacia el nuevo estado (2, 3, 4 ) . De nueva cuenta existe una transición desde 2 a 3 en a y ninguna transición a desde 3 o 4, de modo que hay una transición desde (2, 3, 4) hasta { 2 , 3 , 4 ) = (3) = (2, 3, 4 ) . , De manera que existe una transición a desde (2, 3, 4) hacia sí misma. Agotamos los estados a considerar, y así construimos el DFA completo. Sólo resta advertir que el estado 4 del NFA es de aceptación, y puesto que tanto ( 1 , 2, 4 ) como 12, 3, 4 ) contienen al 4, ambos son estados de aceptación del DFA correspondiente. Dibujamos ei DFA que construimos como sigue, donde nombramos a los estados mediante sus subconjuntos:

(Una vez que se termina la construcción, podríamos descartar la terminología de subconjuntos si así lo deseamos.) 9

3

Ejemplo 2.16

Considereinos cl NFA de la figura 2.8. al cual agregaremos números de estado:

Desde las expresiones regulares hasta los DFA

La constmcci6n del subconjunto de DFA tiene como su estado de inicio () = ( 1, 2. 6 ) . ExisI te una transición en (1 desde el estado 2 hasta el estado 3, y también desde el estado 6 hasta el estado 7. De este modo, ( 1 , 2 , 6 1, = ( 3 , 7 ) = ( 3 , 4, 7 , U), y tenemos ( 1, 2, 6)&(3, 4, 7,U). Como no hay otras transiciones de carácter desde -o 6, vayamos a (3,4,7,8 ). Exisl,2 te una transición en h desde 4 hasta 5 y ( 3 , 4 , 7 , U),, = ( 5 ) = ( 5 , 81, y tenemos la transición ( 3 , 4 , 7, 8)&(5, 8 ) . No hay otras transiciones. Así que la construcción del subconjunto produce el siguiente DFA equivalente al anterior NFA:

Ejemplo 2.17

Considere el NFA de la figura 2.9 (la conqtrucción de Thompson para la expresión regular letraíletraldígito)*):

La construcción del subconjunto continúa como se explica a continuación. El estado de inicio I e es () ( I ). Existe una transición en l. t r a para (2) = (2, 3,4,5,7, LO). Desde este estado existe una transición en l e t r a para ( 6 ) = (4, 5, 6, 7, O, 10) y una transición en d í g i t o para ( 8 ) = ( 4 , 5, 7 , 8, 9, 10). Finalmente, cada uno de estos estados también tiene transiciones en l e t r a y d í g i t o , ya sea hacia sí mismos o hacia el otro. El DFA completo se ilustra en la siguiente figura:

-

CAP. 2 1 RASTREO O

ANALISIS L~XICO

letra

2.43 Simulación de un NFA utilizando
la construcción de subconjuntos
En la sección anterior analizamos brevemente la posibilidad de escribir un programa para simular un NFA, una cuestión que requiere tratar con la no determinación, o naturaleza no aigorítmica, de la máquina. Una manera de simular un NFA es utilizar la constnicción de subconjuntos, pero en lugar de construir todos los estados del DFA asociado, construimos solamente el estado en cada punto que indica el siguiente carácter de entrada. De este modo, construimos sólo aquellos conjuntos de estados que se presentan en realidad en una trayectoria a través del DFA que se toma en la cadena de entrada proporcionada. La ventaja de esto es que podemos no necesitar construir el DFA completo. La desventaja es que, si la trayectoria contiene bucles, un estado se puede construir muchas veces. Pongamos por caso el ejemplo 2.16, donde si tenemos la cadena de entrada compuesta del carácter simple u, construiremos el estado de inicio { 1, 2, 6 ) y luego el segundo estado { 3 , 4 , 7 , 8 ) hacia el cual nos movemos e igualamos la a. Entonces, como no hay b a continuación, aceptamos sin generar incluso el estado ( S , 8). Por otra parte, en el ejemplo 2.17, dada la cadena de entrada r2d2, tenemos la siguiente secuencia de estados y transiciones:

Si estos estados se construyen a medida que se presentan las transiciones, entonces todos los estados del DFA están construidos y el estado (4,5,7, 8,9, 10) incluso se construyó dos veces. Así que este proceso es menos eficiente que construir el DFA completo en primer lugar. Es por esto que no se realiza la simulación de los NFA en analizadores léxicos. Qneda una opción para igualar patrones en programas de búsqueda y editores, donde el uwario puede dar las expresiones regulares de manera dinámica.

2.44 Minimización del número de estados
en un DFA
El proceso que describimos para derivar un DFA de manera algorítmica a partir de una expresión regular tiene la desafortunada propiedad de que el DFA resultante puede ser ni5s complejo de lo necesario. Como un caso ilustrativo, en el ejemplo 2.15 derivamos el DFA

si es posible. y que este DFA de estados o ~iiíriinios iiiíiiirno es único (excepto en el caso de renoiubrx estados).itlo de iio aceptaciih S iiciie una trtinsici6it e1 hacia un esiaclo iIe . s i cualquier conjunto adicional se divide. una trmiición de error). t. errtotices esto define una transicibri ri desdiel nuevo estado de aceptación (el con. debernos regresar y repetir el proceso desde el principio. Si todos I«s estados de aceptacitíti tienen traiisiciones cn a para estados de aceptación. Decimos que 11 distingue los estados s y t.S y t que tengan transiciones en e1 que caigan en diferentes conjuntos. rntelitra que el DFA tarribi6n lo h a r i Corrto la eficiencia es inuy importante en un analizador léxico. existe un DFA equiv. . eritonccs o tlisti~rguc y t 1.rlente (debería ser f i c i l para el lector convencerse de esto de manera infornial al leer el dgoritiiio). Esto es.rciOti t 110 iicire Ir:uisición r i .\ ILII ('o~icluire~nos liucstro m i l i s i \ dc l a tirirrimi~xi¿iti (le ebt:rdo con un par (te c. ~nicntr.y aquí describiremos hrevemeiite el algoritmo. 'Trrinbién es posible obtener directaniente este DFA míniirio de crtalquier DFA <la<lo. . un resultado itiiportante de la teoría de los autóiiiat. entonces esto M i n e una transición ri desde el riuevo estad6 de aceptación hacia el nuevo estado de no :rceptacií. dado cualquier DFA. Por supuesto.iceptaciónf se debe dividir de acuerdo con el lugar en que caen sus transiciones a. . s i u11cst. De hecho.ilente que coiitiene un núnrero niítriino de estados. considere las transiciones en cada carácter r i del alhbeto. De irianct-a sirnilar. también tlebemos considerar trmsiciones de error a u11estado de error que es de iio aceptación. s i todos los estados de aceptación tienen transiciones en ci hacia estados de no aceptacitín. el conjunto de estados que se está consi<lerando(es decir. Coinienza coi1 la suposicibn inás optirriista posible: crea dos con. Por otra parte.S y l . si existeii estados de :iceptación S y t .j~into hasta que todos los conjuntos contengan sblo un elemento (en cuyo caso: mostrarnos que el DFA original es mínimo) o hasta que no se presente ninguna divisiOn ritlicional de cotijuntos. s i hay dos estados de xeptaciciii . mientras que t no iciign tiingiiria transición (1 (es decir. iios gustaría en poder construir. declaraciones similares se inantie~ieti para cada uno de los otros conjuntos de estados.is establece que. P:ira que el proceso que acobamos de ciescribir funcione correctamente.1 inb': CII este c ü o .~uiitide todos los estados de aceptacitín antiguos) hacia sí m i m a .n (el conjunto de todos 10s estados de no acepktcibn mtiguos). Continuamos este proceso de refinar la partición de los estados del DFA original en con.tcept.Desde lar expresiones regulares hasta los DFA prr" la expreíibn regular a*. debemos movernos a ellos. un DFA que sea ~iiíniitio algún sentido.>ii. y una vez que hemos considerado todos los caracteres del alfabeto. El algoritmo funcioiia al crear conjuiitos de estados que se unificarin en estados siirlples. Dada esta particióii de los estaclos del DFA original. En este caso. De l a iiiisiiia iiiruiera. el corUunto de todos los estados [le .rci.iles que S tenga una trmsicibn ci hacia otro estxio de :iccptacih. Por supuesto. uno cuinpuesto <le !o<l«s los estridos de :rceptación y el otro coinpucsto de tctdos los estados de iio nccptacih.jc~iiplos.juntos. sin deniostrar que en realidad construye el DFA mínimo eqtiiv.rsque otro estado de no :iccpt. mtoriccs (1 ilistirigue . entonces ninguna transición ri se puede definir para este :igrupaiiiento de los estados.

De esta manera. rt distingue e1 estado 1 de los estados 2 y 3. Tenía cuatro estados compuestos del estado itiicial y tres estados de aceptación. Ahora los estados 2 y 3 no se pueden distinguir por i r o b. Consideremos ahora el carácter h. que dimos en el ejemplo 2. mientras que los estados 2 y 3 no tienen transición u (o. los tres estados & aceptación no se pueden distinguir por ningún carácter. correspondiente a la expresión regular letra(1etra 1 dígito)*.1 (sección 2. Por consiguiente. el estado 1 tiene una transición o hacia un estado de aceptación.Ejemplo 2.3): letra letra dígi t o Ejemplo 2. obtenemos el DFA de estado mínimo: . y el algoritmo de minimización da como resultado la combinación de los tres estados de aceptacicín en tino. 3 ) .19 Considere el siguiente DFA. Por otro lado. y debemos repartir los estados en los conjuntos ( 1 j y (2. así que ya no lo consideraremos. Así. El coujunto ( I ) no se puede dividir más.3.2) como equivalente a la expresicín regular (ale ) b*: En este caso todos los estados (excepto e1 estado de error) son de aceptación. mejor dicho. una transición de error en a hacia el estado de no aceptación de error). Ahora comenzainos de nuevo. Cada estado de aceptación tiene una transición b a otro estado de aceptación.18 Consideremos el DFA que construimos en el ejemplo anterior. Los tres estados de aceptación tienen transiciones a otros estados de aceptacicín tanto en letra como e11dígi to y ninguna otra transición (sin errores). de modo que ninguno de los estados se distingue por b. dejando el siguiente DFA iníninto (el cual ya virrios al principio de IU sección 2.

que son secuencias de uno o mis dígitos e identificadores.interior.as cxpresiones rcgul.Implementation de un anakzador l4xita TlNY ("D~minuto") 75 IMPLEMENTAUÓN DE UN ANALIZADOR LEXICO TlNY ("DIMINUTO") Ahora deseamos desarrollar el código real para un analizador léxico con el fin de ilustrar los conceptos estudiados hasta ahora en este capítulo. [.7). el código es de formato libre: el espacio en blanco se compone de blancos. . qtie tiene dos. que dan las cuatro operaciones aritméticas básicas con enteros. léxica de TINY. Además de los tokens.tdores y corncnt:iricts ('ClNY tiene versiones p. 2. En rcalidad 1 expresiones regulares se han dado anteriorniente para núineros. Los tokens y Las clases de tokens de.. tabulaciones y retornos de línea.1. T l N Y tiene las siguientes convenciones léxicas. excepto el de asignación.lcnguaje TINY. A l diseñar un analizador léxico para esk lenguaje. : s itlentific.. Haretrtos esto para el lenguaje T l N Y que se preseritó de manera inh~rrnal el capítulo I (sección 1.TINY se resuirien en Ia tabla 2. Tahla 2 1 Tokens del lenguaje TlNY Palabras reservadas Simbolos especiales Otros número ( 1 o más e if then else end regeat until read write + 1 dígttos) Los otros tokens son números. con significados familiares (aunque no necesitamos conocer sus semhnticas hasta mucho después).rres p x a los 011-os1i)kctis 110 son i~iiport. Existen 10 símbolos especiales. Todos los símbolos especiales tienen un cruáeter de longitud. signo de punto y coma y asignación. J y no pueden estar anidados. símbolos especiales y "otros" tokens. Los coinentarios están encenados entre llaves ( .irticular~ilentesirriples de &tos). es decir. y se sigue el principio de la suhcadena mis larga ert el reconociiniento de tokens. paréntesis. definir los tokens y sus atributos. Los tokens de TINY caen dentro de tres categorías típicas: palabras reservadas. dos operaciones de comparación (igual y inenor que).51 lmplementacion de un analizador léxico para el lenguaje de muestra TINY En el capítulo 1 expusimos brevemente y de manera informal el. Después analizarrrnos diveren sas cuestiones de implernentación prhctica planteachs por este analizador Iixico. podríarncts comenzar con expresiones regulares y desarrollar NFX y DFA de acuerdo con los iilgoritriios de la seccitin . Nuestra tarea aquí es especificar por completo la estructura. Existen ocho palabras reservadas.intesp<rrqwtcdos soti catleiiii\ . los cuales (por simplicidad) son secuencias de una o niás letras.

fijas. . una variable en el código). Necesitarnos agregar comentarios. En lugar de seguir esta ruta &sano«llaremos un DFA püra el analizador léxico directamente. que se alcanza desde el estado de inicio en la llave izquierda y regresa a él en la llave derecha. El espacio en blanco es consun~ido mediante un bucle simple desde el estado de inicio hacia sí rnismo. espacio en blanco y asignación a este DFA. que se alcanza desde el estado de inicio en el signo de punto y coma. los diferentes esrados de aceptación distinguen el token que el analizador livico está por devolver. entonces todos los estados de aceptación se pueden colapsar en un estado que denominaremos HECHO. entonces se genera u n tokcn de asignación. La asignacitin también requiere de un estalo intermedio. advertimos que todos los símbolos especiales. Si empleamos algún otro indicador para el token que ser6 devuelto (digamos. obtenemos el DFA siguiente: Advierta el uso de los corchetes para indicar caracteres de búsqueda hacia delante que no deberjn ser consumidos. Los comentarios requieren un estado extra. y ?e genera un token de crror. En primer lugar. De otro niodo. y un DFA para estos símbolos tendría el aspecto que sigue: MÁS de r e t o r n o MENOS d e r e t o r n o En este diagrama. el siguiente carhcter no debería ser consumido. excepto los de asignación. son caracteres únicos distintm. ya que los tokens son muy simples. Si combinamos este DFA de cios estados con DFA que acepten números identificadores. Haremos esto en varios pasos. Si sigue inmediatüinente un signo de igualdad.

Esta v:iri:iblc. son 105 Gnicos servicios ofrecidos a otras partes del conipilador.rtloi. . junto con los tokens de administración EOF (cuando el fin del archivo se alcanza) y ERROR (cuando se encuentra un carácter erroneo). pero dentro del analizador itiisrno (líneas' 612-614).h y 8can. y que no son tlígitos o letras. E l procedimiento priricipal es getToken (líneas 674-793).3. de cada token. totlos los caracteres simples que no están en la lista de símbolos especiales.es. y &te \e c(h>ea en la variable tokenstring. o valor de cadena del token reconociclo. junto con getToken.Irnplementac~bnde un analizador Iéxico T Y ("D~rn~nuta") N l figuid 110 ~ f del analizador lexico b de T Y N l espacio en blanco De hecho. las palabras reservadas se cimsideran s6Io úespués (le que se ha reconocido un identificador.10. y sus c(ci'inici«nes estlín reunidas en el :irchivo de cabecera scan.icirin). Observe que tokenstring se i. . La irnpleri~entacióu utilizael análisis case doblemente anidado que describimos en la sección 2. por qjcmplo. así ~ I los iileutific. el principio de la stihcadenii niás larga garantiza que la única acción del analizador léxico que necesita niodificarse es el token que es devuelto. el cual está contenido en los archivos 8can. y entonces consultar los identificadores en una tabla de palabras reservadas tScsptiés de la aceptación. e1 único ntr~buto que \e calcula es el Icxeina.1. Los estados del analizador léxico también est6n definidos como un tipo enumerado. c m una gran list~ case basada en el estado. Volvamos ahora a un análisis del código para iinplemeritar este DFA.10.c (véase el apéndice B). E l DFA final para nuestro analirador Iéxtco 5c puede aprcctar en la figura 2.10. De este modo.3.¡ci<j~ique se a n i ~ l i m r i [>~)sici~ior~~~e~~te. y iolerarernos éstos con los símbolos de carácter simple. En realidad. que no son espacios eii hlanco o comentarios. N o incluirnos palabras reservadas en nuestro aniílisis o e n el DFA de la figura 2. Esio es porque es más fácil desde el punto de vista del DFA considerar que las palabras rescrvadas son lo mismo que los identificadores. el cual incluye todos los tokens enumerados en la tabla 2. se deberán aceptar conio errores. Ebta cs una liriiit. el cual consume caracteres de entrada y ilevuelve el siguiente tokeri reconocido de acuerdo con el DFA de la figura 2. no j>~~etleri m i s (le -1-0m x t c r c s (más e l cnrickr nril(i [le tcrnrin. y en ocasiones tornar también otras acciones (tales coi110 la inserción de ideutificadores eii una tabla de símbolos). s i existen.186). Los tokens mismos están definidos como un tipo enumerado en globals h (líneas 174.h (líneas 550-57 1). dentro de la cual se encuentran listas case individuales basadas en el carácter de entrada actual. U n analizador léxico tarnbiSn necesita en general calcular 10s atributos. En el caso del malizador Iéxico de TlNY.leclarricon K tericr iula Iorigitutl fija de 41.

Finalmente. esto es necesario.que están declaradas en globals . La figura 2.un buffer interno de 156 caracteres para el analizador Iéxico. La tabla reservedwords (Iíneas 649-656) y el procedimiento reservedLookup (Iíneas 658-666) realizan una búsqueda de palabras reservadas después de que un identificador es reconocido por la iteración principal de getToken. el reconocimiento de números e identificadores en TINY requiere que las transiciones hacia el estado final desde ENTRADANUM y ENTRADAID no sean consumidas (véase la figura 2. y el valor de currentToken es modificado de acuerdo con esto. Como una ilustración del comportamiento del analizador léxico TINY.asumiendo cada vez yue una nueva línea de código fuente se está alcanzando (e increnientando lineno). c.1 1 (el mismo programa que se dio como c.y la variable entera lineno. Si el buffer se agota.h y asignadas e inicializadas en main. getNextChar lo "refrcsca" o renueva desde el archivo fuente con el procedimiento estándar de C fgets. y las alternativas se exploran en los ejercicios. La entrada de caracteres al analizador léxico se proporciona mediante la función get NextChar (Iíneas 627-642). considere el programa TINY sample. dado este programa como la entrada. Implementaremos esto al proporcionar un procedimiento ungetNextChar (Iíneas 644-647) que respalde un carácter en el buffer de entrada. porque los espacios en blanco. un programa 'TINY con líneas de más de 255 caracteres no se manejará de manera tan correcta como se necesita. los comentarios y las búsquedas no consumitlas no deberán incluirse. Dejaremos la investigación del comportamiento de getNextChar en este caso (y las mejoras a su comportamiento) para los ejercicios.jeinplo en el capítulo 1). tny de la figura 2. cuando TraceScan y EchoSource están establecidos. La administración adicional efectuada por el procediniiento getToken es conio se describe a continuaci6n. El resto de esta sección se dedicará a elaborar algunas de las cuestiones de itnplementación alcanzadas mediante esta implemeiitación de analizador Iéxico. De nuevo esto no funciona muy bien para programas con Iíneas de código fuente muy largas. Figura 2 11 Programa de muestra en el lenguaje TINY .12 muestra el listado de salida del analizador Iéxico.10).El analizador léxico emplea tres variables globales: las variables de archivo source y listing. la cual obtiene los caracteres (le lineBuf.Aunque esta suposición admite un código más simple. Una variable de marca ("flag") denominada save se utiliza para indicar si un carácter está por ser agregado a tokenstring.

1 10: ID. 11: reserved word: until 11: ID. val= O 11: .Implementacton de un analizador ié. val= O 6: < 7: 8: 9: 10: 11: 12: 13: 6: ID. 9: ID. name= x 10: := 10: ID. 7: ID. name= x 6: reserved word: then fact : = 1 . name= fact end 13: reserved word: end 14: EOF . n a m e x 11: = Ilr NUM.xtco TINY ("D~mtnuto") Fiqurr 2 11 salida del analtzador lexm proporctonand~al programa llNY de la ftgura 2 1 I tomo entrada TINY COMPILATION: sample. x : = x . { se introduce un entero 1 5 reserved word: read : 5: ID. val= 1 7: . namer x 9: . name= fact 9: := 9: ID. regeat 8: reserved word: repeat fact := fact * x.tny 1: { Programa de muestra 2: en lenguaje TINY 3: calcula factoriales 4: } 5: read x. name= x 10: 10: NUM. 6: if O < x then f no se calcula si x <= O 1 6: reserved word: if 6: NUM. name= fact 7: := 7: NUM. val= 1 until x = 0. name= x 5: . write fact { salida del factorial de x 1 12: reserved word: write 12: ID. names fact 9: * 9: ID.

2 1 RASTREO O ANÁLISIS LÉXICO 25. la tabla de símbolos. ya que sus tamaños de cadena son fijos.que asigna de manera dinámica sólo el cspacio necesario. En nuestro analizador léxico utilizamos un método muy simple (la búsqueda lineal). los cuales por lo regular tienen entre 30 y 60 palabras reservadas. entonces se desperdicia gran parte del espacio. y sus lugares en la tabla estarán fijos para toda ejecución del compilador. es decir. la cual podríanios haber aplicado al escribir la lista de palabras reservadas en orden alfabético. se introducen todas las palabras reservadas en esta tabla y se marcan como reservadas (de manera que no se permita una redethición). Por ejemplo. Una posibilidad es una búsqueda binaria. funciones que distinguen cada patabra reservada de las otras. entonces una función de cálculo de dirección mínima perfecta produciría siempre un valor de O a 7. (Véase la sección de notas y referencias para mayor información. Una función de cálculo de direcciones de esta clase se puede desarrollar en principio.2 Palabras reservadas contra identificadores Nuestro analizador léxico TINY reconoce palabras reservadas considerándolas primero como identificadores y posteriormente buscándolas en una tabla de palabras reservtrdas.) Otra opción para tratar con palabras reservadas es emplear la misma tabla que almacena identificadores. porque las palabras reservadas no van a cambiar (al menos no rápidamente).3 Asignación de espacio para identificadores Un defecto adicional en el diseño del analizador léxico TINY es que las cadenas que forman el token sólo pueden tener como máximo 40 caracteres. de manera que esta solución no es apropiada para este diseño en particular. Esto no es un problema para la mayoría de los tokens. 2. Antes de comenzar el procesamiento. Una solución para la limitación de tamaño de tokenString sería semejante: asignar espacio sólo con base en las necesidades. Esto tiene la ventaja de que se requiere sólo una tabla de búsqueda. pero significa que la eficiencia del analizador dependerá de la eficiencia del proceso de búsqueda en la tabla de palabras reservadas. si sólo existen ocho palabras reservadas. debido a que la mayoría de los identificadores son breves. Esto no es un problema para tablas muy pequeñas como las de TINY. y que tienen el número mínimo de valores. En este caso nos gustaría utilizar una función de cálculo de dirección que tuviera un número muy pequeño de colisiones. porque las cadenas que forman el token se copian utilizando la Función utilitaria cogystring. Aún peor. porque los lenguajes de programación a menudo requieren que identificadores arbitrariamente extensos se permitan en los programas. pero es un problema para los identificadores. y cada palabra reservada producir& un valor diferente.CAP. y esto puede exigir el uso de una mejor estructura de datos que el de una lista lineal. en el analizador léxico de TINY no construimos la tabla de símbolos sino hasta después de la fase de análisis léxico. de manera que se puede utilizar una tabla de cálculo de dirección no mayor que el número de palabras reservadas.Una alternativa es asignar un arreglo inicial grande para todos los identificadores y después realizar una asignación de memoria "a mano" dentro de este arreglo. En este caso se requiere de una búsqueda más rápida.) . Otra posibilidad es emplear una tabla de cálculo de direcciones (o tabla de dispersión). que sólo tienen ocho palabras reservadas. Esto no ocurre en el código del compilador TINY. Se realizó cierto esfuerzo de investigación para determinar las funciones de cálculo de dirección mínimas perfectas para diversos lenguajes. Esto es una práctica común en analizadores léxicos.5. si asignarnos un arreglo de 40 caracteres para cada identiticador. es decir. pero se welve una situación inaceptable en analizadores léxicos para los lenguajes reales. en el cual la tabla se consulta de manera secuencia1 de principio a fin. Sin embargo. como veremos en el capítulo 4. utilizando posiblemente la función estándar de C realloc. (Éste es u n caso especial de los esquemas de ~tdniinistraci6nde memoria ~linámica estáudar que se analizaron en iil capítulo 7.

debeinos escribir *' ( ' l .orrrocxpresiorics regulares. ['or otra parte. jririto con las acciones que se tmn:triri cuando se iguale ciicta expresi6n. Se distribuye coino parte del paquete compiiador (. T : t i i ~ h i ~ uiiliz. es prcferiblc cscr-ibir " ( * 'I Tainbién e1 LISO de la tli:igo~ial iiivcrtitla con caracteres regulares puede tcncr ti11h i g t ~ i l i c. pero ahora u t i l i ~ a r e m o s gerierxlor del :iiializxlor el Iéxico Lex para gcnci-. *. UN ANALIZADOR LEXICO . Una alternativa es utilizar el nietacaricter de diagonal invertida \.6 USO DE Lex PARA GENERAR AUTOMATICAMENTE Eii esta seecicín repeiiriios el desarrollo tle un a i i a l i ~ a d o rIéxico p m el Icnguaje 'I'INY que se realizó en l u seccibn anterior. sean o rto metacarneteres. exactamente como se ligó el archivo scan.is que se analiz:iron en la secciítn 2.il. (. \n citri-esponde a un retorno de línea y \t corrcspotide a uria t. a i lirtr~tareinos nuestro ~uiálisis aquellas caracteristicas que soti comunes a todas o .c. [. Por ejemplo. clarciiios una perspectiva general y posteriormente propctrci«naueiiios iris conveiiciones de [. la mayoría cic las versiones. Corno existe tina variedad de versiories diferentes de Ldex. y tamhién se encuentra disponible gratuitamente en ~ i ~ u c l i o s sitios de Internet.ido especial. Por ejeruplo. t i 1 vez de enumerar todos los inctacitracteres de Lex y describirlos de manera iiitiivitlual. ir 1-cx irilcrpreia 10s i~~ct:iciil-. analizaremos en primer lugar las corivericioiies tle [.nu que produce la Frec SOTIware Foundation. 2.LII. Las cori~illas tarnbién se pueden escribir en torno de caracteres que no son metacaracteres.ador a partir de iioa d e s c r i p c i h de los tokens de T I N Y ~.2. pero esto s6lo iunciona p i r a n ~ tacaracteres simples: para igualar la secuencia de caracteres ( * tendríainos que escrihir \ ( \ *. con s6lo escribir los caracteres eii secuencia.t e l sigtio de iriterrr)gnci(íri como 1. coino l o hiciriios en secciories ariteriores. E n l o que sigue.111 ~iict:i~. L a versi6n más populrtr de Lex se conoce com« flex (por "F. podernos escrihir if o "if" para igu:tlar la palabra reservada if que da inicio a una sentencia "if" o condiciorial.3. y Iii rii. Evideittcrrrente. E l archivo de salida de Lcx.ick~rcs +. se compila y liga entonces a un programa principal para obtener un prograiria ejecutable.c con el archivo tiny c en la sección anterior.ex para escrihir expresiones regulares y el formato de un iirchivo de entrada de Lex.ex en una tabla.6.. Este programa produce un archivo de s:ilitla que contiene u n código fiiente en C que define un procedimiento yylex que es una itnplerneiitación controlailn por labla (le un DFA correspondiente a las expresiones regulares del archivo de entrada.rst 1. denorriinado por l o general 1ex. y que funciona como un prctccdiniietito getToken. p:rni continuar con e l anilisis del archivo de entrada de L e x del analizador léxico T I N Y que se dio c n el apéndice B. donde no tienen efecto alguno.~ piirtc o p c i ~ ~ i i : i(:o1110 I. puesto que es un metacarieter. L.c o 1exyy.ex tarnbién permite que los inctacirracteres se igualen como caracteres reales errceixáridolos entre cumillas.1 Convenciones de Lex para expresiones regulares Las convenciories de [.:ir:íctcrporü iiidiciii.ir uii iitiali/.cx es un progratiia que torna como entrada un archivo de texto que citntieite expresiones regulares.e~"). para igunliir un paréntesis izquierdo.yy.ex so11 muy similares a I. 0 cadenas de caracteres.ex permite l a correspoiidericia be caracteres simples. . tiene sentido escrihir comillas para encerrar a todos los caracteres que se vayan a igtialar directamente.Uro de iex para generar automatitamente un analizador Iéxico 2. ) y i tlc la iriniicra hühiui. De este modo.tyoría de tales coiivertcioircs \e IIcvnii a Lex). [.thulacicín (éstas son ctiiivcnciorics típicas de C. repitiendo dicha diagonal invertida.

Conio otro ejemplo. [AO-9abc]significa cualquier carácter que no sea un dígito y no sea tina de las letras u. excepto un retorno de línea. defininros naturalconSigno cn la sección 2. Por ejen~pb.4 corno sigue: . conjutitos que rco contienen ciertos caracteres) en esta notación utilizando el signo carat como el primer carácter dentro de los corchetes. la expresión [O-91 significa en I. También se pueden escribir conjuntos complementarios (es decir. [ " ? 1 significa ctialquiera de los tres caracteres. De este modo. y podríamos escribir la anterior expresión regular en Lex corito (aa l bb) [abl* c ? También se pueden escribir intervalos ile caracteres en esta forma empleando un guión. b.1 debido al uso del n~etacarácter de para expresar un intervalo de caracteres). Como ejemplo escribamos una expresión regular para el conjunto de números con signo que pueden contener tina parte fracciona1 o un exponente comenzando con la letra E (esta expresión se escribió en torma un poco diferente en la sccción 2. Recordenlos que a una expresión regular se le puede dar un nombre. Sin embargo. De esta manera. la mayoria de los metacaracteres pierden su estatus especial y no necesitan estar entre comillas. comillas o signo de interrogación (los tres caracteres perdieron su significado de metacarácter dentro de los corchetes). Un punto es un metacarácter que también representa un conjunto de caracteres: representa cualquier carácter.9 1 + natconsigno = ( " + " I " . algunos caracteres son todavía ntetacaracteres incluso dentro de los corclietes. punto. h o c. podríamos haber escrito [ -+] en lugar de ( " + I r 1 " . x o i .ejeniplo ile la notación de Lex malizada hasta ahora. y para obtener el carkcter real debemos anteceder al cardcter con una diagonal invertida (las comillas no se pueden utilizar porqne perdieron su significado de nietacarácter). y que estos nombres se pueden utilizar en otras expresiones regulares mientras que no haya rcfcrencias recursivas." ) ? nat . Incluso el guión se puede describir como un carácter regular si está enumerado primero. nat = [ O . Una importante convención adicional de metacarácter en Lex es el uso de llaves para denotar nombres de expresiones regulares.2.2. [ \" \ \ 1 significa cualesqnizra ile los caracteres reales A o \ . podetnos escribir tina expresión regular para el conjunto de cadenas de las (1 y h que comienzan con ciri o hh y tienen una c opcional al find como La convención de Lex para clases de caracteres fconjuntos de caracteres) es escribirlas entre corchetes (paréntesis cuadrados). De esta manera. Así. Por ejemplo.4): IJna curiosa característica de Lex es que dentro de los corchetes (que representan una clase de caracteres).ex cualquiera de los dígit6s desde el cero hasta el nueve." ) en la expresión regular anterior para números (pero no [+. [abxzl significa cualquiera de los caracteres a.

Por coiisiguictite. h. De este titodo. incluso si <i es un inetacarácter c. .rs. 1) o c crialquieríi de los caractcrcs o. el ejemplo anterior aparecería cotitci sipie ctl Ixx (Lex iaiithién prescinde del signo de igwldad eri la ileSiiiici6n de nuinbres): n a t [O-91+ natconSigno ( + l . Idex riiilira la cotiveiici611de que los ttomhres previatneitte definitlos se cilcierrcri ~tietliarite llaves. una colección de reglas y una coleccicín de rutinas auxiliares o rutinas del usuario. tina coleccióii de definiciones.Uso de Lex para generar automaticamente un analizador léxico 83 tiri éste y otros ejeinplos utili~ain»s cursivas « itilic:is para distinguir los notnbres de las secuencias ordinarias de caracteres. c o rl cualquier cnr5cter exccpto o o h cualquier carácter excepto iiii retorno (le línea la expresión regular que representa el nombre crv 11 2. \a a* a+ (a) 1abc 1 [a-dl iAabl i xxx > niisrna cualquiera de los caractcrcs o.cx que no utilizareinos y 110analizawntos aquí (véatise las rcfereticias al fitial tld c~tpítiilo).) ? h a t > Advierta que las llaves no aparecen cuando se define un notnhre. así que no se dispone tle letras ikÍlic.rr. Las tres secciones están separadas por sigiios de porcentaje dobles que aparecen en líneas sepamdas cornenrando en la pri~iiera colunina.ex se compotre de tres partes.' . sólo cuando se utiliza. Existen varias otras coirvciiciones de metacaracteres en L. Tablr 1 2 Convenciones de metacaracteres en tex Patrón Significado el carácter a el carácter a. los archivos de Lex son iirchivos de texto ordinarios..2 contietie tina lista resutnida de las coiivenciones de metacaracteres de Lex que hemos analirado. Siri ernbargo. En su lugar. La tabla 2.áctcr cero o más rcpeticioiies de rr una o m.is repeticiones de u a "a.6.2 E formato de un archivo l de entrada de Lex Uri archivo de entrada tle [.ex es coino se tniiestra a coiitinuación: {definiciones) %% {reglas) "9% (rutinas auxiliares1 .icter rr cuarido 11 cs u n rrietacai. el diseño de tiii archivo de entrada de 1.

Éstas se componen de una secuencia de expresiones regulares seguidas por el ccídigo en C que se va a ejecutar citando se iguale la expresión regular correspondiente.20 siguiente entrada de Lex especifica un analizador léxico que agrega números de línea al texto y envía su salida a la pantalla (o a un archivo..) Daremos una serie de ~jetnplospara ilustrar el formato de un archivo de entrada de ILex. Un nombre se define al escribirlo en una línea por separrtdo. imprimiendo el texto nuevo en la salida estándar */ #include <stdio. 2 1 RASTREO O ANÁLISIS LÉXICO Para entender adecuadamente cómo interpreta Lex un archivo de entrada de esta clase debernos tener en mente que algunas partes del archivo serán iuformación de expresión LEgular que Lcx utiliza para guiar su construcción del código de salida en C.CAP. los nombres para expresiones regulares tanibién se deben definir en esta sección. Ejemplo 2. cualquier código C que se deba insertar de manera externa a cualquier función debería aparecer en esta sección entre los delimitadores %{ y %}. Dareinos las reglas exactas que Lex emplea para esto después de que hay:nnos analizado cada tina de las tres secciones y proporcionado ~Igiinos ejemplos. Esta sección también puede contener un programa principal.a sección de definición se presenta antes del primer %%.alida siguiente: 1 %{ 2 / * un programa de Lex que agrega números de línea . y que Lex insertará textualmente en el código de salida en la localidad apropiada. lineno++. mientras que otras partes del archivo serán cóiiigo C real que estamos suministrando a Lex. si queremos compilar la salida de Lex coino un programa autónomo. 1 main() { yylex0. yytextf . %l line . return O. 1. en cuyo caso no se necesita describir el segundo %%.*\n %% (line) %% { printf ("%5d %S". (¡Fíjese en el orden de estos caracteres!) En segundo lugar. En primer lugar. La tercera y última sección contiene código C para cualquier rutina auxiliar que se invoque en la segunda sección y que no se haya definido en otro sitio. ejecutar el programa ohtenido de Lex en este mismo archivo de entrada proporciona la .3 %{ /* un programa de Lex que agrega números de línea a las líneas de texto.h> int lineno = 1. si es redirigido): 1. Contiene dos elementos. La segunda sección contiene las reglas.~ continuando (después de uno o más espacios en blancoj con y la expresión regular que representa. comenzando en la primera column. Incluso se puede omitir. > Por qjemplo. (El primer %% siempre es necesario.

iguala a O o más caracteres (sin iticliiir un rctorno cpe de Iínea).*\n 10 %% 11 (line) ( printf ("%5d %s". (No olviile que las llaves que encierran CI nombre line tienen una función por completo diferente de la que tienen las llaves que forinai~ bloqrte en el código de C (le un la siguiente acción. cotno estlí al Final del código C producido por Lxx. En este ejernplo la accibn consiste en una sentencia simple en C.a a 1 . Despiit5s de la expresibn regulx c s ~ la acción. 1 12 %% 13 maint) 14 i yylex0.1 definición del nctinhre line. Esto provoca que estas Iíiieas se irisertcn direcintnerite en el código C protliicido por el Lex.linenott.yytext).después de la cual se iticreinerita lineno. return O. que en este caso se compone de cada Iínea de entrada (incluyendo el retorno de línea). En primer lugar.h> 7 int lineno = 1.El nombre yytext es e1 nombre interno que Lex le da a la cadena igtialada por la expresih regular. A coritiiiuacibri del %% en la Iítiea 10.ex). imgrimiendo el texto nuevo en la salida estándar 5 */ 6 #include <stdio. la Iínea 1 I coiiiprende la sección de accióii del archivo de entrütla de L e s En este caso escrihiinos una acción simple por reali~arsesieinpre que se iguale una line (lineesti encerrada entre llaves para distinguirla como un noinbre. el código cn C que se i va a ejecutar siempre que se iguale fa cxpresi6ri regular. En otras palabras. (yylexes cl nonibre que se da al procedirnieiito construido por Lcx que itnplenietita el DFA asociado con las eupresioiics regulai-cs y acciones tlnrlas m la sección de :iccióti del archivo de entrada. y la directiva #include y la definición de la variable entera lineno en las líneas 6 y 7 se insertará externamente.) Esta sentencia en C es para imprimir el niimero tic Iínen (en campo de cinco espacios. Esto permite que el código C protlucitlo por Lex sea coinpilaclo en un programa cjeciitahlc. es decir. seguida por u n reiortio de Iíiicn. las líneas I hasta la X se ericiieritran entre los deliinitadores % { y % } .Uso de Lex para generar automáticamente un analrzador Iéxtto 3 4 a las líneas de texto. de rnoikt que lineno se corivierte en una variable global y su valor se inicia1ií.qiie se define cnnio la expresión rcgttlar *\nU. . En ~xirtictilar. comeiitario cti las Iíneiis 2 a la 5 se insertará cerca del principio del progratna. 8 %) 9 line .' Fi~ialtnente inserta el código en C después del segutise do síniboio de porcentaje doble (líneas 13 y 14). 1 i 1-laremos comentarios en este archivo de rtitrada de Lex utiliründo estos nlírneros de Iínea. En este ejcrnplo el código se compone de la definición de ttri procedirnierito main que llama a In fiinción yylex. La otra definícióii que aparece antes del primer %% es 1. de aciicrdo con la convcnci6n de ¡. externo u cualquier procedimiento. la cxpresicín regular clet'inida por line iguala cada Iínea de entrada. que se encuentra contenida dentro de las llaves de tin hloclue de C. justificado a la derecha) y la cadena yytext.) 5 ".

) 9 . y los pasa hasta la salida. )). (Estudiaremos este uso en el siguiente ejemplo. En realidad. count ) .Este ejemplo también es diferente en el sentido de que no todo el texto es igualado. Lex todavía genera un programa que también iguala todos los caracteres no numéricos. (Si e1 número es niás pequeño que o igual a 9.h> int count = 0. %) digit [O-91 number (digit)+ 06% {number) { int n = atoi(yytext). y finalmente incrementa count si el número es mayor que 9.) Así. fprintf (stderr. if (n > 9) count++. 1 %% main( ) I nlex0. printf ( "%x" n)..Ejemplo 2. la única acción especificada es para cadenas que son secuencias de dígitos. */ . 1 Éste es semejante en estructura al ejemplo anterior. &te es un ejemplo de una acción predeterminada por Lex. . return O. Elimina todo lo demás. luego lo imprime en fornia hexadecimal ( p r i n t f ('r%x". pero aquí no estudiaremos cómo se hace. Lex lo hará de manera predeterminada y duplicará la acción hacia la salida (hará "eco"). sólo los números se igualan en la sección de acción. Ejemplo 2. entonces no tendrá aspecto diferente en hexadecimal.h> #include tstdio. .22 Considere el \iguiente archivo de entrada de Lex: %( / + Selecciona solamente líneas que finalizan o comienzan con la letra 'a'. donde el código C pard la acciún primero convierte la cadena igualada (yytext) a un entero n."número de reemplazos = %d". Si un carácter o cadena de caracteres no se iguala por ninguna de las expresiones regulares en la sección de acción.2 1 Considere el siguiente archivo de entrada de Lcx: %( / * un programa de Lex que cambia todos los números de notación decimal a notación hexadecimal.) La acción predeterminada también se puede irtciicar en forma específica tilediante la macro ECHO de Lex definida internamente. (También se puede obligar a Lex a generar un error de ejecución. excepto que el procediniiento main imprime el conteo del número de reemplazos para s t d e r r después de llamar a yylex. imprimiendo una estadística de resumen hacia stderr */ #include <stdlib.

Ejemplo 2. cualquier línea de entrada iguala la expresión * \n. Entonces. 9 porque toda línea de la entrada se reconocería por medio de la primera regla. .*\n . De hecho.h> #ifndef FALSE #define FALSE O #endif #ifndef TRUE #define TRUE 1 .h> %) finaliza-con-a .. cualquier texto dentro de los delimi*/): tadores /* . Existe una característica adicional en esta entrada de Lex que vale la pena resaltar. .*\n 86% (finaliza-con-a? ECHO. (comienza-con-a} ECHO.*a\n comienza-con-a a. En primer lugar. Las reglas enumeradas son ambiguas en el sentido de que una cadena puede igualar más de una regla. excepto las letras dentro de los comentanos estilo C (es decir. Lex siempre iguala la subcadena más larga posible (de modo que Lex siempre genera un analizador léxico que sigue el principio de la subcadena más larga). return O. si la subcadena más larga todavía iguala dos o más reglas.*\n . Todas las otras líneas de entrada se suprimen.En esta regla la acción "vacía" se especifica mediante la expresión regular *\n al escribir un signo de punto y coma para el código de acción en C.23 En este ejemplo. entonces el programa producido por Lex no generaría ninguna salida para cualquier archivo.sin tener en cuenta si es parte de una línea que comience o finalice con una a. Si tuviéramos clasificadas las acciones en el orden siguiente. (comienza-con-a) ECHO. %% main ( ) f yylex0.Uso de Lex p n generar automáticamente un analizador Iixico a #include <stdio. La eliminación de la entrada es causada por la regla bajo las reglas ECHO.. . . (finalizacon-a) ECHO. Lex tiene un sistema prioritario para resolver tales ambigüedades. Es por esto que el archivo de entrada de Lex anterior enumera primero las acciones ECHO. Lex genera un programa que convertirá todas las letras mayúsculas. Lex elige la primera regla en el orden en que estén clasificadas en la sección de acción. %( / * Programa en Lex para convertir letras mayúsculas a letras minúsculas excepto dentro de los comentarios */ #include <stdio. ) Esta entrada de Lex provoca que todas las líneas de entrada que comiencen o fiitalicen con el carácter a se escriban hacia la salida.

El tiso de este procedimiento. como sigue: while ((c=inputO)!='*') gutchar(c).p ( char c. más que una entrada directa utilizando getchar.4. do ( = FALSE.2. while ((c=inputO)!='*') putchar(c). y entonces. La primera cosa que hacemos es recorrer los caracteres (duplicándolos hacia la salida) hasta que vemos un asterisco (correspondiente al bucle o t r o en el estado 3). (Advierta. Una vez que liemos reconocido la cadena "/*". que empleamos un procetiimierito de salida directo gutchar. mientras proporcionamos la acción apropiada para otros caracteres dentro del comentario (en este caso precisamente para duplicarlos sin procesamiento adicional). 1 %% void main(void) ( yylex0. if (C == ' / ' ) done = TRUE. int done ECHO.6.4 se determinó que una expresión regular para un comentario en C es muy compleja de escribir. gutchar(c). dc 5 . escribirnos una expresión regular sólo para la cadena que comienza un eomenrario tipo C (es decir.asegura que se utilice el buffer de entrada de Lex. gutchar(c). página 53). Kecuerde que en el análisis que se realizó en la sección 2.) El siguiente paso en nuestro código para el DFA corresponde al estado 4. de modo que aquí elige nuestro código el DFA. " / * " ) y después suministramos un código de acción que buscará la cadena de terrninación " * / ". Esto se analizará con nxís detalle en la secci6n 2.S/*.) Este ejemplo muestra cómo se puede escribir el código para esquivar expresiones regulares difíciles e implementar un pequeño DFA directamente como una acción de Lex. si el mricter es uria diagonal. y que se conserve la estructura interna de la cadena de entrada.9 (véase la figura 2. Hacemos esto al imitar el DFA del ejemplo 2. estamos en el estado 3.4. regresarnos al estado 3.#endif %) %% [A-z] (putchar(tolower(yytext[Ol)). while ((c=inputO)=='*') putchar(c). ) while (!done). Icrnlini~nios: otrit manera. En su lugar. sin embargo. Repixireirtos c1 ciclo de nuevo hasta que rlo veamos un asterisco. / * yytext [ O ] es un solo carácter en letras mayúsculas encontrado * / ) . Aquí todavía utilizamos otro procedimiento interno de Lex denominado i n p u t .

Sin eilihorgo.~ »S .irchivo arbitnirio' reernpi:i/xía estos irsos con a .I 4. 2) Cualquier texto en la sección de procedimientos auxiliares se copiará directamente al pmgratna de salida al final del código de Lex. L a mayor parte de ellos ya se estudiaron en los ejemplos anteriores.3 Algunos nombres internos en Lex Nombre interno en Lex 1ex. E l c k i i g o C de que representa una acción puede ser una sentencia simple en C (1 una senteticia compuesta en C compuesta por cualesquiera declaraciones y sentencias erieerratlas entre llaves. Si dos o más reglas provocan que re recotiozcnri s~ibcadcniis igtial Iotq$titl. la de qtic Lex t i m e sus propios nornhres internos para los archivos. .i.stdin) de Aichivo de d i d a de Lex (predetermin.iiiios el archivo de salida interno w o u t y sólo cscrihiiiros 1.yy.Por medio (le la rutina de entrada estántkir de Lcx input autornálic.'. NOMBRES INTERNOS L a tabla 2. Si ninguna rcgla coincide con cualquier siibcailena no vacía. errtotices la acción pretleterminada copiii el siguiente carácter a la salida y cotitiiiúa.cx que present:tnriis (le m los ejemplos.3 enumera los nornhres internos de Lex que analizamos en este capítulo. en los ~!jempl«s ariteriores.inientc tornará la e11tr:ida del archivo yyin. e11tonces la salida de I. cvit. Tnblc 1.co 1exyy.Uso de Lex para generar automi!itameiite ttn aoalrrador léxrto 89 Firializainos csra subseccicín con riri resumen de 13s conve~icio~ics l. ciivía la salida: yyin y yyout.c yylex yytext yyin =out input ECHO SignificadolUso Noriihre de archivo de sdlida de Lex Rutina de iiniilisis léxico de Lex Ctdenü reconoctda en le :iccicín actud Arch~vo entrada de Lex (predeterin~ndo.ex elegirá la rcgla clasilicada en primer lugar en la sección de nccicín.. Una rncjor iinplernent. (le los cuales torna la entrada y h. qiw pcririitc la asigtiacicín tlc la s ~ l i d a uii . 1 .ido stdoutf Rutitla de cntrada con hutfer de Lex AcciC>npretletermlnada de [. 3) Cualquier ccídigo que siga a tina cxpresicín regular (por al rnenos un espacio) en la secciíin de acción (después del prirner %%) se insertará en el lugar .ipropiatlo en el procedimiento de reconocimiento yylex y se ejecutará ctiantlo se presente una coi~icidencia la expresión regular correspondiente. RESOLUCIÓN DE AMBIGÜEDAD !La salida de L x x sieiiipre iguala en priiiier lugar la subcadena iitJs larga posihlc pasa una tic rcgla. i d a estándiir utilizaiitlo printf y putchar.icitiii. INSERCI~N CÓDIGO c DE I ) Citalquier texto escrito entre % { y %} en la sección de definicicíri se copiará directamente hacia el programa de sdida externo para c~talquier procedimicnto. ~ u a l e s LI.ex (itl~prinle yytext a yyout) 0b.iervamoí una caracterktica dc eíta tabla que no \e había mencionado.

aun cuando la expresión regular para los comentarios de TINY sea fácil de escribir.63 Un analizador léxico TINY que utiliza Lex El apéndice B ofrece un listado de un archivo de entrada en Lex tiny. Una peculiaritlad de la entrada de Lex es que tenemos que escribir un código para reconocer comentarios con el fin de aseguramos de que lineno se actualiza de manera correcta. las reglas de resolución de ambigüedad de Lex hubieran podido ocasionar que siempre se reconociera un identificador en lugar de una palabra reservada.il. . El procedimiento yylex de Lex tiene un comportamiento predeterminado al encontrar EOF: devuelve el valor O. h (línea 179). 6. Esto sería en realidad preferible en un compilador real. en consecuencia.y la definición de identifier utiliza a letter definida con anterioridad.1 ciídigo espec'i. la cantidad de memoria utilizada por el analizador). en la que sólo se reconocían identificadores. A continuación haremos varias observaciones acerca de este archivo de entrada (líneas 3000-3072).)' Notamos también que no hay código escrito para regresar el token EOF al encontrar el final del archivo de entrada. 1 que generará un analizador Iéxico para el lengtiaje TINY. A esto se debe que el token ENDFILE se haya escrito primero en la definición de TokenType en globals. puesto que palabras reservadas reconocidas por separado ocasionan que las tablas en el código del analizador Iéxico generado por Lex crezcan enormemente (y. Observe que la definición de number utiliza el nombre previamente definido digit. h y sean. Las definiciones también distinguen entre retornos de línea y otros espacios en blanco (espacios y tabnlaciones. de manera que tendrá el valor 0. líneas 3019 y 3020). En primer lugar. el archivo tiny 1 contiene una definición del procedimiento getToken en la sección de procedinlientos auxiliares (líneas 3056-3072). La sección de acción de la entrada de Lex se compone del listado de los diversos tokens. El uso rle esta vm-iahle en lugar de lineno permitiría eliniinar i. Si se hubiera enumerado primero la regla del identificador.5 (véase la tabla 2. y posteriormente se buscaban las palabras reservadas en una tübla. 2 1 RASTREO O ANALISIS L~XICO 2. cuyos tokeiis se describieron en la sección 2.1 ). la expresión regular es . Finalmente. . porque un retorno de Iínea provocará que lineno se incremente. b a l ~ En esta definición de Lex enumerantos las reglas para palabras reservadas antes que la regla para un identificador.h) y la definición del atributo tokenstring.util. "(M [A\)]*")" (Advierta el uso de la diagonal inversa dentro de los corchetes para eliminar el significado de metacarácter de la llave derecha: las comillas no funcionürjn aquí. El contenido adicional de la sección de definición comprende las definiciones de los nombres para las expresiones regulares que definen los tokens de TINY. Algunas versiones de Lex tictien una variable yylineno definida intcrnainente cluc se :iciu:ilim de manera autom2itica. junto con una sentencia retum que devuelve el token apropiado como se define en gloh. .Esto es necesario para proporcionar la interfaz entre el analizador Iéxico y el resto del compilador TINY. Aunque este código contiene algunas inicializaciones ex profeso de los procedimientos internos deLex (tales como yyin y yyout) que sería mejor efectuar directamente en el programa principal.CAP. en la sección de definiciones. También podríamos escrihir código como en el analizador Iéxico de la sección anterior. el código C que insertamos directainente en la salida de Lex se compone de tres directivas #include (globals h. En realidad.

e. Todas las c.io icner los irietasíniholos <t> (p:im el conj~itito v:ick)) o r.irchivos Sitente de TINY para p t i ~ l u c i imii vcrsiiiri hns.35). T«d.. ):\ que .' Encuentre U d~. ( 0 1 1 1 IYlAlBICIDIEIF)+(xlX) a. ( a a l b ) * ( a l b b ) * d. 1)ihrijc iin LF.scrik cxpresictnes reguiares par& los siguientes conjuntos de caracteres. ilesptiés (le generar e l itrchivo tiel analizador l é x i c o <le C 1ex.i<icple l .rn un ~iúrnero (le <i y un núiiieri~ (le h. dexcriha sus convenciones de inetasinrholos. . Todas Ins cadenas de dígitos que no crlritenfan ceros al principio.irece d e u n servicio de 1.rlrii p. 2.rnas contienen una vcrsinn de grcp (glohd regular expression pririt. par p:ir i. I c. ( a l b ) * a ( a l b l r . sin cambiar n i i i g ú n o t r o . S i n eni'irgo. h. Todas las criden:is de dígilos que representen nú~rieros parcs. 1 r>ihult »FA p r cada uno de los conjuritiis ¡le caracteres del iiicisii a) ti1 inciso i i di.cciniento que descritxr su grcp Iocol. este .~ry ligar tlirectarxiente c o n los otros . . d. Escriba descripcio~iescn español para lo\ Ic~~giiiiJc\ gene~tdos las signicritcs cxpresiones por regulares: a. [:NfX.is r e s ~ ~ r b : ~ r l .ii.is 11 L. o explique las ramnes por las que no se puedcii cscrihir espresio~ics regul.~lahi-.ideiras.m cri. Totlas las c:deiias de I I y h qiic contengan un número impar de u o i m núniero impar dc h (O iuiihos).4 2.is las c:tdenas di: letras nli~iúsculas que cornienceii y liiralicen con ii.. Todas las cadetias de letras tui~iúscul. t:. uti programa de húsqueda de expl-csioncs ~reg~iluws I escritu original~neritcpar:.rdcnas de dígitos tales quc todos los nú~ncms se preserrtc~~ "7" ~111tes todos que los "Y. 'Totlns las cadenas ile ir y h que coittcligm exact:rniente t:int. e n l a q ~ i e o se proporciona ctídigo I'uen~e<luplicaclo c o n los niimeros I S n de línea (véase cl ejercicio 2. no es rieces. no especificamris s i a Ib Ic significiih:~ ( a I b ) Ic o a l (b l c ) .L I. & l Icngt~aJc .~denav. Si su editor acepta alguna clase de expresio~iesregul. c (o lexyy.r la c.itl:i r e n l.irchivo e n e l corrtpilador de TINY.A qué dchió esto? I~ciiiuesirc LAi-"") que L(r") p.i~-espara s t ~ s búsquc<ltrs de c. ~ char.irchivo se puede coitipil..ts que i~ n~rnicriccnIfirialiceii c m ir (o amhos).ilqoier exprcsi<inscgirlar r. ( A I B I IZ)(albl lz)* c. esta versi611 del cotnpilador c. (pirr. const y c o n t i n u e case.i. g.ttividad.OI~I~I h. b. o cstahlczcn por qc16 m i existe un I)F. y lo niisnro para la conc:rtcnacidn.icia). 1)ihujc un !>FA que <'orrcspon<la1 la cupresiiin regular <I>. En la definición (le las cxpresioties regulares describirnos In prcccdencia (le las i~pcrncioties. Todas las cciden. e EJERCIMS 2.cx del compilador. En realidad.3 2.. f.. L.nos p e r m i t e u t i l i z a r d e manera directo e l a n a l i z x i o r I i x i c o generado p o r L c x . C 2.5 2. .. ) b. pero no su asoci.8 2. irirpresih de expresi6n regular global en iiiglés).6 - 2. Expliqrie por qué.\.tres: a. .2 .\ ijcrciciii aa 2. Todas las cadenas dc o y O que no contengan tres b consccoriv:is.9 .. y ilescriha sus ciinvcrici~~rics respecto a rrict:isínihi~lr~s. l.7 2. i cii. c). Por cjernplo. Al describir los tokens de un lenguaje de prograiiiación utilirantlo expresiones regulares. Muchos sistc.~\ de n y h que contcng.yy. b..: I O ~ anterior.

41 se i~ienciorió siiriplitlcación paiz la constn~ccirin Thtrmpson para la ulia de coticatenación que elimina la transiciún E entre los dos NFA de las expresiones regulares que se están concatenancio. ¿.3. y entonces considere el NFA pira rTs*.Enqué circt~tistancias preferiría iisted utilizar esta orgaiiimcióti püra irnplzrnentaciíin de código de tin l>t'i\'? 2.) 2. 2 i RASTREO O 2. I O (secci6n 2.i. lltilice In crinstrucción de Thompson para corivertir la expresión regular ( a I b) *a ( a l b l e ) en'u~i NFA.4 a los siguientes DFA: .2) en un DFA por medio de la constriicción de subconjuntos. 2.16 Aplique el algoritmo de niininiización de estado de la sección 2.13 a. b.4. b. También se mencionó que esta simplificaci6n necesitaba el hecho de que no hubiera transiciones kera del estado de aceptaciitn en los otros pasos de la construcción.3) urilizatido el c:u.15 En la sección 2.icter de entrda etimo prucba en el case extc?rnoy el c ~ t ~ d o mino prueba del cüse intcrno.12 a.1 1 Proporcione una definición matemática de la cerradura c: para un conjiinto de cstatlos tle un NFA.CAP. 2. Convierta el NFA del inciso n cn un »FA utilizando la construcción de suhcoiijuntos.10 ANAUSIS LEXICO Vitelra a escribir el pseudocitdigo para la implementación del DFA para coi~ieiitarios C en (sección 2. Compare su pseudocódigo con el del texto. Utilice la construcción de I'hompson para convenir la expresiitn regulx ( a a I b ) * ( a Ib b ) * en un NFA. Convierta el NFA del inciso a en un DFA utiliraiido la cr)nstnicción de suhconjuiitos. (Sugermcia: considere una nuevit constnicción de NFA por repetición que elimine los nuevos estados de inicio y aceptación. Proporcione un ejemplo para demostrar a q u i se debe esto. 2. 2.14 Convierta el NFA del ejemplo 2.

.

Aunque aq"éllas por lo general no son mínimas.\da al analizador léxico de TINY. (El cálculo de dirección también se analiza en el capítulo 6.CAP. . cada Iínea del código fuente se imprima al archivo del listado con el número de Iínea. han resuelto algunos problemas de eficiencia y son competitivas incluso con analizadores l6xicos manuscritos minuciosamente ajustados (Jacobson 119871). cuando la marca esté activada. También se puede encontrar ahí un análisis sobre el "lema de la extracción" y sus consecuencias para las limitaciones de las expresiones regulares en la descripción de patrones. En particular. tina breve descripción de la familia de grep de reconocedores de patrones (ejercicio 2.32 ANALISIS LEXICO Agregue comentarios estilo .) 2. se comentan en Cichelli /1'980] y Sager 119851. (Un comcntruio en Ada coinienza con dos guiones y continúa hasta el final de la Iínea.) EchoSource)al cótligo 2. La descripción de una constmcción en un paso de un DFA a partir de una expresión regular (en oposición al enfoque de dos pasos descrito aquí) se puede encontrar en Aho. La descripción original del generador de analizador Iéxico Lex se encuentra en Lesk 119751. de manera que.) Las funciones de cálculo de dirección mínimas perfectas. Versiones mhs recientes.2. Una descripción más detallada del algoritmo de minimización de estados también se puede encontrar allí. 2 1 RASTREO O 2. Una descripcicín útil de Lex también se puede hallar en Schreiner y Friedman 119851. junto con una demostración del hecho de que tales DFA son esencialmente únicas. 2.27). lJna utilidad denominada gperf se distribuye como parte del paquete de compilador Gnu. Hopcroft y Ullman [1986]. Un método para compresión de tablas en un anali~ador léxico controlado por tabla también se proporciona allí. mencionadas en la sección 2. donde se pueden encontrar algunas refirencias respecto al desarrollo histórico de la teoría. dotide se pueden hallar descripciones de algoritmos de búsqueda de tabla tales como búsqueda binüria y cálculo de dirección para reconocimiento de palabras reservadas.5. (Un comentario en Ada comienza con dos guiones y continúa hasta el final de la línea.2. especiaimente Hex (Paxson [1990]).3 (un uso del espacio en blanco para suministrar formato) se analira en Landin 119661 y 1-Iutton (19921. La regla de fuera de lugar mencionada en la secciún 2.3) se puede encontrar en Kernighan y Pike [1984]. Puede generar rápidamente funciones de cálculo de dirección perfectas incluso para conjuntos grandes de palabras reservadas.) NOTAS Y REFERENCIAS La teoría matemática tie las expresiones regulares y 10s autómatas finitos se analiza con detalle en ~ o p c r o fy ~ ~ i t n a i i t [19791.34 Agregue comentarios estilo Ada al código de Lex para el analizador Iéxico de TINY. el cual todavía es hasta cierto punto preciso para versiones más recientes. Una descripción de la construcción de Thompson por medio de convenciones bastante diferentes de NFA respecto a las descritas en este capitulo se proporciona en Sedgewick [1990]. e puede hallar una demostración de la equivalencia de los autómatas finitos y las expresiones regulares (empleamos sólo una dirección de la equivalencia en este capítulo). todavía son bastante útiles en la práctica. (Esto requiere un conocimiento más extenso de los aspectos internos de Lex de lo que se ha estudiado.33 Agregue la búsqueda de palabras reservadas en una tdhla para el anülizador léxico de Lex pare TINY (puede utilizar búsqueda lineal como en el analizador léxico de 'TINY escrito a tnano o cualquiera de los tnétodos de búsqueda sugeridos en el ejercicio 2. y un estudio más extenso en Aho (19791. Gperf se describe en Schmidt [1990].35 Agregue duplicación de Iínea de código fuente (utilizando la ~narca de Lex pard el andizador Iéxico de TINY.junto con más ejemplos de programas sintples en Lex para resolver varias tareas de coincidencia de patrones.

La estructura básica empleadaes por lo regular alguna clase de árbol. que se conoce como árbot de análisis gramatical o árbol sintáctico.6 Propiedades formales de los lenguajes libres de contexto 3. de manera similar como se determina mediante expresiones regulares la estructura Iéxica de los tokens reconocida por el analizador léxico. La sintaxis de un lenguaje de programación por lo regular se determina mediante las reglas gramaticales de una gramática libre de contexto. necesitamos estudiar la teoria de las gramáticas libres de contexto antes de abordar los algoritmos de análisis sintáctico y los detalles de los analizadores sintácticos reales que utilizan estos algoritmos. de un programa. De hecho exlsten dos categorías generales de algoritrnos: de análisis sintáctico descendente y de análisis sintáctico ascendente (por la manera - . o estructura. el análisis sintáctico involucra el tener que elegir entre varios metodos diferentes. En realidad. Por ejemplo. Con la única diferencia de que las reglas de una gramática libre de contexto son recursivas. una gramática libre de contexto utiliza convenciones para nombrar y operaciones muy similares a las correspondientes en las expresiones regulares. ya que deben utilizar llamadas recursivas o una pila de análisis sintáctico explícitamente administrada. la estructura de una sentencia if debe permitir en general que otras sentencias if estén anidadas en ella. lo que no se permite en las expresiones regulares. Por esta razón también se le conoce como análisis sintáctico.4 Ambiguedad 3.7 Sintaxis del lenguaje TlNY El análisis gramatical es la tarea de determinar la sintaxis. al contrario de lo que sucede con los analizadores léxicos.2 Gramáticas libres de contexto 3. Los algoritmos empleados para reconocer estas estructuras también difieren mucho de los algoritmos de análisis léxico.3 Árboles de análisis gramatical y árboles sintácticos abstractos 3. donde sólo existe. Como en el capitulo anterior. Las estructuras de datos utilizadas para representar la estructura sintáctica de un lenguaje ahora también deben ser recursivas en lugar de lineales (como lo son para lexemas y tokens). cada uno de los cuales tiene distintas propiedades y capacidades.5 Notaciones extendidas: EBNF y diagramas de sintaxis 3.1 3. La clase de estructuras reconocible por las gramáticas libres de contexto se incrementa de manera importante en relación con las de las expresiones regulares.Capítulo 3 Gramáticas libres de conlexlo y análisis sinláclico El proceso del análisis sintáctico 3. en esencia un método algorítmico (representado por los autómatas finitos). Sin embargo. Este cambio aparentemente elemental para el poder de la representación tiene enormes consecuencias.

si hay un carácter que no puede ser parte de u n tokeii legal. como una herramienta adicional para ahorrür espacio. se puede ver el analizador sintáctico como una función que toma como su entrada la secuencia de tokens producidos por el analizador Iéxico y que produce como su salida el &bol sintáctico secuencia de tnkens analiz. el :uiali/iidor 4niictico no sólo debe mostrar irii mensaje de error. En . Un problema más difícil de resolver para el analizador sintríctieo que para el .&bol sinrticrico La secuencia de tokens por lo regular no es un parámetro de entrada explícito. constniir un árbol de análisis gramatical o árbol sintáctico que represente esta cstructura. en cuyo caso las pasadas adicionales utilizaran el irbol sintáctico como su entrada. De este modo.en que construyen el árbol de análisis gramatical o árbol sintáctico).tr el .inálisis sinticrico (para encontrar tnntos errores conlo sea posible). ya sea de niaiiera explícita o implícita. Este árbol por lo regular se define como una estructura de datos dinámica. para obtener el siguiente token desde la entrada a medida que lo necesite durante el proceso de aiiiílisis sintictico. y. La estructura del árbol sintáctico depende en gran medida de la estructura sintáctica particular del lenguaje. no sólo por aquellos que calcula el analizador sintáctico). un compilador será de inúltiples pasadas. pero el analizador siritictico llama a un procedimiento del analizador léxico.idor sint6ctico . como g e t T o k e n . De este modo.inalizad«r Iéxico es el tratamiento de los errores. y no es necesario construir uingún árbol sintáctico explícito (las mismas etapas del analizador sintáctico representarán de manera implícita al árbol sintáctico). al generar un roken tle error. la etapa de aiialisis sintictico del compilador se reduce a una llarnada al analizador Léxico de la manera siguiente: En un compilador de una sola pasada el analizador sintáctico incorporará todas las otras fases de un compilador.) Por otra parte. EL PROCESO DEL ANALISIS SINTÁCTICO La tarea del analizador sintáctico es determinar la estructura sintáctica de un programa a partir de los tokens producidos por el analizador léxico y. El lector familiarizado con la teoría de las gramáticas libres de contexto y los árboles sintácticos puede omitir las secciones medias de este capitulo (o utilizarlas como repaso). (En cierto sentido. A menudo la estructura de nodos será un registro variante para ahorrar espacio. Por lo común. cntorices es suficientemente siniple generar un token de error y consumir el carácter problemático. sino que debe recuperarse tiel error y coniinti. En el analizador léxico. Los campos de atributo también pueden ser estructuras que se asignen de tilanera dinámica a medida que se necesite. En este capitulo describiremos de manera general el proceso de análisis sintáctico y posteriormente estudiaremos la teoría básica de las gramáticas libres de contexto. en la cual cada nodo se compone de un registro cuyos campos incluyen los atrihutos necesarios para el resto del proceso de compilación (es decir. En la sección final proporcionaremos la sintaxis del lenguaje TlNY en términos de una gramática libre de contexto. el anali~ador Iéxico transfiere la dificultad hacia c1 analizador sintictic». por consiguiente. Pospondremos un análisis detallado de estos métodos de análisis sintáctico para capítulos subsiguientes. incluyendo la generación del código. una llamada lo hará.

concatenación (dada sin un iiietasíirtbolo) y repeticiiín (dada por el inetasínibolo del asterisco). Esto no es kíeil. un ~ r n a l i ~ a i lsintácbm puede el.s reyl. conio resultado de la ii~rturulezarecursiva de las definiciones.las sinipletiiente definiciones de los iio~nbres.alo eii ~iarticul. posible.~ a sint.ri-es .is téciiictis de recupcracitin de errores ilcperideri del algoritnio de ciiirilisis sintáctico que se tinyí~ utilir. También criipleanios el signo de igualdad para representar la defit~ición u n nombre para una cxpresióii regular. rin punto al tual regresarentos en hieve.ctuor reparación de errores.ii-es conio coti~ponentcs. L a barra vertic:rl todavía apdrece coiiio el rnetasímbolo para selección. Como ejemplo de ~jecncióri utilizarertios expresiones aritméticas siinples de enteros con operaciones (le stimi. Estas eupresioties se puedeii dar mediante la gr. Sin embargo.. la regla para erp es rccrirsiv. de modo que podamos distinguirlas de los nombres para l:ts expresiones regulares). Una diferencia adicional en la riotnci6n . porque está irilplicatlo un proceso de tlefinicióti riiás coniplejo. de Las reglas gramaticales utilizan notaciones scnie. excepto que irm yrariiitica libre de coritexto i i i v o l u c r ~ reglas de recursividad.ir.j.iyn prcseittado.xprcsitrtii. se q ~ l a z a r á estiidio Insta capítulos sirhsiguientes. Los noinbres se escriben en o c~irsivas itálicas (pero ahora con una fuente diferente.rrOrcs significativos y la reaoudncicíii del análisis siiitáctico tan próxitrio al error real conlo se:.en lugar del de igualtlad para expresai.) lJn :tspccr« piirticulrir~nerite de iiiiportante de la recuperación de errores es LU exhihici6n de iiiei~sajcs t. (Esto por lo regular se hace sólo eri casos sirriples. resta y rnultiplicaciiíii. y escrihiiiios el n«iiibre en de itálicas para d i s t i n g u i r l ~ una secuencia de cmacteres rcales. e11la cual i~il'ievr re iiiia posible versiíiii de código corregida a partir de lo versión incorrecta que se le 11. la dercclin de la flecha.intes.ii~ticade ~ i r i lenguaje de programaci6n.Idvierta t. es que ahora utiliiainos el síiiibolo de la flecha .tinhih que las reglas grmsticales u t i l i m n i.i.lin las rcgliih p r : i c v p y O/J se ticncri eri I-c. cri el scrititlo qric el iiciirrbrr c ' t p npxece .2 GRAMÁTICAS LIBRES DE CONTEXTO Uiia graniitica libre de coiltexto es una especificacióri para L estructnr. L a concatenación tarnhién se utiliza corno operación estándrir. Conict l.Gramatctas Itbres de contexto 97 ocasioiies. su 3.1 Comparación respecto a la notación de una expresión regular Considerenios cónio se cornpara la gramática libre de contexto (le muestra anterior con las reglas de la expresióii regular dada por número del capítulo anterior: número = d i g i t o d í g i t o * d i g i t o = 0111213141516171819 t n las reglas de la expresión regular bisica terieiiios tres operaciones: seleccióii (tlatla por el rnetasíinbolo de 13 barra veitical). 1 Eii nuestro ejemI plo. liria cspecil'icación así es muy sirnilar a la especificacióri de la estritcctira léxica de un lenguaje utilizniido expresiones regulares. puesto que el iinalizador sintrictico piictle rio ilescnbrir uri error sitio h : ~ r ~irricho dcspu6s tic que el error reir1 haya ociirritlo. .ilitlail seis expr-csioric\ regul. no hay ningún inetasínibolo para la repetición ( c o i ~ i o * de las expresioiies el regulares). Esto se tlche :i que ahora los noinbre\ no p~ietlen ser reeriiplamdos por srrs tlefitiiciorics.iriiática siguiente: 32.

o *. De ntanera similar a la de este ejemplo. Una es el nombre número. ( y ) . La notación fue desarrollada por J«hn Backus y adaptada por Peter Naur para el informe AlgoL60. por una expresión dentro de paréntesis.l* / ( exp ) / número La primera regla define una estructura de expresión (con nombre e . En el último capítulo definimos los tokens en un analizador léxico utilizando un tipo enumerado en C. tina regla grmatical en BNF se interpreta como sigue.CAP. Se define la estructura de manera que incluya una de las selecciones en el lado derecho separada por las barras verticales. justo como si el t6ken fuera un nombre para una expresión regular (lo que por lo regular representa). En términos informales. -. 3. *. 3 1 GRAMATICAS LIBRES DE CONTEXTO Y ANALISIS SINT~UICO representando tokens en el lenguaje. el nombre de un token representando secuencias de dígitos. o bien por un número. En este capítulo. El segundo símbolo es el metasímbolo "+". para evitar entrar en detalles de la manera en que los tokens se representan en un lenguaje de impleinentación específica (como C). una regla gramatical libre de contexto en BNF se compone de tina cadena de símbolos.exp o p exp . Por ejemplo. estos símbolos por lo regular son caracteres. Las secuencias de símbolos y nombres de estructura dentro de cada selección definen el diseño de la estructura.2 Especificación de las reglas de una gramática libre de contexto Como las expresiones regulares. En el caso de expresiones regulares. .aestas conwnciones.pero es ncccsai-io:idvtrtir que no hay un estindar universal pc11. y ) compuesta por una expresión seguida por un operador y otra expresión.n tnciasímholo iIe la flecha cl incluyen " =" (cl "4" . En el caso de reglas grainaticales. Este sínibolo está seguido por una cadena de símbolos. En el caso de tokens tales como identificadores y números. En el caso en que un token sea un sínrbolo fijo. emplearemos las expresiones regulares en sí mismas para representar los tokens. La segunda tlefine nti opcrador (con nornhrc (y)) compum"t) irle uno de los síriibol«s t . o conjunto de símbolos. generalmente se dice que las reglas gramaticales en esta forma están en la forma Backus-Naur. los símbolos son generalmente tokens que representan cadenas de caracteres.2. escribiremos la cadena en sí en la fuente de código que se utilizó en el capítulo 2. un nombre para una estmctura o el metasímbolo "1". Los mctasínih~tlos convenciones que wiinos aquí son semejanres a los di' uso geiiey rnli~:ido. La regla define la estructura cuyo nombre está a la izquierda de la flecha. las reglas gramaticales estiín definidas sobre un alfabeto. como en la palabra reservada w h i l e o los símbolos especiales como + o :=. las reglas gramaticdes se usaron por primera vez en la descripción del lenguaje AlgolóO. Cinco de éstas son tokens de carácter simple: +. representaremos el alfabeto de tokens para el lenguaje TINY como el conjunto en lugar del conjituto de tokens (como \e definieron en el analizador léxico de TINY): Dado un alfabeto. De este modo. las altcrnativrts wnirines 1~1t. cada uno de los cuales es un símbolo del alfabeto. que representan más de una cadena. considere las reglas gramaticales de nuestro ejemplo anterior: exp . El primer símbolo es un mntbre para una estructura. o BNF (BackusNaur Form). F h realidad. op+t 1. utilizaremos la fuente de código en itálicas. Por ejemplo.

Gramatftas libres de ionrexto 99 sigrio de igualdtid).xp + e. '. con diferentes coiivenciones. De hecho.rp " ) " / número En esta regla los paré~itesis son necesarios para tigrupar las opciones de los operiidores entre las expresiones en el lado derecho. Por i ejemplo. ya que siempre es posible separar las partes entre p&ntesis en una nueva regla grainatical.iunque los paréntesis son íttiles para reasignar la precedencia cn Itis expresiones regulares. se puede volver : escribir las rcglas gramaticales anteriores coriio una sola regla gramatical de la rnanera siguiente: e.¿p ("+" / "-" / "*") c z p / " (" e. . En ocasiones.oii las. nuestra gramática de expresión simple podría escribirse corno se aprecia a continuaci6r1: . Por ejemplo.:" (el signo de dos puiitos) y "::=" (signo de dos puntos doble y e de 1 igualdad). conviene incluir paréntesis en los nietasínibolos de la notación BNF. De este modo. Siti etnhargo. vale la pena cirralizar de innie~liato pequeñas cuestiones adicionados les sobre la notación. es necesario distinguir los tokens de paréntesis de los riietüsíii~holos.) Los paréntesis no son absolutttinente necesarios coino nietasímbolos en BNF. De cste niodo. como hacíamos en el caso de expresiones regulares. la regla siguiente tendría un significado diferente (c incorrecto): e r p i exp "+" 1 "-" / "*" exp 1 " ( " e r p " ) " 1 número Advierta tariibién que. En archivos de texto normales i m h i é n es necesario hallar un rcerriplam para el uso de las itiilicas. . > y escribiendo los nombres de token en itálicas ~. Esto se hace í'reciientenietite encerrando los notiihres de estructura con letras rnayúscu"picoparéritesis" < . cuando se iiicloyen los paréntesis cotno rnctasínih»lo. las reglas gramaticales anteriores podría11 aparecer coino Cada autor también tendrá otras variaciones en estas notaciones. s i perniitinios que el mismo nombre aparezca cualquier número de veces a la izquierda de la flecha. la operación de selección que da el rnetasímbolo de la barra vertical tampoco es necesaria en reglas gramaticales. . Varias de las inás iniportaiiadelante en esta les (:ilguiias de las c~tales utilizareinos ocasionaltnente) se analizaran t i ~ á s sección. (Para mantener la consistencia también encerramos los síinbolos de operador entre comillas. pucsto que la concatcnacitin tiene precedencia sobre la selección (con10 en las expresiones regulares).l o que hicimos poniéndolos entre comillas.

Las reglas gramaticales libres de contexto determinan el conjunto de cadenas sintácticamente legales de símbolos de token pwa las estructuras definidas por las reglas. Una derivación es una secuencia de reemplazos de nombres de estructura por selecciones en los lados tlerechos de las reglas gramaticales.2. Por ejemplo. 3 / GRAMATICAS LIBRES DE CONTEXTO Y ANÁLISIS ~INTACTICO En ocasiones. la expresión aritmética corre5ponde a la cadena legal de tiete tokens ( número . En estos casos utilizaremos letras en mayúsculas para nombres de estructura y letras en minúsculas para símbolos de token individuales (que con frecuencia son sólo caracteres simples). porque se tiene un paréntesis izquierdo que no tiene TU correipondiente paréntesis derecho y la segunda selección en la regla gramatical para una expreTión exp requiere que los paréntesis se generen enpares. En ca&a etapa de una derivación se hace un reemplazo simple utilizando una selección de una rezla " grarnalical. nuestra gramática de expresión simple podría escribirse en esta f«mia abreviada de la manera que sigue En ocasiones también sirnplificürernos la notación cuando estemos utilizando solamente caracteres como tokens y los estemos escribiendo sin utilizar una fuente de código: 3. por siinplicidad. &renios ejemplos de reglas gramaticales en una notacicín abreviada. Las reglas gramaticales determinan las cadenas legales de sínlbolos de token por medio de derivaciones. o conjunto de caden~s legales de tokens. .3 Derivaciones y e lenguaje definido por una gramática l Ahora volvamos a la descripción de cómo las reglas gramaticales determin~nun "lenguaje". De este modo. la cadena (34-3*42 no e i una expresión legal. Una derivaciún comienza con un nombre de estructura simple y termina con una cadena i e símbolos de token.número ) * número donde los tokens de número tienen sus estructuras determinadas por el analizador léxico y la cadena misma es legalmente una expresión porque cada parte corresponde a selecciones tleterminadas por las reglas gramaticales exp 4 exp op rxp op++l-l* / ( exp ) / número Por otra parte.CAP.

1 proporciona una deriviiciiin para la eupresi611 ( 3 4 . así conlo para indicar repeiici6n en expresiones regulares.irntrs la ]>alahra"lenguaje" c ~dos witidric dilerente. y el Icrigu. En el primer paso cie la figura 3.xp z.xp o p número. mientras que los psosúe derivación construyen mediante reemplazo.1.3 ) * 4 2 utilizando las reglas grainatic:iles como ?e dan en nitestra expresióii gramatical sirtiple.xp del extremo derecho en la cadena eup o p u p se reemplaza por el símbolo número del lado derecho de la selecci6n urp -+ número para obtener la cadena e. el lenguaje definido por op en nuestra griimiírica de expresión simple define el Icrigu:rje ( c . y los símbolos ** representan una derivaci6» compuesta de una secuencia de reemplazos como se describieron anteriormetite.?rtrrttci. En ese paso el op (operador) se reemplaza por el sÍmh«lO * del lado derecho de la regla o[?-+ * (la tercera de las selecciones en la BNF para o p ) para obtener la cadena evp * número. Cada nombre de estructura en una gramiítica define su propio lenguaje de cadenas sin~ácticamente legaies de tokciis. La grarnitica pra un 1engu:tje de progromacicín i menudo define una cstructiira denominada pro.) Las reglas gramaticales en ocasiones se conoceit corno producciones porque "producen" las cadenas en LfG) niediaiite derivaciones.número] .(T. Podernos escribir esto de niaiiera siiilb6lica como donde C representa la gritmática de expresión. E l conjunto de todas las cadenas de símbolos de token obtenido por derivaciones del símbolo exp es el lenguaje definido por la gramática de expresiones.). Y así sucesivamente.número) * número =+ (numero . (El asterisco se utiliza para indicar una secuencia de pasos.il u t i l i m k i para el 1-cemplaro.) [ r i p -+ f7 i p op vi1 Una derivatian para la expresión aritmética (34-3)*42 (2) * exp o() número /erp . la r x p simple se reemplaza por la cadena rwp o p e .Gramáticas l~bresde contexto 101 Como ejemplo. Por lo regular estamos mis interesiidos en el lenguaje definido por la cstriictrira mis general en i una gramática.número] i [op -+ (3) (4) * q r * número = 1 *1 ( ( uxp ) * número [u\[) -+ [erp otp ) l (6) (7) (81 * ( e i p op número) * número * ((erp .~quíutili~. Este lenguaje coiitiene todas las expresiones sintácticamente legales.imbién numer:inios los pasos para unir refereiicia ~~ostcririr. Advierta que los pasos de derivación utilizan una flecha diferente al rnetasítiibolo de flecha que se emplea en las reglas gramaticales.~ que . * J compuesto sólo tle tres sirriholns. Esto se debe a que existe una diferencia entre un paso de derivacih y una regla gramatical: las reglas gramaticales definen.número)*número - número] [op -+ -1 [ekp . 1. En cada paso se proporciona a la dereclia la sclecci6n dc la i-eglzi gt-amatic.i figura 3.?je de esta estructura cs el ccrtrjunto tle todos los progr:trnas sintácticamente lefalcs tíel lenguaje llc prog~iniitci6ri (atlvicrt. -. ~ p lado derecho de la regla e.. Por ejemplo. s representa iina cadena arbitraria de símbolos de token (en ocasiories dentminada sentencia). la e. i .p -ter/? del op exp (la prirnerti selecci6n en la BNF para rxp). En el segurido paso.

además.1 Considere la gramática G con la regla gramatical simple Esta gramática tiene un no terminal E y tres terminales (. bloque-progrumu -+ . GRANATICAS LIBRES DE CONTEXTO Y ANALISIS SINT~CTICO Por ejemplo. los que. seguido por un punto. a menos que lo especifiquenios de otra manera.2 C o n d e r e la gramática G con la regla gramatical iimple Esta resulta ser igual que la gramática del ejemplo anterior. porque por lo regular son tokens en aplicaciones de compilador). las cadenas compuestas de O o más paréntesis izquierdos. En realidad. ((a)). como C.) = ((" u)" I n un entero > O ] . La causa es que cualquier derivación que comienza con E genera cadenas que siempre contienen tina E. A menudo. Como los temiinales por lo regular son tokens en aplicaciones de compilador. . una regla grarn. (En L teoría matemática de las gramáticas libres de contexto esta estructura se a denomina símbolo inicial.qr(lmu . un RNF para Pascal c o n i e n ~ ~con reglas graniat~cales rá tales como programa -+ encabezarlo-pro. seguidos por una a. no hay manera de que podanios derivar una cadena conipuestn s d o de tcrtninales. porque siempre se debe reemplazar de más en una derivación (no terminan una derivación). corno ocurre con todos los prtrccsos recursivos (como las iler~iostr:icioiies intlucción o las Suncioncs recursivas). Consideremos ahora algunos ejemplos de lenguajes generados por gramáticas..) En lenguajes con compilación separada. seguido por un signo de punto y coma. . . el lenguaje L(G) = (u. seguida por el mismo número de paréntesis derechos que de paréntesis izquierdos.) Otro segmento de terminología nos permite distinguir con más claridad entre los nombres de estructura y los símbolos del alfabeto (los cuales hemos estado llamando símbolos de token. utilizarán ambos nombres. son sinónimos. seguido por un bloque de programa. . Esta gramática no genera ninguna cadena.(u). Ejemplo 3. es decir.itic:il que define por . sólo que se perdió la opción E (1.(((a))). . de modo que su lenguaje es vacío: L(G) = { ). . Como ejemplo de una derivación para una de estas cadenas ofrecemos una derivación para ((a)): Ejemplo 3.. .CP 3 1 A. los símbolos en el alfabeto se denominan tenninaies. (La primera regla dice que un programa se compone de un encabezado de programa. Los nombres de estructuras también se conocen como no terminales. suponemos que la estructura más general se menciona primero en las reglas gramaticales. . bloque-progrurna encubezudo-programa -+ . En todo caso. en esencia. Así. . Genera. En contraste. porque éstos terminan una derivación. se hace referencia tanto a los terminales corno a los no terminales como símbolos. la estructura más general se conoce a menudo como una uniúud de compilacidn.) y u.

suponga la veracidad de la hipótesis para todas Ias cadenas con derivaciones de Iongitiid 11 . La regla grainatical de este ejemplo rio lo tiene... Si In derivación tiene longitud 1. De este modo. 5 Ejemplo 3.senrencia else sentencia / / etp Oj 1 El Icnguaje de esta gramática se compone de sentencias i f anidadas de nianera semejante iil Icirg~iqje (Siniplificatnos Iiis expresiones de prueba lógica ya sea .1.S misma debe tener esta misma forma. L derivación E * íi muestra que (1 está en Lfí. así que .S es de la forrna correcla. mostrainos que toda cadena (1 t i i . entonces es de la hrrna E * 11.srnt-$-+ if ( erp ) . está en L. considere el efecto de la regla E -+ E -t (1: esto provoca que In cadena -t(1 se repita sobre la derecha en una derivación: Finalmente. tamhién mostrantos que cualquier cadena . Mostrainos esto mediante intlucción sobre la longitud de una derivación.3 Consicfere la graniitica (? con la regla grantatical simple Esta grainática genera todas las catlenas compuestas de tr separarlas por signos de "tnis" ( t ): Para ver esto (de nianera informal). 9 Ejemplo 3.. la derivación E = . con n a. y iigrupatrios C. O o a 1. existe una derivación E ** . En primer i lugar. (1. . y de este n i d o será de la + + + + + + + + + Iorrnatr + n + . Esta tierivnciítn debe code menzar con el reemplazo de E por E ti.) Ejemplos tic cadenas cn cstc Icngui~je soii otro if ( O ) o t r o i f (1) o t r o . y cciiilqnier tleriv:ición potencial csti condenada a tina recursiítn infinita. Por lo tanto.Gramaticas libres de contexto 1 03 trnit cstrnctura de iriunera recursiva sienipre debe tener por lo inenos un caso no recursivo (al que podríamos tlenoriiiiiar caso base).Y' + a = s. Entonces.1.Sde L(G) debe ser de la forma a t ir t .. esti en L(G).~.):suponga ahora que s = 11 rr . con n -. n está en L(G) por inducción en el núrnea U ro de IUS. i Lod:is I:LS otras scntcrici. aparte cle las scntcncins if en cl tcrininal otro. y sea E ** s tina deriv~ci6n loiigitud n > l.(G). y de esta manera será de la forma E = E t t z =* 1 .tr* s A la inversa.I (1.E -E ri..S: ahora + ( muesli'a que la cadena s 1 u. S' tiene una clerivación de longitud n ... Podernos demostrtir esto r i i i s fortiialniente mediante inducci6n corno sigue. Ahora..sentenrict if ( e. dehemos reemplazar la E a la izquierda utilizando el criso base E -+ a.rp ) .. (1. + rr.4 Considere In siguiente gramática muy sirnplificada de sentencias: senrenc icr -+ seni-if otro .

. Por ejemplo. tanto la regla gramatical corno la regla gramatical generan el lenguaje {u" I n un entero Z I ) (el conjunto de todas las cadenas con una » más u). puesto que la repericióti se puede obtener por medio de recursión (como los programadores en lenguajes funciortales lo saben). pero no tienen equivalente especíllco de la operación de repetición para el * de las expresiones regulares. seguida por O o nlás a).CAP. De manera similar. Una operación así de hecho es innecesaria. 3 1 GRAMATICAS LIBRES DE (ONTEKIO Y if if if if if (O) ANALISIS 1lflTbCTlC0 (1) (O) (O) (1) otro else otro otro else otro if ( O ) o t r o if (1) o t r o e l s e o t r o o t r o e l s e if ( O ) o t r o e l s e o t r o Advierta cómo la parte else opcional de la sentencia if está indicada por medio de una sclección separada en la regla gramatical para smt-$ 5 Antes advertimos que se conservan las reglas gramaticales en BNF para concatenación y selección. . paa.3 es otro ejemplo de una regla gramatical recursiva por la izquierda. el cual es igual a1 que genera la expresidn regula a+. esta regla gramatical es equivalente en su efecto a la expresitín regular Bol". Bol. Considere una regla de la h n i a donde a y p representan cadenas arbitrarias y P no comienza con A. que ocasiona la repetición de la cadena "+ o". paaa. la cadena untu puede ser generada por la primera regla gramatical con la derivación Una derjvacidn similar funciona para la segunda regla gramatical. Éste y el ejemplo anterior se puede geueralizar como sigue.Por ejemplo. porque el noterminal /l. . la regla gramatical recursiva por la derecha . La primera de estas reglas gramaticales es recursiva por la izquierda. De este niodo. El ejemplo 3. aparece como el primer símbolo en el lado derecho cie la -&la que define A.' La segunda regla gramatical es recursiva por la derecha. Esta regla genera todas las cadenas de la forma p. (todas las cadenas comienzan con una p.

Concluiremos esta s~ibseccitín algurm otros <jcn~plos con mis. entonces debemos Lener una ~iotaciónpara una rcgla gramatical que genere la catleila vacía ipwque la expresii6~egular incluye la cadena vacía). ya sea como Ambas gramaticas generan el lenguaje ((1" I 1 1 u n entero 2 O ] = Lía*).jernplo. c«mo pronto verernos.5 Considere la gramitica Esta gramática genera las c.4 se puede escribir de la siguiente rn. Ahora podemos escribir tina grariiitica que sea equivalente a la expresión regular a*. con10 ocurre en pero eniplearemos más a riienrtd« el rnetasímholo épsilon para la cadena vacía (corno se uscí en las expresioiies regulares): Una regla grarriatical de esva clase se conoce como producci6n E (una "protiuccii>n épsiIon"). Ejemplo 3.adenas de todos los "paréntesis balancettttos".Grambticas lhbrer de contexto 105 Si queremos escribir inia grariiitica que genere el mimo lenguaje qtie la expresih regular a*. Por e. Las pr«(iuccictnes E también sori útiles :i1 definir cstntcturas que son opcionales.incra alternaiiva iitilimido una producciiin E: .icío. la cadena (( )(( )))( ) es genertiila por la derivaci6n siguierite (la prnducci6n 8 se utiliza para hacer qiie desaparezca A cu:indo sea necesario): Ejemplo 3. Poilernos sirnplernente iio escribir nada en el lado <lereclio. tina gramática que genera u n lenguaje que contenga la cadena vacía dehe tener por lo menos una proclt~ccií.6 La grarnitica de sentencia del ejemplo 3.ne. Una rcgla gramatical así a* tlebe tener un Iado derecho \ .

ii para d a caderia se proporciona cit la figiirn 3.3 ÁRBOLES DE ANÁLISIS GRAMATICAL Y ARBOLES SINTACTICOS ABSTRACTOS 331 Árboles de análisis gramatical Una derivación proporciona un método para construir una cadena particular de terminales a pxtir de un no terminal inicial. existen muchas derivaciones püra la misma cadena. pero también mantener el signo de punto y coma como un separador de sentencia.7 CAP. Por ejemplo. secnenciu-sent srnt -+ s /8 pero esto coiivierte al aigno de punto y coma en un terminador de sentencia en vez de en un separador de sentencia: Si deseamos perntitir que las secuencias de sentencia sean vacías. La iinica cfifereiicia . Pero las derivaciones no sólo representan la cstructiira de Las cailerias que construyen. En generd.2.1.1 O6 Ejemplo 3. para una secuencia de sentencias: Esta grriinática genera secuencias de una o más sentencias separadas por signos de plinto y conia (las sentencias se extrajeron del terminal simple S : ) Si queremos permitir que las secuencias de sentencia también sean vacías. 3 1 GRAMÁTICAS LIBRES DE CONTEXTO Y ANAUSIS IINTACIICO Considere la siguiente gramática C. constmyantos la cadena de tokeiis ( ii número - número ) * número partir de nuestra gramática de euprcsih simple utilimido la rlcrivación de la figura 3. llrru wgtinth ilerivacií. podríamos escrihir la siguiente gramática G": secuencia-sent -+ serit . debemos escribir la graniática como se muestra a continuación: Este ejemplo muestra que debe tenerse cuidado en la ubicación de la producción 8 cuando se construyan estructuras opcionales. $ 1.

Parü &ir un ejemplo sinrple.. [ e t p -+ número] (7) (8) = . Árboles d análisis gramatical y arboles sintácticos abstractos e entre las (los dcrivaci<~iies el orden en el cual se surriiriistrün los reeiiiplazos. los nodos hoja esdel tán etiquetxkos por teriiiiiiales y los hijos (le cada ticiclo interno represent:rti el ree~iiplüio no terriiirial asociado c'n un paso de la deriv.i < ~..3 ) * A 2 (O) -=. ~igurn 1 3 Otra derivacion para la expreston < 3 4 .número) * r r p l r t p -+ número] [or.ición niieittras se factorizan las dikrenci.: . (número . * (número. .* ] . y 6st.t« es i i i ~ a estriicturli de irhol.ición.t es es de hecho una dilerencia superficid.número) * número l l n Brhol de análisis gramatical correspoiictiei~le una deriviiciiíri es un á r h d etique21 tado en el cual los nodos interiores estiii etiquetados por t i « teriiiinales. Püra aclarar esto necesitamos una reytrcscntacióri para 1.rs skipcrficinlcs de orden.. L a representación que hace e. la decivüción corresponde al lirbtil de análisis grrmatical número + número .i estructura de tina cadena de terminales que abstraiga las carücterí&xs escriciales tle una <leriv.número) op a p (número . y se conoce coino árbol de m í l i s i s grí~naticiil.

se pueden distinguir derivaciones particul:ires que están asociadas de manera única con el árbol de .) En este caso la numeración es la inversa de una numeración postorden de los nodos internos del árbol de análisis gramatical. Este mismo árbol de análisis gramatical también corresponde a las derivaciones exp d exp op exp -=. 1 . Sin emhar_go.que en conjunto representan la niisrna estructura básica para la cadena de terminales analizada grainaticalrnente.uiálisis gramatical. 2. Una derivación por la izquierda cs acluclla cn la cual se reemplaza cl no termiiral niás a la izquicrcki en cada p. (Un recorrido postorden visitada los nodos internos en el orden 4.) Un árbol de análisis gramatical corresponde en general a muchas deriv:~ciones. exp d exp o p exp * exp + exp a número + exp * número + número pero se aplicarían diferentes numeraciones de los nodos internos.CAP.rso cn la deriva- . exp o p número d exp + número = número + número . 1 I GRAMATICAS LIBRES DE CONTfKlO Y ANALISIS IIHTÁCIICO número 1 + I número l Advieria que esta numeración de los nodos internos del árbol de análisis gramatical es en realidad una numeración preorden. 3. En realidad la primera de estas dos derivaciones corresponde a la numeración siguiente: número t número (Dejamos al lector construir la numeración de la otra.

Para ver esto. regresemos a la expresión ( 3 4 .3 ) *42 y las derivaciones que dimos en las figuras 3.1.3.2. Ésta es de hecho una derivación por la derecha. (La tercera derivación no es por la izquierda ni por la derecha. y la numeración correspondiente del árbol de anlílisis gramatical es una numerüción postorden inversa.3.2. Sin embargo. vimos esta correspondencia en las tres derivaciones y el irbol de análisis gramatical del ejemplo más reciente. Una derivación por la izquierda corresponde a la numeración preorden de los nodos internos de su árbol de análisis gramatical asociado.) Figura 3.2 Arboles sintácticos abstractos Un árbol de análisis gramatical es una representación útil de la estructura de una cadena de tokens. (Invitamos al lector a proporeionar una nuineraci6n preorden del árbol de análisis gramatical conespondiente a esta derivación. En realidad. ya que los tokens aparecen conlo las hojas del arbol de análisis gramatical (de izquierda a derecha) y los nodos internos del árbol de anáiisis gramatical representan los pasos en una derivación (en algún orden). La derivación de la figura 3. considere el árbol de análisis granlatical para la expresión 3 + 4 de acuerdo con nuestra gramática de expresión siitiple: .1 y 3.3 Arbal de análisis gramatical para la expresión aritmética (34-3)*42 número - número 3. mientras que la segunda es una derivación por la derecha. un árbol de análisis gramatical contiene mucha información más de la que es absolutamente necesaria para que un coinpilador produzca código ejecutable. Por consiguiente.Árboles de analtris gramat~caly árboles rintácticos abstrattor 1 09 cióri.) Como un ejemplo más complejo de un árbol de análisis gramatical y de las derivaciones por la izquierda y por la derecha. es una derivación por la izquierda. La primera de las tres derivaciones que dimos es una derivación por la izquierda. una derivaciún por la derecha es aquella en la cual el no terniinal más a la derecha se reemplaza en cada paso de la derivación. El árbol de análisis gramatical para esta expresión está dado en la figura 3. mientras que una derivación por la derecha corresponde a una numeración postorden en reversa. por otra parte. donde también nunieramos los nodos de acuerdo con la derivación de la figura 3.

la sintaxis abstracta para la expresión 3+4 debe escribirse coino OpE:.3 ) * 4 2 se puede cscribir como .Ci~tzstE. existe una manera mucho más simple de representar esta misma información. el nodo raíz simplemente se etiqueta por la operación que representa. representa el valor de su hijo n ú m e r o único. El principio de la traducción dirigida por sintaxis establece que el significado. Aunientamos el árbol para mostrar el valor numérico real de cada uno de los tokens n ú m e r o (éste es un atributo del token que se calcula mediante el analhdor léxico o por medio del analizador granlatical). por otro lado. Cl)ri. Un analizador sintáctico recorrerá todos los pasos representados por un árbol de análisis gramatical. Sin embargo. Cada uno de estos subárboles.3. 1 1 GRAMATICAS LIBRES DE CONTEXTO Y ANÁLISIS IINTACIICO Éste es el árbol de análisis gramatical de un ejemplo anterior. a saber. mediante el hrbol Aquí. se puede representar en forma más simple por medio del árbol En este árbol los tokens de parentesis en realidad han desaparecido.r~)~4)) y la sintaxis abstracta para la cxpresih ( 3 4 . aunque todavía representan precisamente el contenido semántico de restar 3 de 34. o seniántica. para después multiplicarlo por 42. Los árboles sintácticos abstractos pueden imaginarse como una representación de irbol de una notación abreviada denominada sintaxis abstracta. y los nodos hoja se etiquetan mediante sus valores (en lugar de los tokens número).tp hijos.rp(Pll~s. En realidad. cuyo árbol de análisis gramatical está dado en la figura 3. la expresión ( 3 4 .srExp(:3). En este caso el principio de la traducción dirigida por inedio de sintaxis significa que el árbol de análisis gramatical debería presuponer que el valor 3 y el valor 4 se van a sumar. Tales árboles representan abstracciones de las secuencias de token del código fuente real. del mismo modo que un árbol de análisis gramatical es una representación para la estructura de la sintaxis ordiriaria (que también se denomina sintaxis concreta cuando se le compara con la abstracta). y las secuencias de tokens no se pueden recobrar a partir de ellas (a diferencia de Los árboles de análisis gramatical). No obstante. Por qjemplo. podemos ver que el árbol da a entender esto de la siguiente manera.CAP. Tales árboles se conocen como árboles sintiicticos abstractos. de la cadena 3 + 4 deberka relacionarse directamente con su estrticlura sintáctica como se representa mediante el árbol de análisis gramatical.3 ) * 4 2 . pero por lo regular sólo constmirz un árbol sintáctico abstracto (o su equivalente). contienen toda la información necesaria para traducir de una forma más eficiente que los árboles de análisis gramatical. o para abreviar árboles sintácticos. De manera similar. La raíz representa la operación de suma de los vdorks de los dos subirboles e.

Nuestro priricip:il interés r. OpKind op. así coriio pnrn las operaciones (suiita. potlríaiiios escrihir las siguientes reglas tipo B N F para la sintaxis íibstract:~tlc nuestras exprcsiories arittit6ticas hiniples cotirrt N o investig. tales coino tipo de datos.iiiiitica pnr:i las sentencias il siitiplii'icadas del ejemplo 3. Ejemplo 3.tn.' Por ejemplo.Minus.ConstKindl ExpKind. resta. y irrultiplicaci6n) riiisiria~. hecho.idor hiiitáctico. observa~iios que estas trcs declarüciorres de nodus sólo incluyen los atributos clue son ilirectarricnte necesarios para este ejeriiplo. typedef STreeNode *SyntaxTree.iblenientc risarí:iiiios los tokcns p i r a represcncar llts De operaciones. los árboles siiiticticos ahstnictos para r ~ u e s t r .8 Corisidcre la gr.3: .trelrios iiiás 31 respecto. typedef struct streenode I ExpKind kind. priesio que un nodo ito puede scr al rriis~rio un nodo de operador y un notlo de const.iirtli) una n o t a c i h scriie. L cii:tI se detei-minará por ~ i i c t l i o a de una declaración tle tipo de datos.Arbales d anál~risgramat~taly arboles smtactttor ~bstractos e 111 EII realidad.intc. Advierta que enrpleamos tipos enuinerndos para las dos diferentes c1. C«ncloiirr«s esta sección con v x i o s ejeiitplos de irboles de análisis gramatical y árholes sintácticos utilizcindo gmiri6tic.c~ ~ r ~ s i o r i e s ~p aritinétic:~~ si~iiples pueden ser tletcr~nin.inicnde te. <le1rnistiro tirudo que la i i i t a x i s concreta. infor~ii.i<los ~iicdiiinte tlcclarncioIss iics de tipo de tlnros cn C Times) OpKind.icióiidc I a h l i ~ sínitmlos y :rsí iiccsiv. En situacioires prácticas habrá iiiuchos tilis catripos para los atributos de tiempo de corripilnción. int val.as). Fiiialtiiente. como se dqjari claro con los ejeiiiplos que se piiiserrtíirán triás adelante en este cnpítulo y en eapít~los subsig~iientes. typedef enum {OpKind. m i s que definir un nuevo Lipo enunierado.idica en la estructura (le árbol siiitictico 1-ea1que utilirará el .iliz. Por ejc~iiplo.*rchild.ises de nodos de *bol sinliclico (opc~tciones constantes ciitct. prob. TaitihiCn podríairios haber usíido u11 ticrirpo tipo union dc C p i r a nliorrar espacio. struct streenode *lchild.aitte a la HNF. typedef enum {~lus. 13 hiniaxis abstracta ptiedc proporcioiiar una i l e f i i i i c i h lortiial uti1iz.ts que ya consideratrios en ejemplos :iiiteriores. } STreeNode.

la parte de acción (parte "then") y la parte alternativa (parte "else"). De este modo.6. excepto las tres estructuras subordinadas de la sentencia if: la expresi6n de pnieba. un árbol sintáctico para la catlena anterior (usando la gramática del ejemplo 3.El árbol de análisis gramatical para la cadena if ( O ) otro else o t r o es coino se presenta a continuación: o Al utilizar la gramática del ejemplo 3. otro otro sentencia -t sent-if 1 otro sent-if -t if ( exp ) ~entencia parte-else parte-&e -t else sentencia / F exp -t O / 1 esta misma cadena tiene el siguiente árbol de andisis gramatical. otro Un árbol sintáctico abstracto apropiado para las sentencias if desecharía todo.6) sería: o otro otro .4 o la del ejemplo 3.

do.StmtK} NodeKind.9 Consiclere la gratniitica dc una secnencia de sentencias separadas por signos de pritito y ct~tna tiel ejempl» 3.One} ExpKind. Esto se efectuaría inás apropiadan~ente rili tipo enuirrer.*thenpart. s ticrre e1 siguiente i r b o l de m í l i s i s gritniatical respecto .Arboles de analir~sgramatical y arboles sintacticos abstractos 113 Aquí empleainos los ~okens rcslairtes if y otro como cticluetos para distinguir la clssc de1 ei~rpleantlo noclo de senteilcia eii el i r b o l Siiitiictico. typedef STreeNode * SyntaxTree. typedef enum (Zero.7: La cadeila S. typedef struct streenode {NodeKind kind.'1 la csI) i~mctiira las seritcnciss y cxprcsiones en este cjerirplo sería coirio cI que se iiiueslra Y CI de tintlaci6n: S typedef enum {ExpK. Por ejciliplo. s.*elsepart. Ejemplo 3. StmtKind skind: struct streenode *test. u n conjunio de tieclaraciones en C' : r p r q h i o p.OtherK} StmtKind. esta gramática: I U n posible árbol sintáctico para csta misma cadena es .11. STreeNode. typedef enum (IfK. ExpKind ekind.

salvo por la diferencia de que éstos s6lo "funcionan" 3 unirse entre sí las sentencias en una secitencia. los que se denominan vínculos hermanos para distinguirlos de los vínculos padre-hijo. es la representación más sencilla y más fácil para una secuencia de cosas en un hbol sintáctico. Desgraciadamente. El árbol anterior ahora se convierte en el arreglo de hijo de extrema izquierda y herrnanos a la derecha: Con este arreglo también se puede eliminar el nodo seq de c«nexi<ín. y efectúan derivaciones por la izquierda y por la derecha. corno es evidente.y ciitonces el árbol sintáctico se convierte sitriplemente en: Ésta. de modo que el árbol sintáctico anterior se convertiría en El problema con esto es que un nodo seq puede tener un número arbitrario de hijos y es difícil tomar precauciones al respecto en una declaración de tipo de datos. la gramática de la aritmética entera simple que hemos estado ~itilizando como ejemplo estánctar . (i 34 AMBIGUEDAD 3.1 Gramáticas ambiguas Los árboles de anhlisis graniatical y los árboles sintácticos expresan de manera única la estructura de la sintaxis. La complicación es que aquí los vínculos son vínculos hermanos que se deben distinguir de los vínculos hijos. En su lugar podríamos intentar unir todos 1 los nodos de sentencia en una secuencia con solamente un nodo. y eso requiere u11 nuevo campo en la declaración del irbol sititáctico. por ejemplo. Considere. pero no Jerivaciunes en general. La soluciih es utilizar la representación eseándar con hijo de extrema izquierda y hermanos a la derecha para un árbol (esto se presenta en la mayoría de los textos sohre estructuras de datos). En esta representación el único vínculo físico desde el padre hasta sus hijos es hacia el hijo de la extrema izquierda. una gramática puede permitir que una cadena tenga más de un árbol de análisis gramatical.4. Los hijos entonces se vinculan entre sí de izquierda a derecha en tina lista de vínculos estándar.En este árbol los nodos de punto y coma son semejantes a los nodos de operador (como los nodos + de expresiones ~rritméticas).

número * número Y ((JP + [erp - -1 número [op -+ *j [ekp -+ número .rp número l i número l corresporitlierites ii las dos ~Ierivacio~ies la izquierda por [ekp -+ erp O[) O[> l>l/J 1 l'i/) 1 [etp l't/J [e\/> número 1 + * numero .número * eip 1 3 número .número op etp = número .erp op erp * número .p .Ambigüedad < .V/J iip <'.J/I ?ti? * número número - número número - ~.

Los árboles sintácticos asociitdos son

Una gramática que genera una cadena con dos árboles de análisis gramatical distintos se denomina gramática ambigua. Una gramática de esta clase representa un serio problema para un analimdor sintáctico, ya que n o especifica con precisicín la estructura sintáctica de un programa (aun cuaudo las mismas cadenas legales, es decir, los miembros del lenguaje de 1:) gramática, estdn completamente determinados). En cierto sentido, una gramática ambigua es como un autómata no determinístico en el que dos rutas por separado pueden aceptar la misma cadena. Sin embargo, la ambigüedad en las gramáticas no se puede eliminar tan fácilmente como el no determinismo en los autómatas finitos, puesto que no hay algorit'tno püra hiicerlo así, a diferencia de la situación en el caso de los autómatas (la construcción del subconjunto analizada en el capítulo anterior).' Una gramática ambigua debe, por lo tanto, considerarse como una especificación incompleta de la sintaxis de un lenguaje, y como tal debería evitarse. Afortunadamente las gramáticas ambiguas nunca pasan las pruebas que presentamos más adelante para los algoritmos estándar de análisis sintáctico, y existe un conjunto de técnicas estándar para tratar con ambigüedades típicas que surgen en lenguajes de programación. Para tratar con las ambigüedades se utilizan dos métodos básicos. Uno consiste en establecer una regla que especifique en cada caso ambiguo cuál de los árboles de análisis gramatical (o árboles sintácticos) es el correcto. Una regla de esta clase se conoce como regla de no ambigüedad o de eliminación de ambigüedades. La utilidad de una regla de esta naturaleza es que corrige la ambigüedad sin moúitlcar (con la posible complicación que es'to implica) la gramática. La desventaja es que ya no sGlo L gramática determina la estnica -tura sintáctica del lenguaje. La alternativa es cambiar la gramática a una forma que obligue a construir el árbol de análisis gramatical correcto, de tal manera que se elimine la ambigüedad. Naturalmente, en cualquier método primero debemos decidir cuál de los árboles es el correcto en un caso ambiguo, Esto involucra de nueva cuenta al principio de la traduccicín dirigida por sinraiis. El árbol de análisis granmtical (o árbol sinticrico) que buscamos es aquel que refleja correctamente el significado posterior que aplicaremos a la construcción a iIti de tratfucirlo a código objeto.

Ambigüedad

;,Cuál de los dos ái-boles sintácticos antes preseritados reprcsenian Iii iiitcrpret;icih coirbi)l.i~idica, hacer el nodo de resta un h i j o del al rrecta de l a ca&na 3 4 - 3 * 4 2 ? E! pri~irer nodo de niultiplicacicín, epc teneii~os intencicín de ev~tluar expresióii ev;ilriando priinela la ro la resta (34 -- 3 = 3 1) y despnés Iii ~rtultiplicecicín 3 I " 1 2 = 130¿). E l segundo irhol. ( por otra parte, iriclica que primero se realimrá la rirultiplicación ( 3 '* 42 = 126) y después la -92). L a cr~estiónde cuál árbol elegireinos depende de cuál de estos resr;i (34 - 126 cálculos visualicenios comtt correel». L a convenciJii iiiateiixítica estáirdar dicta tgue la segtincla interpretación es la correcta. Esto se eiebe 21 que se dice que la tnultiplicación tiene precedencia sohre la resta Por l o regular. tanto la inultiplicaci~ri como la divisicín tienen nreccdencia tanto sobre L suma coiiio sobre la resta. a Para eliminar la ainbigüediid dada en nuestra gramática de e x p r e s i h simple, ahora pctdríuinos simpleiriciite establecer una regla de eliininacicín tle ambigüedad que establezca [.a las precetleiicias relativas de las tres t~peraciories-representacias. solución estáiidar es darle a la suriia y a la resta 1;i niisrria precedeiicia, y proporcionar a lo niultiplicacicín tina precedencia niás alta. Desgraciatlarnente, esta regla aún no elimina por coimpleto la n~nbigüedacf la grairiáticle ca. Consiclere la cadena 3 4 - 3 - 4 2 . L a cadena también tiene tios posibles :írh«les sinticticos:

-

E l primero representa el cálculo (34 - 3) - 42 = -- 1 1 , mientras el segundo representa el cálculo 34 - (3 - 43) = 73. De nueva cuenta, la cuestibn de cuzíl cálculo es el correcto es un asunto de convencicín. La rnaternática estándar clicta que la primera selección es la e«rrecra. Esto se debe a que la resta se consiclera conio asociativa por la izquierda; es decir, que se realizan una serie de operaciones (le resta de izquierda a tlerechti. De este nioclo. una rtrttbigiiedacl adicioiial que reqiiicre de ttria regla tle eliniin;icitiri tle ~inibigüedades la asociatividad de cada ima de las opernciows de suma, resta y ~ i i u l ~ i p l i es w Escacicín. Es coniún especificar que estas tres opemcio~ies n asoci;ttivas por la i~cpierda. to en i.cülitlad elimina las nii~bigiicclades rcsmites cn nuestra grtii~iáticii exprcsidir siniple de esto (;iiinye no podrcrnos ~leniostr~rr zino h;i\ta rnás ;tdCl;mte). T m h i é n se puede clcgir especificar queuna opcriicirín c s no ;rsociativ:i. CI cl sctititlo de c ~ u ~ ~ n ; ~ c e ~ dee~n c sa u11opcri~clores iiri;i c x p r e s i h que no está permitida. Por ~ n á ide cje~iiplo, podríairros haber escrico ni~cscr;igr;rill:ítica tic euprcsitiri siiriplc de I;i iii;~iicr:iq11c \e i ~ i i i c s ~ ;a ri c011titru;tci6n:

CAP. 3 1 GRANATICAS LIBRES DE CONTEXTO Y ANÁLISIS SINTACTICO

ewp -+ fncror op tu'cictor jactar fuctor -t ( erp ) / número op++ 1 1 *

1

-

En este caso cadenas como 3 4 - 3 - 4 2 e incluso 3 4 - 3 * 4 2 son ilegales, y en cambio se tiehen escribir con p~réntesis,tal como ( 3 4 - 3 ) -42 y 34- ( 3 * 4 2 ) . Estas expresiones completaniente entre paréntesis no necesitan de laespecilicación de asociatividüd o, en realidad' de precedencia. La gramática anterior carece de ambigüedad como está escrita. Naturalmente, no sólo cambiamos la gramática, sino que tamhién cambiamos el lenguaje que se está reconociendo. Ahora volveremos a métodos para volver a escribir la gramática a Sin de eliminar la ambigüedad en vez de establecer reglas de no mhigiiedad. Advierta que debernos hallar métodos que no modifiquen las cadenas hásicas que se están reconociendo (como se hizo en el ejemplo de las expresiones completamente entre paréntesis).

3.42 Precedencia y asociatividad
Para manejar la precedencia de las operaciones en la gratnritica debemos agrupar los opera- . dores en grupos de igual precedencia, y para cada precedencia debemos escribir una regla diferente. Por ejemplo, la precedencia de la multiplicación sobre la suma y la resta se puede agregar a nuesira gramática de expresión Gmple conlo se ve a continuación:

exp -+ exp rq~swxn / term ex,? opsurnci -+ + 1 terrn -+ term opntult tenn / j¿zctor opmult -+ * jbctor -+ ( exp ) 1 número
En esta gramática la inultiplicación se agrupa hajo la regla term, mientras que la suma y la resta se agrupan b j la regia exp. Como el caso base para una exp es un terrn, esto significa do que la suma y la resta aparecerán "más altas" (es decir. niás cercanas a la raíz) en los árboles de análisis gramatical y sintáctico, de manera que reciben una precedencia más baja. IJna agrupación así de operadores en diferentes niveles de precedencia es un método estándar para la especificación sintáctica utilizando BNF. A una agrupación de esta clase se le conoce como cascada de precedencia. Esta última gramática para expresiones aritméticas simples todavía no especifica la asociatividad de los operadores y aún es ambigua. La causa es que la recursión en ambos lados del operador permite que cualquier lado iguale repeticiones del operador en una derivación (y, por lo tanto, en los árboles de análisis gramatical y sintáctico). La solución es reemplazar una de Las recursiones con el caso base, forzando las coincidencias repetitivas en el lado en que está la recursión restante. Por consiguiente, reemplazando la regla

.

exp -t e,~p opsirma exp / term
por

se hace a la suma y a la resta asociativas por la izquierda, mientras que al cscrihir

Ambigüedad

se hacen asociativ:ts por la derecha. E n otras palabras, una regla recursiva por l a izquiercla hace a sus operaclores astriados a la i~qkiicrtla,mientras que una regla recursiva por la derecha los hace asociados a la derecha. Para completar la e l i n i i n a c i h de ambigüedades en las reglas BNF en el caso de nuestras expresiones aritméticas siiiiples escrihimcts las reglas que pesinileii Iiacer todas las operaciories asociativas por la izquiercta:

Ahora el árbol de a n á l i w gramatical para la expre\ií,n 3 4 - 3 * 4 2 e\

número

número

y el árbol de análisis gramatical para la e x p r e s i h 3 4 - 3 - 4 2 es

número

i\dvierta que las cascadas de prccctlcncia provocan clue los ái-holes tle ni~álisis gi.aiti;itic:il v w l v a n mucho iiiáh cotiiplcjos. Sin enihai-go. iio afect;tn L: los árboles sintácticos.

se

3.4.3 El problema del else ambiguo
Considere la granática del ejemplo 3.4 (página 103):

sentencia -+ sent-$1 otro sent-if-, if ( e-rp ) sentencia / if ( exp ) sentencia else sentencia i exp -. O / 1
Esta grarntitica es ambigua como resultado del else opcional. Para ver esto corisidere la cadena
if ( O ) if (1) otro else otro

E\ta cadena tiene los dos árboles de análisis gramatical:

sent-if

1

!

otro

I

otro

I

¡.a ctieslicín de cnál es el correcto depende de si queremos iisociür la parte clse cori In primera o la icgtincla sentencia if: el primer árhol de antilisis grarnatic;il asocia I;i parte clse i graniatic;d la ;isoci:i wti I;i 5cg1itiJa con la pr-itncra iciit~ncia f ;CI sepnrlo it-bol de :~iiiílisis

Ambigüedad
sentencia if. Esta anihigiieilad se tienomina problema del else ambiguo. Para ver cuál i r b o l de análisis gr;ttn:itical es correcto, debenios considerar las iritplicncioiies para el sigitificado de la sentencia if. Para ohtener iinn idea m i s clara de esto considere el sigtiicnte segiiicnto de cúdigo en C

if ( x ! = 0) if ( y = = l/x) ok = TRUE; else z = l/x;
E n este código, siempre que x sea 0, se presentar5 nti error de divisióti entre O s i la parte else está asociada con la primera sentencia if. De este modo. la i~itplicaciúii este código (y en de realidad la implicaciúii de la saiigría de la parte else) es que una parre clse sieit~pre tleheria estar asociada con la seittencia il' rn5s ccrcritla que todab.ía no tenga una parte else asocit~la. Esta regla de eliininación de anihigiiedad se conoce conio regla de la anidacinn más cercana para el problema del else amhiguo, e implica que e l segundo 6rhol de análisis grainatical anteriur es el cotrectit. Advierta que, s i quisi6ramos, podrícrnlos asociiir la parte el else con la priinerri sentencia il-~nedia~ite uso de lltivcs (. .) en C. corito cri

.

iJna solución para la ainhigiieclad del else ambiguo cti la BNF ntisina es i n i s difícil que las ariihigüedades previas que heinos v i ~ t o Una soliicióii es como sigue: .

Esto fuiiciona :il permitir que llegue solainente una .seiirr~nc~icr-i~~i~fI~~~I~i un else antes que en una sentencia il,con l o que se obliga a qire todas las partes else se crtipaten tan pronto conlo sea posible. Por ejerilplo, el k h o l tle análisis g r m a t i c a l asociado para nuestra c;idena (le niuestra se convierte ahora en

1

otro

otro

122

CAP. 3 I

GRAMATICAS LIBRES DE CONTEXTO Y AN~LISISIINTACTICO

Por lo regular no se emprende la construcción de la regla de la anidación más cercana en la BNF. En su lugar, se prefiere la regla de eliminación de ambigüedad. Una razón es la complejidad agregada de la nueva gramática, pero la razón principal es que los métodos de análisis sintáctico son fáciles de configurar de una manera tal que se obedezca la regla de la anidación más cercana. (La precedencia y la asociatividad son un poco más difíciles de conseguir automáticamente sin volver a escribir la gramática.) El problema del else ambiguo tiene sus orígenes en la sintaxis de Algol60. La sintaxis se puede diseñar de tal manera que no aparezca el problema del else ambiguo. Una forma es requerir la presencia de la parte else, un método que seha utilizado en LISP y otros lenguajes funcionales (donde siempre se debe devolver un valor). Otra solución es utilizar una palabra clave de agrupación para la sentencia if. Los lenguajes que utilizan esta solución incluyen a Algo160 y Ada. En Ada, por ejemplo, el programador escribe
if x / = O then if y = l/x then ok := true; else z := l/x; end if; end if;

para asociar la parte else con la segunda sentencia if. De manera alternativa, el programador escribe
if x / = O then if y = l/x then ok end if; else z := l/x; end if;

: 3

true;

para asociarla con la primera sentencia if. La correspondiente BNF en Ada (algo simplificada) es

s e n t - $ 4 if condiciín then secuenciu-de-sentencius end if / if condición then secl~enciu-de-sentencias el se secuencia-de-sentencias end i f
De este modo, las dos palabras clave en& if son L palabra clave de agmpación en Ada. a En Algo168 la palabra clave de agmpación es fi (if escrito al revés).

3.4.4 Ambigüedad no esencial
En ocasiones una gramática puede ser ambigua y aún así producir siempre árboles sintácticos abstractos únicos. Considere, por ejemplo, la gramática de secuencia de sentencias del ejemplo 3.9 (página 113), donde pdríamos elegir una simple lista de hermanos como el &bol sintáctico. En este caso una regla gramatical, ya fuera recursiva por la derecha o recursiva por la izquierda, todavía daría como resultado la misma estructura de árbol sintáctico, y podríamos escribir la grarnitica ambiguamente como

Notaciones extendidas: EBNF y diagramar de sintaxis

123

y aún obtener árboles sintácticos únicos. Una ambigüedad tal se podría llamar ambigüedad no esencial, puesto que la semántica asociada no depende de cuál regla de elirninación de ambigüedad se emplee. tina situación semejante surge con los operadores binarios, ccinio los de la suma aritmética o la concatenación de cadenas, que representan operaciones asociativas (un operador binario . es asociativo si (u . b) c = u ( b c ) para todo valor u, h y c). En este caso los irboles sintácticos toliavía son distintos, pero representan el mismo valor semántica, y podenios no preocuparnos acerca de cuál utilizar. No obstante, un algoritmo de análisis sintáctico necesitará aplicar alguna regla de no ambigüedad que el escritor del cornpilador puede necesitar suministrar.

-

NOTACIONES EXTENDIDAS: EBNF Y DIAGRAMA5 DE SINTAXIS 3.5.1 Notación EBNF
Las construcciones repetitivas y opcionales son muy comunes en los lenguajes de programación, lo mismo que las reglas de gramática de la BNF. Por lo tanto, no debería sorprendernos que la notación BNF se extienda en ocasiones con el fin de incluir notaciones .especiales para estas dos situaciones. Estas extensiones comprenden una notación que se denomina BNF extendida, o EBNF (por las siglas del t é n i n o en inglés). Considere en primer lugar el caso de la repetición, como el de las secuencias de sentencias. Vimos que la repetición se expresa por la recursión en las reglas gramaticales y que se puede utilizar tanto la recursión por la izquierda como la recursión por la derecha, indicadas por las reglas genéricas A
4

Aa

/p

(recursiva por la i~quierda)

4- aA ,

/p

(recursiva por la derecha)

donde a y $ son cadenas arbitrarias de terminales y no terminales y donde en la primera regla @ no comienza con A y en la segunda $ no finaliza con A. Se podría emplear la niisma notación para la repetición que la que se utiliza para las expresiones regulares, a saber, el asterisco * (también conocido como cerradura de Kleene en expresiones regulares). Entonces estas dos reglas se escribirían como las reglas no recursivas

En vez de eso EBNF prefiere emplear llaves (. . . ) para expresar Ia repetición (así hace clara la extensión de la cadena que se va a repetir), y escribimos

A--,

B {el

p:tra la\ reglas

CAP. 3 1 GRAMATICAS LIBRES DE CONTEXTO Y ANALISIS IINTACTICO
El problema con cualquier notación de repetición es que oculta cómo se construye el árbol de asiilisis gramatical, pero, como ya se expuso, a menudo no iiriporta. Tonternos por ejemplo el caso de las secuencias de sentencias (ejemplo 3.9). Escribimos la gramática como sigue, en forma recursiva por la derecha:

secuencia-sent -+ sent xent 4 s
Ehta regla tiene L forma A -+ a A a ERNF esto aparecería como

; rectiencia-sent

/

wnt

/ P, con A
;]

= recuencia-renr,cr = rent ;y

P = wnt. En

,secuencia-sent -t ( sent

sent

(forma recursiva por la derecha)

Tanibién podríamos haber uwio una regla recursiva por la izquierda y obtcnido la EBNF

secuenciu-sent 4 sent (

; sent

)

(forma recursiva por la izquierda)

De hecho, la segunda forma es la que generalmente se utiliza (por razones que anaiizaremos en el siguiente capítulo). Un problema más importante se presenta cuando se involucra la asociatividad, como ocurre con las operaciones binarias como L resta y la división. Por ejemplo, consideremos a la primera regia gramatical en la gramática de expresión simple de la anterior subsección:

exp -+ exp opsunm terrn / term
Esto tiene la forma A -+A a 1 p, con A = exp, a do, escribimos esta regla en EBNF como
=

opsuinu term y

p = term. De este mo-

exp -+ term { opsuma term )
Ahora tamhibn debemos suponer que esto implica asociatividad por la i~quierda, aunque la regla misma no lo establezca explícitamente. Debemos suponer que una regla asociatsva por la derecha estaría implicada al escribir

e,rp 4 ( terrn opsuinu } term
pero éste no es el caio. En cambio, una regla recursiva por la derecha como

.secuencia-sent -+ sent

; secuencicc-sent

/ sent

se visualiza corno si fuera una sentencia seguida opcionalmente por un signo de punto y corna y una .xecirencicz-senr. Las construcciones opcionales en EBNF se indican encenándolas entre corchetes [. . .J. Esto es similar en eseilcia a la convención de la expresión regul~rr colocar un signo de inde terrogación despuis de una parte opcional, pero tiene la ventaja de encerrar la parte opcional sin rcyuerir pür611tesis. Por ejemplo, las reglas gramaticales para las sentencias if con partes iqxionaies clse iejctrtplos 3.4 y 3.6) se escribirían en ERNF corno se ilcscrihc a cor~tirirr;ici(,ti:

Notaciones extendidas: EBNF y diagramas de sintaxis

U11a regla recur\iv;r por la derecha tal como

tanibik $e escribe como

(compare esto con el uso de las llaves anterior para escribir esta reglaen Iortna recursiva por la izquierda). Si deseáramos escribir una operacidn aritmética tal como la suma en forma asociativa derecha, escribiríamos

en lugar de utilirar llaves

3.5.2

Diagramas de sintaxis

Las representaciones gráficas para simbolizar de manera visual las reglas de la EBNF se denominan diagramas de sintaxis. Se componen de cajas que representan terminales y no terminales. líneas con flechas que simbolizan secuencias y selecciones, y etiquetas de no terminales para cada diagrama que representan la regla gramatical que define ese no terniinal. P r indicar terminales en un diagrama se emplea una caja de forma oval o redonda, mientras aa que para indicar no terminales se utiliza una caja rectangtilar o c,uadrada. Considere como ejemplo la regla gramatical

Ésta se escribe como un diagrama sintáctico de la siguiente manera:

Advierta yue~irctor está situado en iinrt caja, pero se utiliza como rma etiqueta para cl no di:igrama tle sintaxis. lo cual indica que el <li;tgr;ini;trepresenta la dcFinici61ide la cstriictnr3 de cse nombre. !\rlvierta tmbién el tiso de los líneas con flechas para iixlic:~rla wlcccih y la sccuenciaci61i.

CP 3 1 A.

GRAMATICAS

LIBRES DE CONTEXTO Y

ANALISIS IINTÁCTICO

Los diagramas de sintaxis se escriben desde la EBNF mds que desde la BNF, de modo que necesitamos diagramas que representen construcciones de repetición y opcionales. Dada una repetición tal como

el ciiagratna de sintaxis correspondiente se dibnja por lo regular como sigue:

Advierta que el diagrama debe tener en cuenta que no aparezca ninguna B. Una construcción opcional tal como

se dibuja como

Concluiremos nuestro análisis de los diagramas de sintaxis con algunos ejemplos en los que se utilizan ejemplos anteriores de EBNF.

Ejemplo 3.10

Considere nuestro ejemplo de ejecución de expresiones aritméticas simples. Éste tiene la BNF (incluyendo asociatividad y precedencia).

exp -+ exp opsuma term / term opsuma -+ + 1 term -+ term opmult fbctor 1 factor opmult -+ * factor -+ ( exp ) 1 número
La EBNF correspondiente es

exp - ternl ( opsuma term ) , opsurna -+ + / term 4 jzctor ( opmult juctor ) opmult -+ * fLictor -+ ( e up ) 1 número
Los tiiagramns de sintaxis correspondientes se proporcionan en In tlgnra 3.1 (el Jiagrasna de sintaxis parqfiictor \e dio con anteriorid;iti).

1 1 Considere la g r ~ r n á t i c ~ bis sentencias if sirnplific:t<las del ejeinplo 3. Ésde .te tiene la BNF y la EBNF .Notaciones extendidas: EBNF y diagramar de sintaxis rer rn w Jm tor Ejemplo 3.4 (pllgioa 103).

i<i serit-if 3. Sea G una p t n i t i c a detinida del modo anterior. í1. J. donde u y y son ele3 mentos de (l'u N)" y . Un conjunto N de no terminales (disjunto de 7J.5 Diagramar de sintaxis para la gnmátita del ejemplo 3. S).6 PROPIEDADES FORMALES DE LOS LENGUAJES LIBRES DE CONTEXTO 3.1 1 srma. Un símbolo inicial S del conjunto N. donde A es un elemento de N y cu es un elemento de ( T U Nf* (una secuencia posiblemente vacía de terminales y no terminales). de manera que G -. Comenzaremos por establecer una definición formal de una gramática libre de contexto.61 Una definición formal de los lenguajes libres de contexto Pre5entarnos aquí de una manera más formal y matemática algo de la terminología y definiciones que presentamos anteriormente en este capítulo. P. 3 I GRAMATICAS LIBRES DE CONTEXTO Y AN~LISIIIIINTÁCTICO Fisura 3. está en P. Definición Una gramática libre de contexto se compone de lo qiguiente: Un conjunto T de terminales.a mitin T U N de 10s ~oiijimtos terminales y de .(T.? 4 /i. Un conjunto P de producciones. de la forma A -+a. N. Un paso o etapa de derivación sobre G es de 1 forma a y * cr P y.128 CAP. o reglas gramaticales.

. 1./ tal que u = al y P = a. & . cada árbol de aniílisis graniatical tiene una única derivación por la izquierda y por la derecha que le da origen. Xn (los cuales pueden ser terminales o no terminales). . l. En general.iIes). 4. X. Cada nodo no hoja está etiquetado con un no terminal. .Y2... a 3 % s i y stilo si. La derivación por 1ü izquierda corresponde a un recorrido preorden del irbol de análisis grainatical. .) Una derivati611sohre la gramática G es de la forina S 3" cv. Cada nodo hoja está etiquetado con un terminal o con e. Una gramática G es ambigwa s i existe una cadena w E L(G) tal que w tenga dos árboles de anilisis gramatical distintos (o derivaciones por la izquierda o por la derecha). a fi como la cerradura traiisitiva de la relación de paso de la derivacitin 3:cs dccir. . conesponde a la construcción d e n hijos con etiquetas XI. E l nodo raíz está etiquetado con el símbolo inicial S. u p y en la derivación. cioritle ilcfiníaritr)~los nonihres de Cst. . 5. entonces a = P.. y una cadena cu en ( T U N)* se conoce como forma de sentencia u oracional.is nietlimrc el signo [le igmldatl. entonces A -+ X. . .ircs. . mientras la derivación por la derecha corresponde a un recorrido inverso postorden del árbol de análisis graniatical. . Es decir. L(G) es el conjunto de sentencias derivable de S. . 2. .. Si un nodo con etiqueta '4 E Ntiene n hijos con etiquetas XI.iti~:~lcs l a dc(irlici61l dc LS c . donde w E T+ (es decir. En general. se dcfine como el conjunto L ( G ) { w E 7-1 existe una derivación S ==+ * I ~ Vde G ) . X. Cada nodo está etiquetado con un terminal o un no terminal o E . muchas grarnáticas diferentes generan el mismo tcnyuaje libre de contexto. muchas derivaciones pueden (lar origen al mismo irbol de análisis gramatical. del riodo coi1 etiqueta A. deriv:tción u A y =r a p y tiene ID Un árbol de análisis gramatical sobre la gramática G es un árbol etiquetado desde L a r a í 7 que tiene las siguientes propiedades: - *E.isgr~iii. Se dice que un conjunto de cadenas L es un lenguaje libre de contexto s i existe una gramática G libre de contexto tal que L = L(G).2 Reglas gramaticales como ecuaciones A l principio de esta seccitin advertimos que las reglas gramaticales u t i l i ~ a n l símbolo dc c la tiecha en lugar de un signo de igualdad para rcprescntar la <lefinicititi (le los tioinbrcs i par:i las eupresiones de 1:ts estructuras (110 terlnin. 3. una derivaci6n por la derecha es aquella en la cual cada paso de propiedad de que y E F .Yl X2 .Propiedades formales de l s lenguales l~bresde contexto o 129 no tcrrninales se conoce en ocasiones como conjunto de símbolos de C.. . E P (una producción de la gramática). u se compone sólo de tcrminales. con fl = . Cada clerivación da origen a un árbol de :inálisis gramatical tal que cada paso a A y I-. . El Lenguaje generado por G. decir. Sin ernbargo. X.. w es una cadena de sólo tern1in:iles dcnominada sentencia) y S m el síntbolo inicial de G. Una derivacich por la izquierda S iv es una derivación en la cual cada paso de dees rivación a A y * a p y es de tal nianera que a E p .) 1 relación tu =*fi se definc . pero las cadenas en el lenguaje tendrin árboles de análisis gramatical diferentes dependiendo de la gramática utilizada. . denotado por '(G). X2.6. De manera similar. (Si II = O. existe una secuencin (le O o inás pasos de derivación (11 2 O) . 3.a r a ~ i í n de hitce I q ~ sc dio kie yuc la n:itiit-de/a rcct~i-sita 13s ~rcgl. diferencia de nuestra ~iotaciiiii rcgul.

Suponga que nombramos a este conjunto E y que N es el conjunto de los números naturales (correspondiente al nombre de la expresión regular número). también lo está N N N. Considere. Considere cómo puede definir esto a E. Sin embargo. En primer lugar. hay un sentido en el que aún se mantiene la igualdad de los lados izquierdo y derecho en una regla gramatical. y vimos que en realidad las cadenas definidas por reglas gramaticales resultan de derivaciones. puede demostrarse que esta E satisface la ecuación en cuestión.relaciones (las reglas gramaticales) menos parecidas a la igualdad. sino haciendo la concatenación de ellas con el símbolo + entre las mismas. la regla gramatical dada Fe puede interpretar como la ecuación de conjuntos donde E E es el conjunto de cadenas ( u + v / u. Puede demostrarse que todas las estructuras de lenguaje de programación definidas recursivamente. tienen semántica de punto fijo mínimo cuando se implementan según los algoritmos habituales que estudiaremos más adelante en este libro. En realidad. pero el proceso de definición de lenguaje que este punto de vista da como resultado es diferente. de la manera en que está definido por este método. está proporcionando una semántica de punto Ftjo mínimo. ya que los niétodos como éste se pueden emplear en el futuro para verificar la exactitud de los compiladores. Podemos visu a1' izar eqto como una construcción inductiva de más y más cadenas largas y más largas. Decimos que E. y la unión de todos estos conjuntos es el resultado deseado: + + + + + + + De hecho. Si observamos e1 lado derecho de la ecuación para E como una función (conjunto) de E. como tanto N como N N están contenidos en E. Esto es un punto importante. de manera que definamos f ( s ) = (S S) u N. incluso en compiladores producidos coinerci~lincute.Entonces. el conjunto N está contenido en E (el caso base). Este punto de vista es importante para la teoría de la semántica de lenguajes de programación y vale la pena estudiarlo brevemente por su penetración en los procesos recursivos. entonces la ec&ción para E se convierte en E= f ( E ) . tipos de datos recursivos y funciones recursivas. En otras palabras. + . y a menudo se mantienen errores sustnnciales. En su lugar sólo se realizan pruebas del código del cornpilador para asegurar una aproximación de su exactitud. E es un punto fijo de la función f y (de acuerdo con nuestro comentario anterior) en realidad es el punto fijo más pequeño. Acto seguido. Actualmente es raro que se pruebe si los compiladores son correctos. por ejemplo. como el análisis sintáctico. el hecho de que E E también esté contenido en E implica que N N está contenido en E . donde se utiliza un método de reemplazo de izquierda a derecha que sigue la dirección de la flecha en la BNF. la unión de N y E E. puesto que E er. la siguiente regla gramatical. que se extrajo (en forma simplificada) de nuestra gr'mática de expresión simple: exp -+ exp + exp / número Ya vimos que un nombre de no terminal como exp define un conjunto de cadenas de terminales (el cual es el lenguaje de la gramática si el no terminal es el símbolo inicial). Pero entonces. y así sucesivamente.v E E ) . E es el conjunto más pequeño que lo hace.) Ésta es una ecuación recursiva para el conjunto E. aun cuando los algoritmos de análisis sintáctico que estudiamos no se basen en ello. como la sintaxis. (No estamos sumando las cadenas u y v aquí.

pero es proporcionalniente menos eficiente. a Por t$einplo. Una situación diferente se presenta respecto a las reglas de contexto. y de ese modo no expresar la sintaxis completa direciamente. Hemos estado utilizari<loel término "libre de contexto" sin explicar por qué tales reglas son en efecto "libres de contexto".M La jerarquía de Chomsky y los límites de la sintaxis como reglas libres de contexto Cuando se representa la estructura sintlíctica de un lenguaje de progrmación. podríamos escribir reglas gramaticales para la construcción de todos los tokens de caracteres y prescindir del todo de las expresiones regulares. ¿Por qué no es una buena idea hacer esto? Porque podría quedar eomprornetida la eficiencia. Se dice que una gramática con esta p p i e d a d e s una gramática regular. Naturalmente. el ~mplementador del lenguaje esperaría extraer estas definiciones de la gramática y convertirlas eri un analizador léxico. El análisis anterior mostró que las grarniíticas libres de contexto pueden expresar concatenación. o donde pueda ser imposible expresar un requerimiento en la gramática. Pero tatribién es importante saber lo que puede 0 debería ser representado por la BNF. consideremos L definición de un número como una secuencia de dígitos titilizantio expresiones regulares: d í g i t o = 0111213141516171819 número = d í g i t o d í g i t o * En cambio. Pueden surgir otras situaciones donde podamos intentar expresar demasiado en la grarnática. exactamente como pueden hacerlo las expresiones regulares. repetición y selección. las grarnáticas libres de contexto en BNF o EBNF son una herramienta útil y poderosa. puede \er razonable y útil ineluir una definición de lo\ toketi\ en la BNF en 5i: la gramática expreiaria entonces la estructura cintúctica completa. Una cuestibn que surge con frecuencia cuando escribimos la BNF para un lenguaje es la extensión para la cual la estructura léxica debería expresiuse en la BNF más que en tina descripción separada (posiblemente utilizando expresiones regulares). una regla . Aun así. las cuales se presentan frecuentemente en lenguajes de prctgramaci6n. podemos escribir esta definición utilizando BNF como Advierta que la recursión en la segunda regla se utiliza sólo para expresar repetición.a izquierda de la flecha en las reglas libres de contexto. Por lo tanto. Un analizador sintictieo es una miquina más poderosa que un analizador léxico. incluyendo la eitructura Iéxica. De este modo. y las graniáticas regulares pueden expresar todo lo que pueden hacer las expresiones regulares. En esta sección analizaremos algunos de los casos comunes. IJna consecuencia de esto es que podríamos diseñar un analizador sintáctico que podría aceptar caracteres directamente del archivo fuente de entrada y prescindir por completo del analizador léxico. Ya vimos una situación en la cual la gramática se puede mantener ambigua de tnanera intencional (el problema del else ambiguo). La razón mis simple es que los no terniinales aparecen por sí mismos a l.Prop~edadesformales de los lenguajes libres de contexto 3.

tipo 2 y tipo 3. incluir las cadenas de nombre inistnas en las reglas gramaticales en vez de incluir todos los nombres como tokens de identificador que son indistinguibles. de modo que el número de posibles identificadores es (al menos potencialmente) infinito. tipos suministrados estáticamente) y reglas coino declaración a&es del uso.~máticas tide po 0. De 1 h ora en adelante consideraremos como sinraxis sólo aquellas reglas que se puedan expresar mediante reglas BNF. Evidentemente. El cuerpo de las reglas de lenguaje.ític:issin rci- . Estas grarixíticas rcpresentm ~tivcles ilistiritos rle potencia computacioiial. al que se hace referencia como semántica estática del lenguaje. existe una diferencia: una regla así no se puede imponer por el analizador sintictico mismo. las p~n. pero son mucho más difíciles de utilizar como la base para un an~lizador sintáctico. Todo lo deniás se considerará como semántica.Qué clase de requerimientos en los lenguajes de prograrnación requieren de reglas sensibles al contexto'? Los ejemplos típicos involucran el uso de nombres. A las clases de lenguaje que se construyen con ellas se les conoce tairihién como Jerarquía de Chomsky. está más allá del alcance de verificación del analizador sintáctico. Aquí. Existe otra clase de gramática que es incluso ~ n á s general que las gramáticas sensibles al contexto. en prinier lugar. porque depende del uso de la tabla de símbolos (que registra cuáles identificadores se han declarado). pero todavía puede ser verificado por el cornpilador. podnarnos definir un contexto de manera infornial como un par de cadenas (de terminales y no terminales) p. Pero en muchos lenguajes la longitud de un identificador no está restringida. Por otra parte. libres de contexto y regulares) también se conocen como gr. Sin embargo. Estas gmnkicas se denominan gramáticas sin restricciones y tienen reglas gramaticales de la forman + p. Esto incluye verificacibn de tipo (en un lenguaje con '. esta situación es imposible. quien kie cl prinrero en utilizarlas para describir los lenguajes naturalcs. y. tipo 1. un nombre debe aparecer en una declaración antes que sea permitido su uso en tina sentencia o una expresión: Si intentáranios abordar este requerimiento utilizando reglas de BNF. puesto que está inás allá del poder de expresar de las reglas libres de contexto (razonables). en honor de N o m Chomsky. sin impowar dónde se presente. La solución es semejante a la de una regla de eliminación de ambigüedad: simplemente estabiecemos una regla (declaración antes del uso) que no está explícita en la gramática. En su lugar.CP 3 1 A. sensibles al contexto. En segundo lugar. tendríamos que. tendríamos el potencial para cientos de nuevas reglas gramaticales. GRAMATICAS LIBRES DE CONTEXlO Y ANALISIS IINTACTICO dice que /1 se puede reemplazar por a en c~ulyuier sitio. respectivatneute. tendríamos que escribir para cada nombre una regla estableciendo su declaración antes de un uso potencial. Incluso si permitiéramos que los nombres fueran de sólo dos caracteres de longitud. E k realidad. esta regla se vuelve parte del análisis semántica. Las gramáticas sensibles al contexto son más poderosas que las gramáticas libres de contexto. tales que una regia se aplicaría sólo si P se presenta antes y y se presenta después del no terminal. Po<l&amosescribir esto como Una regla de esta clase en la que cu f E se denomina regla gramatical sensible al contexto. Las cuatro clases de gramáticas (sin restricciones. donde no hay restricciones en la forma de las cadenas cu y P (excepto que u no puede ser e). La regla en C que requiere dectaraciún antes del uso es un ejemplo común. ¿.

xp-sitnple opsicniu trnn / term ucla'op + + / lefm -+ ferm oprnn[t. rcpresenian la clase mí$general de computación cotiocida. y las ticnicas elaboradas ex profeso para la eliminación de la anihigüedad que descrihiinos por lo regular han probado ser adectiadiis en casos prácticos.1. secuenciu-. sólo que la senterici~i f u t i l i z a e n d i como palabra clave de agrupación (de manera que no hay anibigüedad de else m h i g u o en TINY) y que las sentencias i f y las sentencias repeat permiten seciiencias de sentencim como cuerpos.is ambiguas.f«cforfrtcior 1 oprrnilt + * 1 / firctor i ( e. A partir de &í hacenios las siguientes observaciones.sentencia / serztmciu sentencia -+sent-if / sent-repeat / sent-assigo / sent-reíid / serzt-write . sentencias r e d o de Lectrira.recuenciu-se~zi'else secuencia-sent end setit-repeut + r e g e a t secrtencicl-sent unt i l exp .inaliz. sentencias write o de escritura y sclltc~icias o (le asignación.rp-simple -+ e.tlgoritmo que convirtiera una gramática ambigua en tina n o aiiibigua sin inoilificar el Icnguaje subyacente. e incluso no se ha resuelto el problcnia de la deteroiinaciórt de si un lenguaje es inherentemente ambiguo.inrática no ambigua (éstos se clenominan lenguajes inherentemente ambiguos). y.Itntaxis del lenguaje TlNY 133 tricciones (o de tipo O ) son equivalentes a las iriáyui~~as I'uring. existeri incluso lenguajes lihres de coriiextu para los cuales no existe una gr.7 SINTAXIS DEL LENGUAJE TINY 3.rp sent-reud + r e a d i d e a ti f i c a d o r sent-write -+ write exp rsp -+ exp-simple op-cornpurucicin rxp-simple / e.iremos inás.6.?en1 secuencia-sent -+ secuencia-sent .b Gramática del lenguaje TlNY en BNF programa-.rp-simple opcornparucicin -+ < = e. De hecho. de manera que posihleincnte iio exista un algoritmo así. sintaxis tipo Pascal.Iior qjeriiplo. Existen cinco clases de sentencias: sentencias if o condicionales. así que no los .e n d (e incluso ~ Fiturd 3. Desgraciatlamente. . las complicaciones tales como nna ainhigiicdad inherente no surgcii como una regla en los lenguajes de programación.ip ) / número i identificador . sentencias repeat o de assign repeticicín.tmbién tienen un equiv:ilctite de tiiácjniiia corresy>o~itliente.sent-if-+ if exp then secitencLiu-sentend / if exp then . 3.1 Una gramática libre de contexto para TINY L a gramática para TINY está diida en B N F en la figura 3. al tratar coi1 gramátic. de l a inisiiia iriimcra cpc de las gramáticas regril. se s:~hrque &te es un problema que aún no se ha resuelto.pero no iiecesitiiremos todo el poder de uim r n i q i i i m así pura nucstros ülgorirnios de análisis sintáctico. Por suerte. .sent-assign+ i d e n t i f i c a d o r := e. clenonriiinda ant6rnat:i "de pila" (pirshdown). por consigtiieiite.ires son equivalentes a los autómatas finitos. de modo que no s o necesarios los corchetes o pares de b e g i n . U n programa TINY es simpleinerite una sectieiicia de sentencias. l ambién dcherí~niosser cttidadosos respecto a que ciertos problemas coiiiput:icii>tialmente intratables están asociados con grainiílicas y lenguajes libres de coritex~o. sería excelente que pudiCranios c u h l e c e r riti . Los graiiriíticas libres Je contexto t. Estas tienen una.

-. en contraste. Las operaciones aritméticas son asociativas por la izquierda y tienen las precedencias habituales. Los identificadores en TINY se refieren a variables enteras simples. tíunbién podríamos simplemente haber escrito la regla secuencia-sent recursivamente a la derecha en su lugar. Sin embargo. sentencias read y sentencias write) y tres clases de expresiones (expresiones de operador. son no asociativas: sólo se permite una operación de comparación por cada expresión sin paréntesis. * y / (este último representa la división entera. Las expresiones TINY son de dos variedades: expresiones booleanas que utilizan los operadores de comparación = y < que aparecen en las pmebas de las sentencias if y repeat y expresiones aritméticas (denotadas por medio de exp-simple en la gramática) que incluyen los operadores enteros cstándar +.begin no es una palabra reservada en TINY). Las operaciones de comparación. Las operaciones de comparación también tienen menor precedencia que cualquiera de las operaciones aritméticas. Además.2 Estructura del árbol sintictico para el compilador TINY En TINY existen dos clases básicas de estructuras: sentencias y expresiones. Una nota final acerca de la gramática de TINY. De este modo. un nodo de árbol sintáctico se clasificará principalmente como si fuera una sentencia o expresión y en forma secundaria respecto a la clase. Por lo tanto. Las secuencias de sentencias deben contener signos de punto y coma separando las sentencias. También escribimos la regla BNF para secuencia-sent como una regla recursiva por la izquierda. . Tampoco existen declaraciones de variable en TINY: una variable se declara de manera implícita a1 aparecer a la izquierda de una asignación.de sentencia o expresión: Un nodo de árbol tendrá un máximo de tres estructuras hijo (solo se necesitan las tres en el caso de una sentencia if con una parte else). existe sólo un ámbito (global). sentencias repeat. Los nodos de sentencia generalmente no necesitan atributos (aparte del de la clase de nodo que son). porque el propósito es simplemente que se ejecuten en orden. Las sentencias de entraddsalida comienzan mediante las palabras reservadas read y write. Las sentencias serán secuenciadas mediante un campo de hermanos en lugar de utilizar un campo hijo. y no procedimientos o funciones (y. pero en realidad no importa la asociatividad de las secuencias de sentencias. Existen cinco clases de sentencias (sentencias if. por lo tanto. expresiones de constante y expresiones de identificador). Esta observación también se representará en la estructura del árbol sintáctico de un programa TINY. Los atributos que deben mantenerse en los nodos de árbol son como sigue (apate de los campos ya mencionados). Un nodo de identificador necesita un campo para contener el nombre del idcntificador. y un signo de punto y coma después de la sentencia final en una secuencia de sentencias es ilegal. No hay variables estructuradas. sentencias assign. tampoco llamadas a los mismos). en ocasiones conocida como div). Esto se debe a que no hay sentencias vacías en TINY (a diferencia de Pascal y C). Una sentencia read de lectura puede leer sólo una variable a la vez. Cada clase de nodo de expresión necesita un atributo especial. 31. por simplicidad. tales como arreglos o registros. Un nodo de constante necesita un c a m p para la constante entera que representa. Y un nodo de operador necesita un campo para contener el nombre del operador. donde las secuencias de sentencias estarán representadas por listas en lugar de árboles. en el caso de las sentencias assign y read conservaremos el nombre <lela variable que se está asignando o leyendo justo en el nodo de sentencia rnisnio (mis que como un nodo hijo de expresih). Ahora volveremos a un análisis de esta estructura. y una sentencia write de escritura puede escribir solamente una expresión a la vez.

Fiyurai? Oedaracionos en C para un nodo de árbol iintactico TlNY Ahora queremos dar una descripción visual de la estructura del lírbol sintáctico y mostrar visualmente este árbol para un programa de muestra. Éslas también nos ayudarin a recordar cuáles atributos van con cada clase de nodo. E! primero es el atributo de contabilid~d lineno. Advierta que utilizanios uniones en estas declaraciones para ayudar a conservar e1 espacio.el cual después necesitaremos para verificar el tipo de las expresiones ( y solamente expresiones).7. También indicaremos otras estructuras de rirbol que no se especifican en los diagranias mediante triángulos y Iíneas punteailas para inilicar estructuras que puedan o no aparecer. Iíne:is piinteiidas y los triringtilos). 2 declaraciones en C dadas en la figura 3.. Una secuencia de sentencias coiiect~das tnediante campos de hermanos e vería entonces como se muestra a continuacicín (los subrirholes potenciales cstán intlicados ine<li.éste nos permite imprimir los nún~eros Iíneas del código fuente con errores que de puedan ocurrir en etapas posteriores de la traducción.Sintaxis del lenguaje TlNY 135 1 1 estructura de nodo de árbol que acabamos ile describir se puede obtener mediante las . mientras que los de apuntadores hijos se dibujarán ahajo de las cajas. Para hacer esto utilizamos cajas rectangulares para indicar nodos de sentencia y cajas redondas u ovales para indicar nodos de expresión. La clase (le sentencia o expresión se proporciona& como una etiqueta dentro de la caja. Dos atributos adicionales que aún no hemos mencionado están presentes en la dec'ración. Los apuntadores hermanos se dibujarán a la ~Icrechü las cajas de nodo.uitcla.h en el apéndice B (líneas 198-217).esto se estudiará con todo detalle en el capítulo 6. mientras que los atributos adicionales también se enumerarán allí entre paréntesis. Está declarado para ser del tipo enumerado ExgType. que se repiten del listado correspondiente al archivo globals . . El segundo es el campo type.

prueba parte then parte else Una sentencia repeat tendrá dos hijos. la segunda es la expresion de prueba: cuerpo prueba Una sentencia de asignación ("assign") tiene un hijo que representa la expresión cuyo valor se asigna (el nombre de la variable que se asigna se conserva en el nodo de sentencia): expresión [Jna sentencia de escritura ("write") tamhién tiene un hijo. que representa la expresión de la que se eicrihir. 3 1 GRAMATICAS LIBRES DE CONTEXTO Y ANALISIS SINTACTICO Una sentencia if (con potencialmente tres hijos) tendrá un aspecto como el siguiente. La primera es la secuencia de sentencias que representan su cuerpo.i el valor: .CAP.

Iintaxis del lenguaje TINY expresión Una expresión de operador time dos Iii. write fact í salida fsctorial de x end 5 > .i~ido iiquierdo y dereclio: 'focios los otros nodos isentencias reid. Su :irbol sintictico se in~iestrü la figura 3. expresiones de identiI'icndor y expresiones de constante) son iiodos hoja. que representan las cxpresioiies de i>per.8. Finaltneiltc estamos listos piirn mostrar el irbol de iin programa TINY. regeat fact until x :P no calcula si x := O ) fact * x.0. x : = x . eii fiourd 18 Programa de muestra en el lenguaje TINY í Programa de muestra en lenguaje TINYcalcula el factorial 1 read x.jos.1 O. ( entrada de un entero ( if O < x then fact := 1. El pri)graim di: inriestra del u p í t u l o 1 que calcula el I'actorial de ttii entero se repite en la figura 3.

Muestre que es ambiguo..2 Dada la gramática A -+ AA / ( A ) / E .5 + 6 ) c.S. 8. puesto que la barra vertical también es un operador además de un metusímbolo): re. D<Suna derivación por la izquierda y por la dcrecha para la cadena {S. S. .( 4 + 5 * 6 ) 3. 3+4*5-6 b. b.S. árboles de análisis gramatical y árboles sintácticos abstractos para las siguientes expresiones: a.3 Dada la gramática e..rp -+ rexp "/ " rerp j re. 3. .s.xp rexp / rexp "*" / " ( " rerp " ) " 1 letra . S.CAP.8 .4 La gramitica siguiente genera todas las expresiones regulares sobre el alfabeto de letras (utiliznmos las comillas para encerrar operadores.9 Arbol rindctico para el programa TlNY de la figura 3.t. Describa el lenguaje que genera. 3 1 GRAMATICAS LIBRES DE CONTEXTO Y AN~LISISSINTACTICO Fisura 3. utilizando su gra- 3.fac..rp -+ exp opsuma term / tertn opsutna -+ + / term -+ term opmult. b. S. a. 3 * ( 4 . 3 .tor /factor opmult -+ * factor -t ( exp ) / número escriba derivaciones por la izquierda.

Es un cóuigo similar permisible en C? Justifique su respuesta.Qué asociatividad da su respuesta en c l inciso c para los operadores binarios? Explique su respiicsta. Escriba declaracictnes ~ i p o para una estructura de árhol sintictic« :ibstract« corresponC diente a la gramática del ejercicio 3.rec -t kxp-. . 3. además de permitir wrese la repetición del operador not como en la expresión boolema not not t r u e . d. Dihtje un árhol de análisis granuticnl para la cadena del inciso a. Ase&' también de que su gr~mática sea ambigua. los operadores and. o r y not.ven!-if / o t r o / F . y a and una precedencia rnás baja que a not. lerp -+ t r t m / list utoin 4 número / i d e n t i f i c a d o r list -+ ( lexp-sec ) krp-. del 3. ¿. T r a i l w t ~ I:I gr. .ir. Muestre que esta gramática es ambigua. p. Escriha una graiuátic.ia .igramas de sintaxis para la ENNF del inciso a. Dibuje un L h o l de an6lisis grainatical para la cadena i f ( 0 ) i f ( 1 ) otro else else otro b. i b.9 (Aho.sec le. .6. ¿. 3.serir-$-. 3.r IL91.8 Dada la graluática siguiente . 3.X) u N (N es el conjunlo (le los riiirnen>sn:ituralcs: &se Ii sección 3. adernás de los paréntesis.serite~z~. Dihuje el árbol sintáctico para la cadena ( a 23 ( m x y) ) que result.~para expresiones hooleanas que incluya las co~istantest r u e y f a l s e .a. b.6.rp / lrrp a.im6tiw del ejercicio 3.1 1 Dada la ecuacih de conjiintos X = ( X i. Vuelva a escribir esta gramática para establecer Ias precedencias ~orrectas'~. b. . Escriha nna derivación por la iyuierda y por la derecha para la cadena ( a 23 ( m x y) ) .7 a.6 a EHNF. no 3 6 Considere Iü gramática siguiente que representa expresiones simplificadas tipo IJSP: .iríit de sus tlecla~ i c i o n e s inciso a.10 a. Proporcione ima derivación para la expresiún regular (ab Ib ) * utilimnilii esta grümática.i operalos ) &>res (véase el ciipitulo 2 .igiii.5 b. c. Dibuje di. if ( r q ) sentencitr pirrr-else ~ ptrrte-else 4 e l s e sentenciu / E exp -) 0 / 1 a. Asegúrese de darle a o r nna prccedeiicia niás haia que a and.2. Scthi y Ullinan) Muestre que el siguiente intento para resolver la ambigüedtid del else ambiguo todavía es ambiguo (compare con la soluciún de la página 121): 3.Cuál es el propósito de los dos e l s e ? c.

3.3 es legal pero .3 ) al principio de una expresión. 3 1 GRAMÁTICAS LIBRES DE CONTEXTO Y ANÁLISIS IINTACTICO Muesue que el conjunto E = N u (N + N ) u (N + N t N) v (N N +.. de manera que -2 . la expresión 3 4 .rp) / ConstExp(iritrger) op i Plus / Minr~s/ Times esth cerca de una declaración tipo real en algunos lenguajes. 3. b.( . b.2 . ¿Son un problema la precedencia y la asociatividad con esta gramática? ¿. Escriba declaraciones tipo C para una cstructirra de árbol sintictico para la gr:irnática del ejercicio anterior.14 a. b.3.16 Vitelva a escribir e1 irbol sintáctico typedef al principio de la secckn 3.CAP. .1. Por ejemplo. Corno máximo se permite un signo de menos tinitario antes de cierto número o paréntesis izquierdo. lexp -r número / ( o p 1e.rp 1 lexp Esta gramática puede pensarse como representaciitn de expresiones aritméticas enteras simples en forma de prefijo tipo LISP.6. Conto niáximo se permite un signo de menos unitario en cada expresiitn. y dehe :ipiirrcer se evüluü a -.Lagramática es ambigua'? 3.12 Signos de nienos unitarios se pueden agregar de diversas formas a la gramática ilz expresión aritmética simple del ejercicio 3.2 .r)~-sec) q + + 1 .Cid1 respecto a las expresiones ( + 2 ) y ( * 2 ) ? b.. . pero . Se permite iin número arbitrario de signos de menos antes de números y paréntesis i ~ pierdas. 3. de modo que cualquier caso de los aritericxes es legal.3 no lo son.. Dibuje el jrbol sintáctico para la expresión ( . Adapte la BNF para cada uno de los cüsns que siguen de manera que satisfagan la regla establecida. Dos lenguajes de esta clase son ML y Haskell. a.3 ) ) .34 ( * 3 4 2 ) ) utilizando su respuesta para el inciso a. a.2 ) ? ¿. ¿Qué interpretación debería darse a las expresiones legales ( ..N + N ) u . Advierta que CI segundo higno dt: rnenos en zsta expresión es u n tireriiis Di~rrrrio. Escriba declaraciones de tipo de datos que implemeriten la sintaxis abstracta anterior. dado cualquier c«njunt« E' que satisfaga la ecuación.15 Se mencionó en la página 1 1 1 que la descripción tipo BNF de un árbol sintiictico abstracto e i p -t OpExp(op. 3.34 ( * 3 4 2 ) ) .exp. satisface Iir ecuación.3 es es legal.2 .2 (pfigina l I I ) para i~tilizar union una 5.S)y .3 * 4 2 se escribiría en esta gra( ~nfiticacor~io. a. E C E'.l* lexp-rp-src+ lexp-íec le.no un menos 11ilil. Escriba una expresiún sintáctica abstracta para la expresión ( 34 * ( 4 2 . b.ll-io. c. a. Muestre que. de manera que .2 y .13 Considere la ~iguiente simplificación de la gramática del ejercicio 3. Este ejercicio es para aquellos lectores que conocen alguno de esos dos lenguajes.e. .2 3 4 ) y ( ..2 .3 no lo es.

3.rrnática libre de contexto p:ir. b.17 1)enriiestrc que la graiii5tica del -jeiriplo 1. BEGIN END P.6 en EHNF.19 En itlgunos lengi~ajcs (por . i.is de par6ntesis hilance.Ejercicios 3. write x .jtmplo Modiila-7 y Ada).Es posible csct-ihir iinit pi-. tSir.20 a. donde tanto a. 3.irli)s. se espera que una declaraciiln de procediinicnto \e termine iiiedinnte sintaxis que iiicltiya el noinbre del procediinieiito. x := x+l. ('adu prel'ijo ir de ii.qwerii.5 (p. a.r Iiis d e n a s del iitciso a? Explique por qué.21 I ! prnduccih simple es iina selección de regla grntnatical dc la forma A m -t H.. il~mile es (te r iina cadena de cr y h. Advierta el i ~ s o iiotnbre de procedimiento P después del END de cierre. en Modula-2 un pnicediiiiiento se declara corno se muestra a coiitinuacih: PROCEDURE P. b. Escriba una grmática iloe genere el m i m o lenguaje que la expresih regular quc se muestra a contitiuacicin: 3. Escriba rmi expresión regular que genere el mismo lenguaje que la griirniítica ~iguierite: b.*A parti algún no tcriiiinal A. Fscriha iirtii gramítica seticihle nl contexto que geiiere ci~deiias la lijrnin w r .v para algiinii i )tieiie al menos taiitos p:irénte\is izquienlos cii- !no pai-6rttcsis derechos.i el prtigntnia TINY . read x. Por ejctrrplo. b. 11.lísted esperaría que itpareciernti proclucciones siniplcs con freciicncia en la definición de los lenguajes de pngrarnaeión? Jtistifique su respuesta. hlue9re que las prodricciones siinples pueden ser sistern6iicüntente elimittn<laide una gramAtica para eritregar una gra~iiliticasiii pr«duccioiies sirtiples que genere el iiiismo lengua.icititi fueran a tncniido cíclicas'! JiistiEiquc su respuesta.e que la gramática origiiial.igiiia 105) genera el conjunt~r to<i:is las d e de n.i -=.22 Una grani6tica cíclica es aquella en la que existe una deriv:teión . .n. contieiic cxact:riircntc el ~ni\itio h e r o de pxentesis irqoicr<Io\ y ilcreclim. doiide w es una c:alcna de pi~réntcsishalancetttlos \iempre que tengan las siguientes dos propied:ides: 1. 3.kr: de~iiucsire iitducci6n en la Iongiti~dde tina deriv:rcií.~de I'INY de l:i figura 3.24 P:i¡.23 Vuelva a escribir la gniirxitic. i i v = r1. .Usled esper:iría que las gr:im:iticas que definen lenguajes de progr:im. 3..18 a.) por 3. i.Se puedc verificar del un requeriniieiito así mediante rin analirador siiitáctico? Justifique su respuesta. 3. Mticstre que una grariiática cíclica es iimhigua. n 2.

( entrada de dos enteros 1 if v = O then v := O ( no hacer nada 1 else repeat temp := v.2) se tornó de la semántica denotacional. donde se pueden hallar demostraciones de muchas propiedades. Dibuje el árbol sint&crico TINY.CAP. b. Chomsky [ 1956. . write u { salida gcd de u y v original ) NOTAS Y REFERENCIAS Gran parte de la teoría de las gramáticas libres de contexto se puede encontrar en Hopcroft y Ullman [1979]. La solución de la página 121 para el prohlema del else ambiguo utilizando reglas libres de contexto. v := u . 3 1 GRAMATICAS LIBRES DE CONTEXTO Y AN~LISISIINTACTICO a. Para un estudio de la misma véase Schmidt [1986]. También contiene una relación de la jerarquía de Chomsky. así como el ejercicio 3. Información adicional está en Ginsburg 11966. 19751. u := temp until v = O end.6. y el primer uso de las gramáticas libres de contexto llegó en la definición de Algo160 (Naur [1963]).u/v*v. Dibuje el árbol de anúlisis gramiitical de TINY. Sólo mucho después se comprendió la importancia del tema para los lenguajes de programación. read v. se tomó de Aho. la que aplicó al estudio de los lenguajes naturales.25 Dibuje cl árbol sintáctico de TINY p a a el programa siguiente: read u. 19591 fue responsable de gran parte de la teoría inicial. de 3. tales como la indeterminación de la ambigüedad inherente.9. Hopcroft y Ullman [1986]. El enfoque de las gramáticas libres de contexto como ecuaciones recursivas (sección 3.

) . mientras que un analizador sintáctico inverso intentará las diferentes posibitidades para un análisis sintáctico de la entrada. Aunque los analizadores sintácticos inversos son más poderosos que los predictivos.3 4. El número I entre paréntesis significa que se utiliza sólo un símbolo de entrada para predecir la dirección del análisis sintáctico. pero un símbolo de búsqueda hacia delante es el caso mas común. y puede servirnos como un preludio para los algoritmos ascendentes más poderosos (pero también más complejos) del capítulo siguiente. Nos ayudará también al formalizar algunos de los problemas que aparecen en el modo descendente recursivo. La primera "L" se refiere al hecho de que se procesa la entrada de izquierda a derecha. es el método más adecuado para un analizador sintácdco manuscrito. Un algoritmo así se denomina descendente debido a que el recorrido implicado del árbol de análisis gramatical es un recorrido de preorden y.Capítulo 4 Análisis sinlaclito descendente 4.2 4. Los analizadores sintácticos descendentes existen en dos formas: analizadores sintácticos inversos o en reversa y analizadores sintácticos predictivos. también son mucho más lentos. por lo tanto. lo estudiaremos primero. Aquí no estudiaremos los andizadores sintácticos inversos (pero revise la sección de notas y referencias y los ejercicios para unos cuantos apuntes y sugerencias sobre este tema). La segunda "L" hace referencia al hecho de que rastrea una derivación por la izquierda para la cadena de entrada. se presenta desde la raíz hacia las hojas (véase la sección 3. por consiguiente. Estudiaremos esto a grandes rasgos más adelante en el capítulo. respaldando una cantidad arbitraria en la entrada si una posibilidad falla. El análisis sintáctico LL(1) se abordará después.4 4.5 Un analizador sintáctico descendente recursivo para el lenguaje TlNY Recuperación de errores en analizadores sintácticos descendentes Un algoritmo de análisis sintáctico descendente analiza una cadena de tokens de entrada mediante la búsqueda de los pasos en una derivación por la izquierda. son inadecuados para los compiladores prácticos. Las dos clases de algoritmos de análisis sintáctico descendente que estudiaremos aquí se denominan análisis sintáctico descendente recursivo y anális3 sintáctico LL(1). pero eso es poco habitual en la actualidad). El análisis sintáctico descendente recursivo ademhs de ser muy versátil.1 Análisis sintáctico descendente mediante método descendente recursivo Análisis sintáctico LL(I) Conjuntos Primero y Siguiente 4. El método de análisis sintáctico LL(I) debe su nombre a lo siguiente.3 del capitulo 3). aunque ya no se usa a menudo en la práctica. utilizando k símbolos de búsqueda hacia delante. (También es posible tener análisis sintáctico LL(k). de este modo. U n analizador sintáctico predictivo intenta predecir la siguiente construcción en la cadena de entrada utilizando uno o más tokens de búsqueda por adelantado. ya que requieren una cantidad de tiempo exponencia1 en general y. es útil estudiarlo como un esquema simple con una pila explícita. del inglés "Left-right" (algunos analizadores sintácticos antiguos empleaban para procesar la entrada de derecha a izquierda.

end fiwtor .irárnetro.CAP. n d e r : r n u t c h ( n d e r ). E l lado derecho de la regla gratiiaticaf para A especifica la estructura tiel código par este procedimierito: la secuencia de terminales y no teniiinales en una selección coirespond a concordancias de la entrada y llamadas a otros procedimientos. av:inza la entrada s i tiene éxito y declara un error s i no lo tiene: . 4 I ANALISIS INTACTICO DESCENDENTE Tanto el análisis sintáctico descendente recursivo como el análisis sintáctico LL(I) requieren en general del cálculo de conjuntos de búsqueda hacia delante que se conocen como conjuntos Primero y Siguiente. mutch( ) ) . En este pseudocódigo partimos del Supuesto de que existe tina variable token que mantiene el siguiente token actual en la entrada (de manera que este ejemplo utiliza un símbolo (le húsqueda hacia delante).1 ANALISIS SINTÁCTICO DESCENDENTE MEDIANTE MÉTODO DESCENDENTE RECURSIVO 41 E mitodo básico descendente recursivo 1 l La idea del análisis sintjctico descendente recursivo es muy simple. Como primer ejemplo consideremos la gramática de expresicín del capítulo anterior: exp --+ exp cipsrmn term / temz 0/7F11mU -t + / term --+ terrn oprnultfuctor /factor opmutt -3 * jilctor -+ ( exp ) / n ú m e r o y consideremos la regla gramatical para un. Observamos la regl gramatical para un no tenninal A como una definición para un procedimiento qlie reconocer una A. Entonces continuaremos con un análisis de un analizador sintáctico de TlNY construido de manera descendente recur siva y cerraremos el capitulo r o n una descripción de los métodos de recuperación de errores en el análisis sintáctico descendente. end case . exp . mientras que las seiecciories corresponden a Las alternativas (sentencias case o it) dentro del código.U n procedimiento descendente recursivo que reconoce un fiictor (y al cual llamaremos por el mismo nombre) se pue& escribir ei pseudocódigo de la manera siguiente: procedure factor .fizctor. pospondremos un análisis de los mismos hasta que hayamos introducido los algoritmos básicos. 4. begin case token of ( : rncitch( ( ) . 'Tanihién dituos por hecho que existe un procediniicnto mutch que compara el token siguiente aciual coi] su p.' Puesto que se pueden construir analizadores sin construir estos conjuntos de manera explicita. else error .

2 Repetición y selección: el uso de EBNF C:orisiilcre coino un segundo cjcinplo la regla griiniatical isitnplificad:~)para una witencia if: l h o se puede traducir c ~cil pr«cediniiento . Ilatn~rrlo. ( eri/. El c6tligo parafiit.tor t a n r h i h supoiie qiie se ha definido i i r i procediniicnto erp qtlc ptiedc ti1 . 1'01- 41.gr.rp.irna~-á iJ. f i ~ i ~ tIl. de manera que es necesaria una prucha.iiir..f¿ictor y el procctlim i e i i t o . capaces i~r a escribir prticc~iiiriietitits desceridenies recursivos para de 1laln:irse entre sí. se sahe que I. las reglas restantes en la gr:irnática ilc cxpresi6n no es l a t i E c i l coriio para)rr. e11 l. como verenros a eorrtinriacicíti.~ M Ilainadii nierrch() ). rin iinalimdor siiitáctico ilesccndente rectirsivo p:ira Ii gKintática tlc expr.esi6n el procediitiieriro evp IIarnari íi tcnn. Se puede suporier que se iinprinle tin nieiiszijc (le error y se sale del progrma.Sin criibnrgo.Analisis iintactita destendente mediante método descendente returiiva el riionierrto tlejareriios sin cspccil'icar el procc<Iiinietito cJrrorque es 1l:itnado tanto por iririrdi e01i10 por fiictor. i>e.)i( ) y i r i c r t ~ h ( n u m i ~ e r ) riables <'.is ire~ttr.xpri~irdTok~~ci y ~okcri I las n~istn.rctor. de ~n.\ que todoseestos procedimientos deben ser.ror y requiere el iiso de ERNF.is vaAdvierta que err las Il.~d. n o se sea p w d e suponer q w i i ~ k r n un pnrt%te\is derecho.is. el proccdiiriicnto terin llamará a.iner.1ciucta111e11tc.

i la BNF. es natural escribir un analizador sintáctico que haga concordar cada token else tan pronto como se encuentre en la entrada. y esto conducida a un inmediato ciclo recursivo infinito.up opsirmcr renn / term Si estamos intentando convertir esto en un procedimiento exp recursivo de acuerdo con nuestro plan. ya que tanto e.q como terrn pueden comenzar con los misnios tokens (un número o paréntesis izquierdo). Considere ahora el caso de una exp en la gramática para expresiones aritméticas simples en BNF: exp -t e.terrn) es realmente problemático. hegin term .do De inanera similar. donde los corchetes de la EBNF se traducen en una prueba en el c6digo para Serit-if De hecho. . Esto corresponde precisamente a la regla de eliminación de ~imbigiiedad incís cercanamente anidada.xp . while tokrrz = + or token tnntch (toketz) . = . .suma term. aun cuando esta gramática es ambigua (véase el capítulo anterior). Las llaves expresan la repetjción que se puede traducir al código para un ciclo o bucle de la manera siguiente: procedure e. la regla EBNF para terrn: terrn -+ factor { opmultjactor ] se convierte en el código procedure terrn . end while . de modo que una gran~cítica debería siempre traducirse en EBNF si se está utilizando el modo descendente recursivo. end while . end terrn . Tratar de probar cuál selección usar fexp =t rrxp op. Advierta tanibieui que. La solución es utilizar la regla de EBNF cxp =t term { opsumn terrn ) en su lugar. hegin factor .que . 10 primero que potlemos intentar hacer es llamar a e-rp niismo. terrn . u p . fiictor . while token = * do rncitch (token) . o bien. end exp . la notación EBNF está diseñada para reflejar muy de cerca el código real ile un analizador sintáctico descendente recursivo.

Podemos asegurarnos de que las operaciones son wociativas por la izquierda realizando las operaciones a medida que circularitos a través del ciclo o bticle (suponemos ahora que los procedimientos de anilisis sintictic« son funciones q~te devnelven un resultado entero): function exp : rrzte. En realidad. cuyo código en C está dado en la figura 4. Sin einbargo. y lo utilizaremos para proporcionar un analirador sintáctico completo para e l lenguaje TINY de la sección 4. optamos por utilizar llarnadas a getchar y scanf en lugar de un procedimiento getToken. terrrp := terflp trrrn . Una cuestión que surge con este código es s i la asociatividad izquierda iniplicada por las llaves (y explicita en la BNF origiiial) todavía puede rnanlenerse. t (páginas 148-149). return terrip . ya que su única función es igiialar a los operadores: En su lugar haremos esta concord. while token = o r tokrn = do case token of :rrratt h (+) . También debe tenerse c l misino cuidado al programar las acciones durante la construcción de un irbol sintictico. .niZtica entera simple de nuestra gramitica. + - + + end case .siíirrcr o p ~ i r l t y conio procetliniientos separados. Un ejemplo de esto está en el pseudocódigo anterior para a ctp. existen algunas dificultades y debemos tener cuiciado al progra m r las acciones dentro del código. Suponga. Este método de convertir reglas gramaticales en EBNF a código es rriuy poderoso. por ejemplo. end exJJ. l o que generaría un error).Análisis sintáctico descendente mediante método descendente recursivo 147 Aquí eliminamos los nu tcrniinales o[~. tetirp := teinp . Viiiios que la asociatividad por la izquierda en tina EBNF con i repetición puede rnanlenerse para propósitos de cálculo al realizar Zstc . . var ternp : iriteqer . be& trtnp := t e r n ~ .: r~ziztch(-) .terrrz .rp y t'rm. nicdida que sc ejecuta el ciclo. Empleanios estas ideas para crear una calculadora siniplc funcionando. donde en lugar de escribir un analizador léxico completo. donde tiene que ocumr una concordancia de las operaciones antes de las llamadas repetidas a term (de otro modo term vena una operaciijn como su primer token.tncia dentro dc'e.de haber probado con éxito un token (ponemos esto en el procedimiento »iutclz eli el pseudocódigo). el protocolo siguiente para conservar la variable global rokerl actual debe estar rígidamente adherido: token debe estar eslablecido antes que comience el análisis sintáctico y getTokert (o su equivalente) debe ser Ilainado precisamente dcspués.qer . y de manera similar para teirn. que deseamos escribir una calculadora descendente recursiva para la arit. end while .4.

Figura 4.1 íiniclo) Una calculadora descendente recurriva para aritmética entera simple .

Anal~s~s rintáct~co descendente med~antemetodo descendente recursivo 149 .

En realidad. newtenzp := rnakeOpNode(+) . newtemp := makeOpNode(-) .temp) := remp .con árbol sintáctico ~j + el nodo que representa la suma de 3 y 3 debe ser creado (o procesado) antes del nodo raíz (. newtenzp :sqritmTree . Sin embargo.p . esto ya no corresponde a una construcción descendente del árbol gramatical o sintáctico. r~ghtClzrld(ne~utrn~p)term . : function exp : ~yntc~xfiee . := fetnp := t)~ewtemp .do newtemp := ~nukeOpNode(token) . begin t m p := term .. . . return trmp . while token = + or token = .el nodo que representa su suma con 5). := I ~qht('/iilrl(nrwte~np) t e n . temp := newtemp . end ~ i . rightChild(newtemp) := trrm . end case . end while . var trmp. o la versión más simple function eifp :syntaliTree . IeflClzild(ncw. newtenzp :syntaTree . tenemos el siguiente pseudocódigo para el procedimiento exp: : . /l-fiChild(newtrmp):= temp . return temp .do case token of + : match (+) . := temp := newternp . end while . var temp. white taken = + or toXen = . begin temp := term . match (token) .:match (-) . lLlfiClziltl(t~e>vten~p) temp . end exp . Al traducir esto a la construcción de árbol sintáctico real. si consideramos la expresión 3+4+5.

/ ¿ ~ t o(véame los ejercicios). r En contraste. thenChi/c(temp) := stutetnewt . Formalizaretnos los detalles de este cálculo en la secciOn 3.xp no construye invariableniente un nuevo nodo del árbol. if token = else then ~mtch (else). end i'fStatentent . La flexibilidad del análisis sintáctico descendente recursivo. que recibe un token de operador como un piirámetro y devuelve un nodo de árbol sintáctico recién construido. Pueden surgir diversos problemas. si no hay operadores. elseChi/d(tetnp):= .Análtsis sintáctico descendente medtante método descendente recursivo 151 En este código utilizainos una nueva función nrnkeOpNod'.rtChikd(tcnt[~)= exp . También indicamos la asignación de un árbol sintáctico p como un hijo a la izquierdas a la derecha de un árbol sintáctico t al escribir lefiCltild(t) := p o riglztChild(t) := p. exp sinlplemente pasa de regreso al árbol recibido desde la Ilaniada inicial a term como su propio valor. al permitir al programador 0 aa ajustar L programación de las acciones.3. pero todavía es de propjsito específico. estos métodos son adecuados para construir un malizador sintáctico completo. También se puede escribir el pseudocódigo col~espondiente para tcrm y . el &bol sintictico para una sentencia if se puede construir de manera estrictarnente descendente por medio de un analizador sintáctico descendente recursivo: function if'Stutetnent : syntuxTwe . Esto se debe a que una llamada a e. var temp : . En tercer lugar. match ( ( ) . donde se construye una BNF transformada que en esencia es equivalente a la EBNF. 31 escribir el código para una protluccicín c: . te. Una alternativa para el uso de la EBNF se estudia en la siguiente sección. 1 hace el mitodo de elección p r analizadores sina tácticos generados a mano. Un problema de decisión de esta naturaleza requiere el cálculo de los conjuntos Primero de u y P: e1 conjunto de tokcns que pueden iniciar legalmenle cada cadena. : end if . temp := mctkeSttntNotlc(if) . Con este pseudocódigo el procedimiento mp en realidad construye el árhol sintictico y no el á r h l de análisis gramatical. cuando se formula una prueba para distinguir dos o niás opciones de regla gramatical . begin rnatch (if ) . 41. En segundo lugar. En primer lugar. Dehedarnos estar conscientes de que pueden ser necesarios más métodos formales en situaciones complejas. else elseCl~ild(ternp)= ni1 .3 Otros problemas de decisión El método descendente recursivo que describimos es muy potleroso. si tanto a como p comienzan con no terminales.syntd'ree . : inutch ( ) ) . \ puede ser difícil decidir cuándo utilizar la selección A + w y cuándo emplear la selección -t 0. puede ser difícil convertir una gramática originalmente escrita en BNF en forma EBNF. Con un lenguaje pequeño cuidadosamente diseñado (tal como TINY o incluso C).stutenterit . .

Considere. niatch (conc«rdar) S -+ e :iwptar . En este análisis a manen de introducción usaremos la gramática simple que genera cadenas de paréntesis halanceados: (véase el ejemplo 3.1 E método básico del análisis sintáctico LL(1) El análisis sintáctico LL(1) utiliza una pila explícita en vez de llamadas recursivas para efectuar un análisis sintáctico.1. El con. Tabla 3 1 Acciones de. como se inlo drca en el siguiente renglón de la tabla. La primera columna numera los pasos para una referencia poslerior. Una inquietud tinal que puede requerir el cálculo de los conjuntos Primero y Siguiente es la detección temprana de errores. Se utili~a signo monetario para marcar el final de la entrada [esto corresponde a un un token de EOE (por sus siglas en inglés) o fin de archivo. lo que permitiría uua detección m& temprana del error. La tabla 4. una pila que contenga el no terminal S en la parte superior aparece como y los elementos adicionales de la pila 5on empujados a la derecha. Es títil representar esta pila de manera estándar.3.5 del capítulo anterior). el programa calculadora de la figura 4.anilisis rintáctico de un analizador rintactico descendente Pila de análisis sintáctico Entrada Acción S-t ( S )S I 2 3 1 5 0 $S $ S S ( O $ O ) match (concordar) $ S )S $5) $S $ a. En esta tabla hay cuatro columnas. en tanto que la segunda muestra el contenido de la pila de análisis sintáctico. puesto que tales tokens indican que A puede desaparecer de manera apn~piada este punto del análisis sintáctico. Dada la entrada ) 3 -2 ). El cálculo de este conjunto también se precisará en la sección 4. Marcamos la parte inferior de la pila con un signo monetario ($). La tercera columna de la tabla 4. con la parte inferior de &a hacia la izquierda y la parte superior hacia la derecha. De este modo. S % S+ 8 ) 0. cual cambiartí la pila y (posiblemente) la entrada. el analizador sintáctico descenderá desde exg hasta term hasta factor antes de que se informe de un error. mientras que se podría declarar el error ya en exg.1 muestra las acciones de un analizador sintáctico descendente dada esta gramática y la cadena ( ) . puesto que un paréntesis derecho no es un primer carácter legal en una expresión. por ejemplo.) l 42.puede ser necesario conocer cuáles tokens pueden aparecer legalmente después del no teren minal A. generado por un analizador Iéxic La cuarta columna de la tahla proporciona una descripción abreviada de la acción toma por el analizador ~intáctico. de manera que las acciones de un analizador sintáctico LL(1) se puedan visualizar rápida y lijcilmente.junto Primero de exp nos diría esto. Los ~ímbolos entrada están enumerados de i~quierdaa dede recha. (La detección y recuperación de errores se analiza con más detalle al final del capítulo.1 muestra la entrada. Este conjunto se llama conjunto Siguiente de A.

que podría ser llamada generar.1.. Estas dos acciones.e quedan vacías. de donde ha reconocido el token de entrada y puede descartarlo tanto de la pila como de la entrada. Realiza esto con miras a producir el tokeii de enímh actual en la parte superior de la pila de análisis sintáctico. en el paso l del análisis sintác~ico la tabla 4. la pila y la entrada .Analiris iintactico LL(I) 153 Un rinalizador sintáctico descendente comienza al insertar el símbolo inicial sohre la pila. La segunda acción iguala o lrace concordar un token en la parte superior de la pila con el siguiente token en la entratla (y los desecha a anibos sacáitd«los de la pila y avanzando a la entrada). cti. De este ~iiodo. Hacer concordar un token en la parte superior de la pila con el siguiente tokeri de entrada. cadena de reemplazo cx de la BNF se debe la insertar en rrvrrrci en la pila. sintáctico.inthiC~i cl p. "o [. Es importante advertir que en la acción .it~:ilisis gro~nntic. podemos agregar acciones que e de construcción de nodo a ~nieditla cad:i no termitial o tcrinirial s inscrta en la pila.tntlo .ictico. esquema general para realizar con 6xito un análisis siutácieticti un descendente es $ $ aceptar En el caso de nuestro ejeniplo. y 2. \i~ir. son las dos acciones básicas en un analizador sinthctico descendente..il (correspondiente al sítnhol« iriiciali se i-n l. en Por ejemplo. l.. después de una serie de acciones. Acepta una cadena de entrada si. e símbolo inicial es S. la indicümos al escribir la seleccicín de RNF utilizaika en el reemplazo (cuyo lado izquierdo debe ser el no terniinal que actualmente esrá en la parte superior de la pila). Si queremos construir un árbol de análisis gramatical a inedKIü que iivanra el ~u~ilisi.in:ili4.. cl i i o h raíl dcl 2rhol di. en la parte superior de la pila. a saber. (3s )S [S-+ ( S ) S ] [S+el . la pila y la entrada son $S O $ y la regla que usamos para reemplazar S en la parte superior de la pila es S -+ ( S ) S.( .i que eso asegura que la cadena a asrihará a la parte superior de la pila en el orden de izquierda a derecha).i iahla 1. Reemplazar un no terminal A en la parte superior de la pila mediante una. y la cadena de entrada es ( ) . de manera que la cadena S ) S ( se inserta en la pila para obtener $S)S( ()F. I corresponde precisamente a los pasos de una derivación por la izquierda de la cadena ( ) : S-. un paréntesis izquierdo.iw 2 de 1. y efectuamos una acción de concordur o rnatch para obtener la situación siguiente: 3S)S ) $ La lista de acciones generadas en la tabla 4.yerzerc~r. Ahora generamos la siguiente terminal de entrada. 1 Un analizador sintáctico descendente realiza su análisis al reemplazar un no terniinal en la pane superior de la pila por una de las seleccioiies en la regla gramatical (en BNF) para ese no terminal.(y.Y ~.Y + el Estci es caracteristico del análisis siritáctico desccntlente. cadena a utilizando la selección de regla gramatical A -+ a. De esie rnotio. cimstriiyc a1 principio ilci . indicamos esta acción escribiendo la palabra rncitch (conrorri~ir). La primera acción.

Sin embargo. Puesto que hay sólo una pr&ducción no vacía para S. Esto completa todos los casos bajo la regla l . en vez de simplemente 1 no terminales o terminales mismos. 1 (y sólo . involucrando a los conjuntos Primero y Siguiente ya mencionados. Para hacer esto efectivo tenemos que modificar la pil de manera que contenga apuntadores para estos nodos construidos. y esta opción de producción se agrega a la entrada M[S. A = S. la regla 2 se aplica con u = e. dado un token u en la entrada.cada cadena derivable de S debe ser vacía. 10s nodos para cada uno de los cuatro símbolos de reemplazo s constmyen a medida que se insertan los símbolos en la pila y se conectan como hijos respect al nodo de S que reemplazan en la pila. que selecciona la regla gramatical que va a utilizar para A cuando se reemplaza A en la pila. donde ri. La idea dentro de estas reglas es la siguiente.?1 inicia con todas sus entradas vacías. entonces se agrega A -+ u a la entrada de tabla M[A. Veremos también cómo se puede modificar este proces para producir una construcción del árbol sintáctico en lugar del .u].ubol de análisis gramatical. eliminamos el hecho de que F debe agregar $ a 7 ) y M ?e puede imaginar como la tabla de "movimientos".iIIí). cuando un no terminal A está la parte superior de la pila de análisis sintáctico. o bien. donde S es el símbolo inicial y u es un token (o $). S -+ ( S ) S. a saber. Cualquier entrada que permaneLca vaci después de la constnicción representa errores potenciales que se pueden presentar durante u análisis sintáctico. comenzar con un ( paréntesis izquierdo. paréntesis derecho y $) y dos opciones de producción. hay un no terminal (S). B = (. no es necesario tomar u decisión cuando un token está en la parte superior de la pila. Si A -+ u es una opción de producción. Podemos expresar las selecciones poslbles construyendo una tabla de análisis siniáctico LL(1). y se presenta una concordancia. Tes el conjunto de terminales o tokens (por conveniencia. si u es un token que puede venir legalA mente después de A en una derivación. $1. Ya que S * ( S ) S. Si A -+ u es una opción de producción. Considere como primer ejemplo la gramática para paréntesis balanceados utilizada en la wbsección anterior.CAP 4 1 ANÁLISIS SINTÁCTICO DESCENDENTE S se reemplaza por ( S ) S. pero en la siguiente seceió desarrollaremos algoritmos que permitan hacerlo así. Esto conipleta la . 4. y se presenta un error. Suponemos qu la tabla M[N. de rnancra que S + E se agrega a M[S. S -+ e también se agrega a MIS. Estas reglas son difíciles de implementar de manera directa. y existen derivaciones a ** e y S $ =+* f3 A u y. Advierta que un caso especial de la regla 2 ocurre cuando u = u. entonces deseamos ~eleccionar -+ u para hae que A desaparezca. En la y regla 2. puesto que es el mismo que token de entrada actual. 2. Puesto )J.2. deseamos seleccionar una regla A -+ a si u puede producir una u para comparar. si A deriva la cadena vacía (vía A -+ a). En contraste. u = ) y y = S $. y existe una denvac~ón ** a p. Una tabla de esa naturaleza es esencialmente un arreglo bidimensional indizado p no terminales y terminales que contienen opciones de producción a emplear en el paso piado del análisis sintáctico (incluyendo el signo monetario $ para repreientar el final entrada). o no lo es. Llamamos a esta tabla M[N. entonces se agrega A -+ a a la entrada de tabla M[A. Agregamos selecciones u opciones de producción a esta tabla de acuerdo con las regl siguientes: 1. En la regla 1.2 El algoritmo y tabla del análisis sintáctico LL(1) Al utilizar el método de análisis sintáctico apenas descrito. estas reglas se pueden realizar a mano. en casos muy simples. basada en el ken de entrada actual (la búsqueda hacia adelante). debe tomarse una decisión. T].a]. a es un token. 1 que S $ = VS (la derivación vacía).Aquí N es el conjunto de no terminales de la gr tica. tres tokens (paréntesis i~quierdo.

establecenius la siguiente Una gramática es una gramática I. se pueden construir reglas de no ambigüedad en la construcción de la tabla para casos ambiguos simples tales como el problema del else ambiguo.2 Algoritmo de análisis rintáctico LL(1) basado en tabla . dada una gramática LL(I). Este algoritmo produce precisamente las acciones descritas en el ejemplo de la subsección anterior.construcción de la tabla de análisis sintáctico LL(I). Figura 4. 4. porque la definición implica que un analizador sintáctico no ambiguo se puede construir empleando la tabla de análisis sintáctico LL(I).2 se proporciona un algoritmo (le análisis sintáctico que utiliza la tabla de análisis sintáctico LL(1). de una manera similar a la descendente recursiva. esta tabla debe proporcionar opciones únicas para cada par de no terminal y token: De este modo.L(l) si la tabla de análisis sintáctico LL(1) asociada tiene como máximo una producción en cada entrada de la tabla.2 Mientras que el algoritmo de la figur~ requiere que las entradas de la tabla de análisis sintáctico tengan conlo máximo una producción cada una. la que podemos escribir de la manera siguiente: Para completar el algoritmo de análisis sintáctico LL(I). Una gramática LL(1) no puede ser ambigua. en la figura 4. En realidad.

.w -+E else sentencia . la entrada Mrpcirte-else. ya que no producen ninguna acción. donde no enumeramos los terminales de paréntesis ( o ) en la tabla. purte-e1.. dada la cadena if ( 0 ) if (1) otro else otro (Para ser más concisos utiliramos las siguientes abreviaturas en esa figura: sentencia = S. else]contiene dos entradas. la gramática simplificada de las sentencias if (véase el ejemplo 3.6 del capítulo 3): sentencia -+ sent-if / otro senr-if+ if ( exp )sentencia parte-else parte-else + else sentencia / E e.2.sentencia sent-if if sentencia --+ sent-if sent-if+ otro sentencia -+ otro else O 1 $ if ( exp ) sentencia parte-else parteelse parte-else -t panee1. TI . Con esta modificación la tabla 4. Esto de hecho corresponde a la regla de eliminación de ambigüedad más cercanamente anidada. otro = o. if ielse = e. al construir esta tabla poúríamos aplicar una regla de eliminación de ambigüedad que siempre preferiría la regla que genera el token de búsqueda hacia delante actual sobre cualquier otra.) Tabla 4. la tabla 4. por consiguiente.w = L.) . Como en el caso descendente recursivo.2 Tabla de análisis sintáctico LL(1) para sentencias if (ambiguas) M[N. - .3 muestra las acciones de análisis htáctico del algoritmo de análisis sintáctico LL(l). Por ejemplo. (La construcción de esta tabla \e explicará en forma detallada en la ~iguiente sección.IAP. por ejemplo. exp = E. se preferiría la producción parte-else -+ else sentencia en vez de la producción parte-else -+ E.2.2 se hace no ambigua. y la gramática se puede analizar sintácticamente como ~i fuera una gramática LL(1). 4 1 ANALISIS SINTÁCTICO DESCENDENTE Considere.e exp exp -+ O exp -+ 1 En la tabla 4. correspondientes a la ambigüedad del else. purte else .seni-if = I. y.rp -+ O / 1 La construcción de la tabla de análisis sintáctico LL(1) da el resultado que se niuestra en la tabla 4.

1 SLLS)E(i $Ll. se pueden irtilizar para generar a~it<tmáticarnente un analizador sintáctico L L ( 1) (véase la seccicín de notas y referencias). concornla (match) concuerda (match) E-+ O concuerila (match) concuerda (match) S-. L o 'j L 1.3 Aaianer de análisis sintáctico LL( 1) para IentPntiar if utilizando la reglade eliminación anidadade ambigüedad mas próxima Pila de análisis sintáctico $ .S ) 'GLLS Y l. N o ohstante. del m i m o modo que EBNF no garantizaba resolver todos los problemas al escribir un analirador sintáctico descendente recursivo. La recursión por la izquierda se utiliza por lo regular para hacer operaciones :isociativas por la i/.U Eliminación de recursión por la izquierda y factorización por la izquierda En la repetición y seleccicín en el análisis sintáctico L L ( I ) se presentan prohlemas similares a los que surgen en el análisis sintáctico descendente recursivo.quicrda. por esa r ~ z ó n no hemos aún podido dar una tabla de . en vez de eso debemos volver a escribir la gramática dentro de la notación BNF en una forma que el algoritmo de análisis sintáctico LL(1) pueda aceptar.S)E( FLS E $¿S)O $¿S) L S X l.S) E ( i SL. $LSe $LS $Lo 6L a $ $ S9 1 l + i ( E ) Sl.ja de que sus aplicaciones se pueilen autoniatizar.Tabla 4.o concuerda (match) L-eS concuerda (match) S -+ o concuerda (match) L+ o aceptar (accepr) 4.inálisis sintáctico L L ( I ) para la gramitica de expresión aritmética simple de secciones anteriores. de rnanera que.LS)E( SL1. donde Eliminación de recunión por la izquierda . son muy útiles en la niayoría de las situaciones prácticas y tienen la venta. Resolvimos estos problenias para descendente recursivo utilizando notacicín EBNF.S) E $ L 1.S á1 Entrada i(0)ifl)oeo'G i(O)i(l)oeo$ i(O)i(l)oeoF (O)i(l)oeo$ O)i(l)oeo$ O ) i ( l ) o eo $ )i(l)oeo$ i(l)oeo$ i(l)oeo$ i(l)oeo$ i ( l ) o eo $ (1)oeo'G l)oeo$ )oeo$ oe o$ o e o$ e o$ e o$ o 'j O Acción - $l. 1 1 . Debe hacerse énfasis en que nada garantiza que la aplicación de estas técnicas convertirá una gramática en una gramática LL(l). suponiendo un resultado con éxito. Consideraremos cada una de estas técnicas. N o podemos aplicar las mismas ideas al análisis sintáctico LL(L). como en la grarnitica de expresión siniple. Las dos técnicas estándar que y aplicamos son la eliminación de recursión por la i~quierda l a factorización por la izquierda.S)E(i $L.+ i ( E ) SL 1--i(E) S L c«ncner&a (mttch) concucrtla (~i~atch) E+ 1 concuerda (match) S -.

rp opsuma tenn / term La forma de esta regla es A -+ A u / P. La selección A -+ p es el caso base. / l. mientras que A -+ A a es el caso recursivo. . ~ P .+~A'/E Ejemplo 4. 4 1 ANALISIS SINTACTICO DESCENDENTE hace las operaciones representadas por (p. lo que pasa si escribimos opsuma: exp -+ exp + term / exp . . . pero incluiremos una solución para este caso con el fin de completar la exposición. . B--+AaI . Tales reglas casi nunca se presentan en gramáticas de lenguajes de programación reales.+ B ~ .CAP. donde la recursión se presenta sólo dentro de la producción de un no terminal simple (como exp). con A = exp. . tal como en las reglas A . .3 vimos que estas reglas gramaticales generan cadenas de la forma Bu".rp 4 term exp' expt -+ opsuma term exp' CASO 2: Recursión Ior la izquierda . Para eliminar la recursión por la izquierda volvemos a escribir estas reglas gramaticales divididas en dos: una que primero genera (3 y otra que genera las repeticiones de u utilizando recursión por la derecha en vez de recursión por la izquierda: A-tpA' A'-. son cadenas de terminales y no terminales y P no comienza con A. nmediata general . Al volverla a ewibir para eliminar la recursión por la izquierda obtenemos que e. . i. para n 2 O. Un caso más difícil es la recursión por la izquierda indirecta. CASO 1: Kecursión por la izquierda inmediata simple En este caso la recursión por la izquierda se presenta s61o en reglas gramaticales de la forma ~ . donde hay sólo una opción de producción recu siva por la izquierda simple. Éste es el caso rnás simple de recursión por la izquierda./ .+ ~ ~ . . ~ ~z Al u ~ / P ~ ~ P ~ / . Otro caso ligeramente rnás complejo es aquel en que más d una opción es recursiva por la izquierda. u = opsuma ternz y f3 = term.1 Considere de nueva cuenta la regla recursiva izquierda de la gramática de expresión ~imple: exp -+ e.+ ~ u / p donde u y (. Consideraremos primero la recursión por la izquierda inmediata.2.term / terrn Estos dos casos involucran la recursión por la izquierda inmediata.suma asociativas por la izquierda. En la sec? ción 3.1 I /E Éste es el caso en que tenemos producciones de la forma i i .

pero por L regulx en formas muy restringidas. puesto que cada paso en un ciclo así sfiio incrementaría el índice. . digamos. A. coniien~iin con .trrtn a p ' / E * § CASO 3: Reiursión por la izquierda general E l algoritnio que aquí describimos está garantizado para funcionar . + A P por la regla l A .filo en el caso de gr:init tic as sin produccioues E y sin ciclos. F~qura4 1 Rlgor~tmopara ehminaoón de retundn por la aquierda general for i := 1 t o m do forj := 1 to i. . . Esto elimina to&s L s reglas de la forma Ai + Ai y con 1 a j 5 Si hacemos esto para cada idesde I hastam.!. . A. + c u l p I u Z P / . donde un cielo es una derivación de por lo menos un paso que comienza y fir~alira el mismo no terminal: A con LY -J* A. este caso In solución es seniejante a l En la tlel caso simple.3. y las gramáticas con ciclos nunca aparecen como gramáticas de lenguajes de programación. $.Análisis s~ntáct~to LL(I) 159 doiide ninguna de las Pl. .-tu. . de modo que este algoritmo casi siempre funcionará también para estas gramáticas.. . 1. y posteriormente eliminar todas las recursiones izquiertlas que 1 0 incrementen el índice de las A. E l algoritmo se proporciona detalladamente en la figura 4...1 do reemplazar cnda selección de regla grunzuticul de la f o n o A. . /u~@. entonces ningún ciclo recursivo puede ser izquierdo.3 Conídere la gramática \iguiente: (Esta gramática es couiplet~irrientc artit'icid. ES casi seguro que un ciclo ocasionará que un analizador sintáctico entre en un ciclo infinito.tje de programación e&r<lar.2 Considere la regla gramatical Eliminemos la recursifin por la i~cltiicrda la nianera siguiente: de uxp rp terrn rxp' etprrp + term eup' / .don<leA.. . Estas gramáo ticas tienen producciones E .. ya que esta situacicín rto se presenta en ningítri Iciigu./cw../ ukes la reglcz actziul puro A. con las opciones extendidas en consecuencia: Ejemplo 4.. . E l algoritm« funciona al elegir un oden arbitrario para todas los no terminales del lenguaje..) . . . y de este m& no se podría volver a alcanzar el índice original./ Ejemplo 4.

4 1 ANALISIS SINTÁCTICO DESCENDENTE Consideraremos que A tiene un número más alto que A para propósitos del algoritmo (es decir. La eliminación de la recursión por la izquierda no cambia el lenguaje que se está reeo nociendo. De este modo obtenemos la gramática A+BaAr/c. pero modifica la gramática y. Considere. Como vimos. de modo que la única acción es eliminar la recursión por la izquierda inmediata de A. La gramática resultante es A+BnA1 /cA' A1+aA' / E ~ + ~ b / ~ b / d Allora el ciclo externo se ejecuta para i = 2.4' A'+riA' / E B + B ~ / B u A ' ~ / c A ' ~ / ~ Finalmente.jecuta dos veces. y el ciclo interno se ejecuta una vez. también los iúboles de análisis gramatical.1. Si elimin mos la recursión por la izquierda inmediata como en el ejemplo 4. Figura 4.4 Gramática de expresión aritmética simple con recunión por la izquierda eliminada exp 4 f e m exp' exp' 4 opsumu term exp' / E opsumu -+ + 1 ferm -t factor term' term' + oprnulffactor ferni' / e opmult + * factor + ( exp ) / número - Ahora considere el árbol de análisis graniatical para la expresión 3+4+5: . Como n = 2. la gramática es recursiva por la izquierda para expresar la asociatividad por la izquierda de las operaciones. el ciclo interno (con índice j ) no se ejecuta.CAP.4. En este caso eliminamos la regla B -+ A b reemplazando i\ con sus opciones de la prime regla. obtenemos la gramática dada en la figura 4. la gramática de expresión simple que hemos estado utilizando corno ejemplo estándar. En realidad. este cambio ocasiona una complicación para el analizador sintáctico (y para el disefiador del analizador). c o n j = 1. Cuando i = 1. eliminamos la recursión por la izquierda inmediata de B para obtener A+Buit'/c~' A'+aAt lc B+cA'bBf/dB' B1+bB'/aA'b~'/~ Esta gramática no tiene recursión por la izquierda. A l = A y A2 = B). en consecuencia. una vez para i = 1 y una vez para i = 2.3 se e. por ejemplo. el ciclo externo del algoritmo de la ftgura 4.

al nodo raíz e. Este nodo tiene únicamente un hijo E y simplemente pasa el valor -6 de regreso. Para hacer esto. Este nodo exp' a su vez debe restar 5 y pasar el valor -6 hacia el nodo clup' final.1 hacia . Este nodo cxp' debe entonces restar 4 y pasar el nuevo valor .i&a daría origen a los procedimientos e.rp y es el valor final de la expresión. La gramática con esta recursión por la izquierda elirnin. Considere cómo podría funcionar esto en un analizador sintácticodescendente recursivo. Este valor se devuelve entonces hacia la parte supetior del árbol. No obstante. hegin ternz . un analizador sintáctico todavía construiría el drbol sintáctico apropiado asociativo por la izquierda: No es ccimpletamente trivial hacer esto utilizando la nueva gramática. prp' : end et. se debe pasar el valor 3 desde el nodo raíz errp hasta su hijo a la derecha exp'.su hijo de extrema derecha (otro e.rpl). . considere en su lugar la cuestión algo más simple de calcular el valor de la expresión utilirando el hbol de ctnálisis gramatical diido.Analiris sintáctico LL(l) número (4) número (5) i Este árbol ya no expresa la asociatividad izquierda de la resta.rp y exp' de la manera siguiente: procedure rxp . Para ver cómo.p .

. end caie . Advierta cómo el procedimiento exp' ahora necesita un parámetro pasado desde el procedimiento e.: mutch (-) . vulsofar := valsofur .rp.: niutch (-) . function exp' ( lialsofar :integer ) :integer . end case . notamos que la nueva gramática de expresión de la tigura 4.1 empleaba una nolución más rimple basada en EBNF. begin case token of t : mutch ( t ) . end exp' . .procedure exp' . else return vcilsofur . regresaremos a su construcción en la sección siguiente.4 es en realidad una gramática LL(1). end e. esp' . begin if token = + or token = . wls&r := vulsofar + ternz . como en la regla . trrm . Como con las tablas anteriore5. Una situación semejante se presenta si estos procedimientos están por devolver un árbol 5intáctico (asociativo por la izquierda). term . los cuales no requieren el parámetro extra.then case token of + : mutch ( t ).term . begin tenzp := term . exp' . E1 código que dimos en la ~ección 4. Finalmente. Para obtener esos procedimientos que realmente permiten calcular el valor de la expresió volveríamos a escribirlos de la manera siguiente: function exp : integer . La tabla de análinis sintáctico LL(1) se proporciona en la tabla 4.pl(temp) . var temp : inteyer . return e. return exp'(va1sofar) . factorización por la izquierda La factorización por la izquierda se requiere cuando dos o más opciones de reglas gramaticales comprirten una cadeha de prefijo coniún. end exp .rpf .4.

también podríamos escribir A -+ n ( @ 1 y). La solución en este caso simale es "factorizar" la n por la izquierda y volver a ehcribir la regla como dos reglas (Si querernos utilizilr paréntesis como metasímbolos en reglas gramaticales. algoritmo general cn la iigura 4.Advierta que a medida que cl algoritmo continh. el ttúmero de tipciiines de prodirccitín para cada no terrnin:il que .trios ejemplos.reglas recursivas por la derecha para secuencias de sentencias (ejemplo 3. ( eip ) fiic tor + número Ejemplos de esto son las. T I "\/) <'Y(.5 y entonces lo aplicamos a v.) P x a que la t'actorizaciítn por la inluiertla trabaje atlecuatlamente.4 M[N. Esto tainbiéii es posihle cuando hay más de dos opciones que compartan un prefijo. .Analisis sintácttto k(I) Talila 4 4 Tabla de anallsis stntactito LL(I) para la gramat~ca de la figura 4. un analizador sintáctico LL(1) no puede distinguir entre las opciones de producción en una situaci6n de esta clase. Estahlecem»s el. lo que se parece mucho a la factorizaci6n en witrnética.sent-(f+ i f / exp ) sentencia i f ( exp ) sentencici e l se sentencici ( Obviamente.7 del capítulo 3): y la siguiente versiítn de la sentencia if: .' ( número ) + fu<tor . debemos estar seguros de que cu es de hecho I: cxlena más larga compartida por los lados derechos.

sent sec-sent' 1 e sec-sed Esto es casi igual al resultado que ohtuvirnos de la factorizaeión por la izquierda.5 hlgoritmo para factorirac~ón por la izquierda de una gramática Ejemplo 4. Fisura 4.puede compartir un prefijo se reduce en al menos uno en cada paso. de modo que el algoritmo garantiza llegar al tinal. secuencia-sent -+ secuencia-sent .4 Considere la gramitica para las secuencias de sentencias eccrita en forma recuniva por la derecha: 1. 5 jemplo 4. al sustituir la secuencia-ient por'sint sec-sed en la últinta regla se hace que los dos resultados sean idénticos.sent 1 sent entonces la eliminación de la recunión por la izquierda inrnediata daría como resultado las reglas secuencia-sent -+ sent sec-sent' .a regla grlrmatical para secuencia-sent tiene un prefijo compartido que se puede factorizar por la izquierda de la manera que se muestra a continuación: ~ e c ~ ~ e n c i ( z . y.5 Co~isidere siguiente gramitica (parcial) p r a sentencias if: la .~ esent sec-sent' -+ n t sec-sent' -+ . secuencia-rent / E Advierta que si habíamos escrito la regla secuenciu-sent recursivamente por la izquierda en lugar de recursivamente por la derecha. efectivamente.

2 (véase la tahla 4. por ejemplo. entonces debemos arreglar que cada operación + se aplique al final en vez de al principio.Analiris sintáctico LL(I) [.rpr / F Esto es idéntico a Lu gramática obtenida de la regla recursiva por la izquierda mediante la eliminación de la recursión por la izquierda.si.a brma factorizada por la i~ipierda esta grariilítica es de esta es precisamente la forina en In que la utilizanios en la sección 4.pritncro Lo .irittiiéticaen la que damos rina asociatividad por la derecha de operación aritmética en lugur de una nsociatividad por la izquierda (aquí itsamos + precisamente para tener un ejemplo concreto): rvp . de cualqttier manera. puesto que tanto las asignaciones como las llamadas de procedimientos comienzan con un identificador. Entonces obtenemos rup -+term exp' exp' -+ + tevrn e. la grartiática no está en una forde nia que se pueda factorimr por la i~quiertla.scnt-asigr~ctc~irírr corno tle smt-llcrtn~ufa. ambas oscurecen la asociatividad).7 Aquí tenemos un caso típico donde una gramAtica de lenguaje de programaciciri deja de ser LL(l).de mtdo que podría ser el token de búsqueda hacia delante para cti~lipicrü ellas.7. tanto la Sactori/aciítn por la i~yuierda como la eliniinacicín de recursión por la izquierda pueden oscurecer la seniáiitica de la estructura del lenguaje (en este caso. 5 Ejemplo 4. esta cxpansicin tendría lugar en el siguiente paso de una derivlicicín). y obtenemos las reglas ' Ahora.srrir-~~. de Si.tp j term .teirn + e. Desgraciadaniente.s oper~ición las reglas gramaiicales anteriores (en cualquier forma).. deseamos conservar la asociatividad derecha de 1.tncra que w presenta a conrinii. Dejanios al lector escribir esto paraprocediniieritos desccndentes recursivos. snpotiga que sustituirrios teirn r q ~en lugar de cxp en la segunda regla (esto es legal. (pie debenios hacer es recinplam. porque.6 Suponga que escribimos una grainática de expresióti .2). continriando como en el ejemplo 4. 5 Ejemplo 4. Escribimos la representación siguiente de este problenia: Esta grainiítica no es LI.( 1 ) porque el identificador es compartido como el prinier toketi tanto de . Por consiguiente.4.tsde la por in.~ticici~jrt y ~etif-il(rrit(~rl<r los latlos tkrcchos de srts protlucciones <lcfiiiid. Esta gramática necesita ser factorirada por la izqtiierda.iciti~i: .

proporcionamos sólo un breve ejemplo de los detalles de cómo se puede hacer esto para el análisis sintáctico LL(I). son los que se' prefiere usar como métodos de análisis sintáctico basados en pilas y controlados por tabla.1 describimos cómo se pue den construir los árboles de anáiisis gramatical utilizando la pila de análisis sintáctico. 4. se debe principalmente al hecho de que la pila de análisis sin táctico representa sólo estructuras pronosticadas. Esto en parte se debe que. Así. la estructura del árbol sintáctico (tal como la asociatividad por la izquier puede ser oscurecida mediante la factorización por la izquierda y la eliminación de la recursi por la izquierda. analizadores sintácticos LL(1) son más difíciles de adaptar. Las demás se dejan para los ejercicios. se debe posponer la construcción de nodos de árbol sintáctico hasta el pun en que se eliminen las estmcturas de la pila de análisis sintáctico. Sin embargo. ajustando el árbol sintáctico. Ejemplo 4. esto requiere que se utilice una pila extra para segui pista de los nodos del árbol sintáctico y que se coloquen marcadores de "acción" en la pila análisis sintáctico que indiquen cuándo y qué acciones deberían presentarse en la pila de1 ár bol.2. Un analizador sintáctico LL(1) debe arreglar haciendo que el identificador esté disponible para la llamada o para la asignación de alg manera (por ejemplo.t identi f i cador := ewp / identificador ( exp-list ) / otro Entonces factorizamos por la izquierda para obtener sentencia i iden ti f icador sentencia' sentencia' i 1 otro := exp 1 ( exp-list ) Advierta cómo oscurece esto la semántica de la llamada y la asignación al separar el identific dor (la variable que se asignará o el procedimiento que se llamará) de la acción de asignaci o llamada real (representadas por senfencia'). Finalmente. En general.CAP.+ ~ + n / n . Los analizadores sintácticos ascendentes (el capítulo siguiente) son más fáciles de adapta a la construcción de árboles sintácticos utilizando una pila de análisis sintáctico y. La BNF es . ~ .) Vim en la sección en la que se trató el análisis sintáctico Sescendente recursivo que la adaptac de ese inétodo a la construcción del árbol sintáctico era relativamente fácil. por lo tanto.2.8 Utilizaremos una &rimarica de expresiones muy sencillas con 5610 la operaci(íii de suma o adición. (En la sección 4. co ya hemos visto. En contraste. advertimos que todos los ejemplos a los que hemos aplicado factorización la izquierda en realidad se convierten en gramáticas LL(1) después de la transformació Construiremos las tablas de análisis sintáctico LL(1) para algunos de ellos en la siguien sección.4 Construcción del árbol sintáctico en análisis sintáctico LL(1) Resta comentar cómo se puede adaptar el análisis sintáctico LL(1) para construir árboles s tácticos en vez de árboles de análisis gramatical. 4 1 A N ~ I S SINTÁCTICO DESCENDENTE IS sentencia -. en vez de hacerlo cuando inserten por primera vez. no estructuras que se hayan visto en realida Por consiguiente. un parámetro) o bien.

cuando se extraiga. Este síitibolo . pero . Debeinos clasificar dos operaciones eii esa pila.Análisis iintáttico LL( i ) 167 Ebto provoca qiie 1 s~tiiia aplique asociativaniente por la izquierda. Tabla 1. Esto cs típico de tales csqueiiias de i.v:rliiricii>trhiisaclos cri pila.¡hora se convierte en un nuevo sírnbo10 de pila y también se debe agregar a la regla grairialical que iguala a un +. pero acietnb presentarnos la pila de valores a la derecha (que crece hacia la izquierda). La primera se puede realim ~rieiIi:iiiteel procediinien~o esth en coricordancia). Indicamos la pila de airálisis sinticticci.Advierta que la suma es programada precisamente des[~i&s riúinero siguiente. el cual. La segunda iiecesita ser organizada eri la pila de análisis sintáctico. Las acciones del analizador sintáctico se proporcioriaii en la tabla 4. p. La scgrinda es s~irriar números en la dos tririrch !basada en el tokcn que pila. los oper:rn<los están cri ortlcn iri\ersr>en tic wlorcs. a saber. ttceptar Observe que cuando tiene lugar riria unla.5. E' $ conc«rdar/insertar E'-++n#E1 ci)nc«rtlar cortcordarlinsertar agregar a pila E' -+ + n # E' concortlar ~oncordarlinsutar agregar a pila E'4e 36 3$ 3$ J?$ 7 7S 7$ 5 7 '$ l? S 12 $ S Y. ki pila 9 . 1. Aliora verenios c h o tiene lugar el cálculo del valor para la expresih 3+4+5. Efectuaremos esto inseitando un síniholo especial en la pila de análisis sintáctico.L(I) 3 se corrcsporidiente con i-ecursi6ri por kr izquierda elirninada es Mostrarnos ahora c6ino se puede ulilizaT esta grarriática para crilcular el valor aritriiético de la cxpresih. ulilirarenios tina pila por separado 'tia . entrada y accih corno antes.8 Pila de análisis sintáctico SE Pila de Entrada 3 + 4 + 5 $ 3 + 4 + 5 $ + 4 + 56 + 4 + 53 4 + 5Y + 5$ + 5S + 5$ 5S $ Acción E+nE' valores S $ S E' n $E' %Er#n+ SE'#n $ E! # S Ei $E'#n+ $E'#n $E:# 9. La gratnática L.indel les de que se procese cualquier otro iio terminal E'.ii conslruccióii de tin irbol siritictico es seiiicjante. Esto garantiza la asuciatividad por la iquierda.5 Pila de análisis sintáctico con acciones de la pila de valorer para el ejemplo 4. ~alcular valor del resultado de una expresih. La priinera operaciciri consiste en insertar riri tiúrricro ciiando es igualado en Ir1 entrda. indicará que se va a realizar una suma. a la cual Ilnriiaremos pila de valores. un en la que se pueda alriiaccnar los vrilores interinetlios del cálculo. El sírnholo que utilizaremos para este propiisito es el signo de número (#). la regla para E': ..

Primero(n) contiene Primero(XI) . . el único c difícil se presenta cuando se calcula Primero(A) para todo no terminal A. X. 4. 2. entonces Primero(X) contie Primero(Xi+. . Por consiguiente.). . . después de lo c proporcionaremos una descripción precisa de la construcción de una tabla de análisis sint tico LL(1).{ E ] . . (una cadena terminales y no terminales).4. entonces Primero(X) también contiene E.{E). si para toda i = 1. se define de la manera siguie l. el cual se presenta en la figura 4. . Figura 16 .Finalmente. X Primero(X) contiene ~ r i m e r o ( x ~ ) ( e ] . para cualquier cadena u = XIX2.. entonces para cada selección de producción X -+ X.Si también para cualquier i < n. i . de la manera siguiente. . . establecemos el pseudocódigo p el algoritmo sólo en el caso de no terminales. ya que el co Pnmero de un terminal no es importante.Si todos los conjuntos Primero(X. si Primero(Xk)contiene E para toda k = 1. compuesto de terminales' y posiblemente de E.1. . Primero(X.3 CONJUNTOS PRIMERO Y SIGUIENTE Para completar el algoritmo de análisis sintictic« LL(1) desarrollamos un algoritmo permite constmir la tabla de análisis sintáctico LL(1). . . y más adelante en e sección volveremos a la definición y construcción de estos conjuntos. Algoritmo para calcular Pnmero(AJ para todos los no terminales A . . Al final de la sección consideraremos de manera breve cómo se puede exten la constmcción a más de un sínibolo de búsqueda hacia delante. entonces Pn ro(a) contiene Primero(X.). Si X es no terminal.) . Esta definición se puede convertir fácilmente en un algoritmo.) contl nen E. . . Ahora definamos Primero(a). Primero(Xi) contienen E.) . esto involiicra el cálculo de los conjuntos Primero y Siguiente. .6. Si X es un terminal o E. . X2 .3. y el conjunto primero de una cadena u se con a partir de los conjuntos primero de los símbolos individuales en n pasos como máxim donde n es el número de símbolos en u. entonces Pnmero(a) contiene E. En realidad. todos 1 conjuntos Primero(X. entonces Primero(X) = {X]. n.( Pasa cada i = 2.. Como ya lo indicamos en varios p tos.1 Definición Conjuntos Primero Si X es un símbolo de la gramática (un terminal o no tenninal) o E. . . . .) con e. .. entonces el conjunto mero(X). n. Pr¡mero(X. . .

la por definición. . Tales no terminales también a se (lenominan anulables: Fiaura 4. . . do agregue Primeru(Xl) a Pvtmero(d). entonces debe haber una producción A + S y. deben ser no terminales. el proceso se terminará después de un número finito de pasos. Emplearemos la inducción sobre la longitud de una derivación. En realidad.6. [.). PrimeroíXJ coiitiene c. considere s d o el c a o k = I de la figura 4. y si cs así. desaparecer). Establecetrios este algoritnio de manera separada eii la figtira 4. y el ciclo while interno no es necesario. ** e una derivación de longitud ri (utilizando una selección de producción '4 -+ Xl . de la cual son una parte.ttla. Prirriero(A) debe contener a F . Demostración: Mostramos que si 4 es anulable. En realidad. Eii otras palabras.. Finzlniente. ya que también debernos preguntirrnos si e csti en Primero(X.7 Algoritmo simplificado de la figura 4. no puede derivar a c. ~ ~ ~ ~ ~ Ol'reccmos varios ejcrnplos del cálculo (le conjuntos Prirriero para no tcr~iiiniilcs. este proceso no sólo calcula los teririinales que pueden aparecer cottio los prirueros símbolos en una cadena derivada de un no terminal. Si cualquiera de las X. .7.t) para cada no terminal '\ y «pcii>n de prodiicci6n A 4 Xi . implica que cada X. X.). la existencia de le cierivación anterior. ri~cdiante hipótesis de inducción. y en menos de tt pasos. .6 en ausencia de producciones c for todos los txo teninntes A do Primero(A1 := ( ] . Definición Un no terminal A es anulable si existe una derivüción A ** E Ahora inostraremo\ el teorema \guiente Teorema ír0 Un no terminal A es anulable si y sólo si Primero(A) contiene a e. por definición. . hasta que no tengan lugar sumas iidicioii.iles. así que todas las X. . Si A * e. . entonces Priniero(A) contiene a e. y sea A = X . whik existan rcrnzbios para cualquier Primert>(A) d~ for cada selecci6n de praduccitin A -+X1X2. Si hay producciones e:. Prirnero(A) contiene Primerofg) = ( e ] . la situaci6ii se vuelve liiás cornplic. Aun así. X. sino que también determina si un no terminal puede derivar L cadena vacía (es decir. longitud < n. .Yi. . ** e.Conjuntos Primero y Siguiente 169 'También es fácil ver c h o se puede interpretar esta definición cuando no hay producci«nes E : sólo se nccesila tnantcncr suiriando Primero(Xi)a Pritncro(. Suponga ahora la certeLa de la sentencia para derivaciones de .a iiiverya se puede demostrar de manera simihr. continuar rcalizantlo e1 inisrno proceso para X2. es termin. De este rttodo. .d. para cada i. y así sricesivqiente.

.

número 1 Printero@ictor) ( ( . número ] Priii?ero(o[?~uniri) ( +.Conjuntos primero y siguiente Pr~mero(~xp) { (.5): Esta grairiitica tiene una pmdiiccicín 6 .urgir muy poc. entradas en blanco indican que ningún conjunto ha cambiado en ese paso. Las Tainbién cliininanios el último piso. número 1 § Ejemplo 4. d o ~ieccsitarenlos :Igreg. En esa tabla s6io se registran los cambios en el cuadro apropiado tloiide ~~curren. podríamos haber reducido el número de pasos de cuatro a dos.) Intlicarnos este cálculo en la t.is coiiiplicacioncs tlrirantc el cilcr~lo.pero lo único que se puede anidar es el rio terminal En pcwto-rlsc.~blü4.u. .9 Regla gramatical ? rp 6'Xp - Paso I Paso 2 Paso 3 O[JMimrl k'llti Pr~mero(e L/?) ( 1. de ino<hque puetlen . número ) Prirnzro(trrrn) ( 1. iwalidad. ) = Priri~eroío[~rnul~)( * ) = - - (Atlvicrta que s i hubiérainos eiiutnerado Iris reglas grainaticales parqfirm~r priiiier lugar en e en v L de al últitno. número = Priniero(1etm) = { (. número ) - /frc ror * número Prtniero(factor) = (.6..10 Considere la gramática (factorirada por la izquierda) de las sentencias if (ejemplo 4. E cn iin paso. Tdhld 4 6 [altula de conjuntos Primero p r la gramát~cadel aa elemplo 4. lo quc tio a f c c t d a riiiiputio de lo\ okos prsos. puesto que no ocurre ningún cambio.

Nin otro cambio se presenta durante la segunda pasada.de rnanera que Primero@arte-else) = {else].wp 4 O (7) exp -+ 1 De nueva cuenta.w -t else sentencia p < i r t ~ p / . 1l. puesto que Pr¡mero(sent-if) todavía está vacío. De este modo. if ( ~cp) sertrencm nartr-else . puesto que inerofsmt-3 contiene este terminal. La regla dos a y e1 terminal otro a Primerofsentetzciu). y una tercera pasada no da como resulta ningún cambio. donde las producciones casi siempre están muy limitadas y rara vez exhiben la complejidad del caso general. La regla granlatic comienza sin hacer cambios.)arte-e1. Ahora efectuamos una segunda pas comenrmdo con ia regla l.senteni:iu) = (if. Corno antes. Es es típico en las gramáticas de lenguajes de programiicilín reales. 11 La tabla 4. regla 5 agrega E a Pnm La m(purte-else). Primero(. modoque Pnmer«(scrrtenci~) ( otro]. Esta regla &ora agrega if a Priinero(sentenciu). La regla 4 agrega els Pnmero(przrte-else).E . la tabla sólo niue5tra cambios y no exhibe la pasada final (donde no se presentan cambios) r .La regla t de = agrega if a Prinicro(. de tal modo que Wmern(sent-3 = (if). calculamos los siguientes conjuntos Primero: Primeroísentencirr) = ( i f. otro ) Primero(sent-if) = { if } Primeroíparte-else) = (el E ) se. De este modo.xp).CAP. (6) e.7 exhibe este cálculo de manera semejante a la tabla 4. Las reglas 6 y 7 agregan O y E a Prirnero(e.xp) = ( 0 . Primero(exp) = ( 0. recorremos paso a paso las opciones de producción. 4 / ANALISIS SINTACTICO DESCENDENTE puesto que ninguno comienza con un no terminal cayo conjunto Primero contenga E.otro).seritencia -+ otro sent-if if ( e. Como antes. de tal suerte que Primero(puite-else) = (else.6.<a Tabla 4 7 Cálculo de conjuntos Pr~meropara la gramática del ejemplo 4.otro) Primero(senrenccu) = {otro) Pttniero(rent-IJ) = iifl Primero@~<rtre-rlse) = (else) ~entencra o t r o + <en!-rf-. escribimos las opciones de regla gramaticd por separado y las nunieram (1) (2) (3) (4) (5) sentenciu -+ senf-if . haciendo una nueva pas siempre que un conjunto Primero haya cambiado en la pasada anterior.10 Regla gramatical wnlenc~u r cent-lf - Paso I Paso 2 Pnmerofrenrerrcra) = {if. s--+ t e : . ) .sozt-3.rp ) seritencia par$<-else purte-else -+ else sentencia parte-else . de modo que Primero(e.

secuencicr-sent / e . En realidad no es posible reconocerlo en la cntrxla..sent -+ s De nueva cuenta. Las reglas 2 y 3 producen Primerof. sienipre se debe agregar e1 símbolo $ al conjunto Siguiente del símholo inicial.3): .incon los . y una tercera pasada tarnpoko produce cambio alguno. no tendríamos un símbolo para seguir la cadena entera a ser comparada.os . compuesto de ternnnales. el conjunto Siguiente(A).No se hace ningún otro cambio.7. Primero examinamos el contenido de esta definicicín y después escribimos el algoritmo para el crílculo de los conjuntos Siguiente que resultan de ello.íinholos sigrtiente. por otra parte.6 y 4. sictnlxe se coinpnr:i~. se define de la manera siguiente.secuencia . La primera cosa que advertimos es que el signo monetario a. utilizado para marcar el fin de la entmda. e ) . 3.seciienciu-. secitenci(i senr (3) sec-sent' -+ e (4) sent 4 s En la primera pasada la regla l no agrega nada.~ec-. (Sed el únicc miembro del conjunto Siguiente del símholo inicial si este símbolo nunca aparece en el lado derecho de una produccih.) La segunda cosa que rihservarnos es que el "pseudotoken" v:icío E nunca es un elemenr de un conjunto Siguiente. la regla 1 ahora produce Primero(..reni) = Primero(setzt) = (S}. 2. entonces Primero(y) .( e 1 ectá en SiguientefH). enumeramos de manera individual las opciones de prodticci6n: (1 ) . Esto tiene scntido porque c: se ulilizaha en conjuntos Primero o 4 l o para marcar las cadenas que pudieran desapcirecer.2 Conjuntos Siguiente Dado un no terminal A. entoncey $ está en Siguiente(A). SI A es el . Puesto que una cadena así se genera mediante el símbolo inicial de la grmática.sent") = { .seal -+ sent sec-srnt' (2) sec-sent' -+ . se comporta corno si fuera un token en el cúlculo del conjunto Siguiente.iímbolo inicial. Sin él. La regla 4 produce Primero(sentencia) = [ S ) . 1. En la segun& pasada. entonces Siguiente(A) contiene SiguientefB). [.. Si hay una producción N -+ a A y tal que e está en Pnmero(y). y posiblemente $.Conjuntos Prtrnero y Sigutente 173 Considere la gramitica siguiente para secuencias de sentencias (véase el ejemplo 4.secuencia-sent 4 sent sec-sent' . Calculamos los siguientes conjuntos Primero: Dejamos al lector ta constnicción de una tabta semejante a las tablas 4.sec-sent' -+ . 5 44. Si hay una producción B -+ a A y.

en cualquier cadena que contenga A. número } PrirneroCfactor) = ( (. número ] Primeroiop. puesto que al construir tablas de análisis sintá tic0 LL( l j sólo necesitaremos los conjuntos Siguieute de no terminales.tokens de entrada existentes (incluyendo el símbolo $.vumcl) = { +.$ '4 3. Además. Finalmente. siniplificación que dejamos al lector. .$ . número ] Primero(term) = ( ( . Sólo las ocurrencias de A en los la derechos de las producciones contribuirán a Siguiente(A).1 Primero(ol.8 damos el algoritmo para el cálculo de los conjuntos Siguiente que resul tan de su correspondiente definición. lo opuesto de la situación para los conjuntos Primero. mientras que la definición de los conjuntos Primero funciona "po izquierda". cuyos conjuntos Primero calculamos en el ejemplo 4. el algoritmo se simplifica. En la figura 4. pero no es necesario. dada una regla gramatical A iE. Esta propiedad es. Con este algoritmo calculamos conjuntos Siguiente para las rnismas tres gramáticas. Por lo tarito. "libertad de contexto" en acción). Esto se debe a que.12 Considere otra vez la gramática de expresión simple. Con esto queremos decir que una producción A -+ u rto tiene infomacicín a . ca del conjunto Siguiente de A si u no contiene A. para las cuales antes calculamos los conjuntos Primero.) Figura 4. (Como en los conjuntos Primero. .9 de la manera siguiente: Primero(e. lección de regla gramatical hará contribuciones a los conjuntos Siguiente de cada no ter nal que aparezca en el lado derecho. que se concordad con un EOF ge fado por el analizitdor léxico). Siguiente(Bj incluirá a Siguiente(Aj u el caso 3 de la definición. donde si A i entonces Primero(A) incluye Primero(B) (excepto posiblemente para e ) . e B cierto sentido.r>l~tlt) { * } = . en general cada. También notamos que los conjuntos Siguiente están definidos sólo para no terminal mientras que los conjuntos Primero también están definidos para termindes y para caden de terminales y no terminales. A se dría reemplazar por u B (es decir.8 Algoritmo para el cálculo de conjuntos Siguiente % Ejemplo 4. don cada selección de regla gramatical se agrega al conjunto Primero de únicamente un no t minal (el que está a la izquierda). a diferencia del caso de los conjuntos Primero. cuando no hay producciones E.rp) = { (. Podríunm extender la definición de conjuntos Siguieute a cadenas de símbolos. advertimos que la definicicín de conjuntos Siguiente funciona "por la dere cha" en producciones.

La regla 2 vuelve a c.. -. ) }). +.rp. -. -. la regla 8 agrega Primero() ) = ( ) 1 a Siguiente(erp) de iriodo que Siguiente(exp) = (. * } . -. +. (lncluimo< las dos reglas cJ. y el algoritmo termina. . ) ) . +. 3. = Priniero(o~/w"'n) agrega a Siguienie(rrp). +. de triartera que no ocurre riri cciiirbio a los conjuntos Siwiente. Siguienteírxp) = (. *. Entonces Primero(lNctor) se agrega a Siguiente(op= rnult). number). por lo ianto. (a. euhibinios el progrew de este c~álcolo I:i en t. +.id. -1./ilcror) = +. Siguientc(r.r?n -+ t¿rmr porque pnerlcn lericr n n efecto. La regla 5 tiene tres efectos.iun cuiindo no lo tcngiirt cn recilitl. ) ) -.idos derechos. (s. tnodo que Siguiente(rerrn) = (S.) . de rnodo que SigiiienteíJi<. * ] . Considercinws las otras reglas en urden. Hemos calcul. 7 y 9 no tienen rio terniinales eii sus I. todos Los utros conjuntos Siguiente se inicializan coino vacíos. *. amariera de que Siguiente(rrrrri) = +.rp) se a g r e p a Siguiente(ti~r~n). o/t. Como mtes. Una tercera pasada no produce mis cambios.) . +. -. La rcgla 6 tiene el inisrno efecto que el úliiino paso para la regla 5 y. An= tes de comenzar.a que Siguiense de = de te(ops~irrro) ( (.~d« siguientes conjuntos Siguiente: los % .ip) (. ) }). Conio en el cálculo de los conj~iiitosPrimero.r/~) (S}. ¡. se A contiriitaci6n. Finnltnente. Siguiente(ferrn) se agrega a Siguienteíjkmr). omitimos el paso de conclusión cn esta rahla y 40 indicanws los camhios a los cor!juntos Siguiente cir:iiiJo ocurren.8. ) } Siguiente(o/)rn~rlt)= ( (. numberl.iblri 4. ) 1 Siguiente(o!tsurt~(i)= ( (.Finalniente. +. número) Siguiente(tcrnt) = {S.a regla 1 afecta a los conjuntos Siguiente de tres no terminiiles: c.icitin: que Las reglas 3. de modo que ahora Sig~iieritc(~.Conjuntos primero y siguiente Volveinos a escribir las selecciones u opciones (le procii~ccicíiicon ~iitnier. iniir1ei.cor) = (S. pero esto se se acaba de realizar inediarite la regla 1 . de tnodo que Sigiiieiite(o~?rrrlIlt) { (. Se agrega Pri~riero(o/~»rtiIr) a Sig~iiente(rerrr~). de ~rrodo 110 ngregan nada al cálculo de los conjuntos Siguiente. Finalmente. +. -.vunu y ternr. -. En la segunda pasarla la regla 1 agrega ) a Siguienic(lemz) (de miinera qne Siguieiiie= (tutn) = {S. .iusiir qne Sig~iiente(it\-(J') agregue 3 Signientc(term). *. *. »o se hacen canrhios. Primero(~ern~) agrega a Sigliiente(o[~xurt~). estrihlczcamos Signieiite(c. y la regla 5 iigiega ) a Siguiente(firmr) (así que Sigt~ieritcuuctor) (S.sp-+ wrni y k. número ] Sigliiente(. Ornititiios tairihiin las cuatro seleccioiies de regla gramatical que 110 pueden :tfeciar cl cilccilo.

Finalnienlc. Comencemos establecietido que Siguiente(sentencict) = ($1 e inicializando los Siguiente de los otros no terminales como vacíos.6 y 7 no tienen efecto sobre el c~ílculo los conjuntos Siguiente. sentenci<l y pwte-else. Siguiente(e. o t r o ] Pritnero(.ventencicr) a Siguiertte(senf-VI. de modo que Siguientdexp) = ( ) ).Tabla 4. 1) Repetimos aquí las opciones de producción con numeración: - (1) (2) (3) (4) (5) (6) (7) .13 Corisidere de nuevo la gramática simplificada de las seriteiicias if.8 Cálculo de <onjuntor Iigu~ente para la gramática del ejemplo 4.~enteti<ia) iniplica Primero(pc1rre-elve) .10. de modo de que los ignoraremos.r~~) r o = ( ) ). de la manera siguiente: Primer«(setlter~cia) ( i f . En primer lugar. (le manera que Siguiente(. Etitonces Siguicntc(. else J.serirerici«) j S.(c:). 3 op\11n111 ferm Siguiente(opmu1f)= Ejemplo 4.vp.I Siguiente(srrrrrr1~~i~l) - . cuyos conjuntos Primero calculamos en el ejemplo 4. S.vent-@) = ( i f } Primero(parte-else) = [ e lse.se1 corno . e } Primero(exp) = ( O. La regla 3 afecta los conjuntos implica PrinieSiguiente de c.12 Regla gramatical <'t[l. rle modo que Siguiente(serzt-if) = ( $ ) .xp -+ O exp -+ 1 Las reglas 2. Siguicntcísrnt-lf) se Ltgrega tanto :i Siguieiite(pcirle-eí. La regla I agrega ahora Siguiente(.sentencia + srnt-if sentrrzcia -+ o t r o sent-if -+ i f ( exp )sentencia parte-else parte-else -+ else sentencia parte-&e -+ e e.

de Finalmente.lo que tampoco pl-«duce ningún cambio. Finalniente. La regla 3 ahora provoca qrie el tenninal e l s e se agregue a Sigr~iciite(lt<rrte-e/.-e&) ( Se l s e / .2: 1 \e .cicl).srtit-iff.~e).modo que Siguieritc(l>ctrt<. lo que resulta en Siguienle(trnt-18 = /S. La primera adici6n nos proporciona Sigi~ieiite(purte-e/sr) = {S).-sertt') = ($1.2. la regla 4 no ocasiona más cambios. co~no clicron en la iección 4. La regla I produce Siguiente(ser1t) = (.se) se iigregile a Siguieute(. 1 ~ ) .secuericiu-. La tercera pasada tampoco' prodnce cambios adicionales. De este modo. Una segunda pasada no prodiice cambios adicionales.1 1 . con las opciones de regla gramatical: En el ejemplo 4. ) y Siguiente(sec.14 Calciilenios los conjtintos Siguiente para la gramática simplificada de secuencia de sentencias del ejemplo 4.1 1 calculamos los siguientes conjuntos Primer«: Las reglas gramaticales 3 y 4 no contribuyen al cálculo del conjunto Siguiente.de desaparecer).l. En la segunda piisada. e l s e ) .33 Construcción de tablas de análisis sintáctico LL(1) C'wxidcre ahora la consirrccci6n origin. calculamos los conjiintos Siguiente 4.Conjuntos primero y siguiente 177 (esto se debe a que .sertteniiu)11Siguicnte(. la regla 1 vuelve a agregar Siguiente(.s<.s<~nter.tit)= ( S ) y los otros conjuntos Siguiente como vacíos.srr~t-1fpuc. Conienmtios con Siguiente(. la regla 4 provoca que SiguienteQcrrtr-<. La regla 2 no tiene efectos. fi Ejemplo 4. y hemos calciilado los siguientes conjuntos Siguiente: - Dejamos al lector el constniir una tabla para este cálculo como en el ejemplo anterior.il de las entrail:is de In tabla de :niálisis 5intiictico l . pero la segiinda no produce carnbios.

u a la eiitriida M[A.E es una producción E y existe una derivación S $ ** a A a p. entonces agregue '4 E a la entrad M. Para cada token u en Primero(u). Primero(ui) n Primero(a. l . 1. y el token u de la regla 2 está SiguientefA). donde a es un token. En la última sección escribimos una gramática equivalente utilizando la eliminación de recursión por la izquierda de la manera siguiente: exp -+ term exp' exp' -+ opsurna twm exp' 1 E opsuma -+ + / term -+ &tor ternz' terml -+ opmultfactor terrn' 1 opnwlt -+ * j¿lctor -+ ( e. entonces agregue A -t a a la entrada de tabla ul. para cada elemento u de Siguiente@) (un token o signo de $).CAP. Primero(A) n Sig~iente(~4) es vacío. El teorenia siguiente es Iundamentalrnente una consecuencia directa de la definición de gramática LL(1) y la construcción de L kabla de análisis sintáctico recién dada. el token (1 en la regla I está en Primeroíu). 2. . donde S es . Ejemplo 4. / a. l . I ( i . / cu2 / .'~sta gramática. 4 1 ANALISIS SINTACTICO DESCENDENTE. . cij. j c n . Para toda produccicín A 4 u . Si E está en Primnero(cu). v de tabla M[A. 2. agregue A + cu a M[A.9 anterior) es recursiva por la izquierda. couio fue dada originalmente (véase el ejemplo 4. y deja a su tlernostración para los ejercicios: Teorema Una gramática en BNF es LL(1) si se satisfacen las condiciones siguientes. De este modo llegamos a la siguiente construcción algorítniica de la tabla de análisis sintáctico LL(1): Construcción de la tabla de análisis sintáctico I. (11. 2.rp ) / número - E Debemis calcular los conjuntos Primero y Siguiente para los no terminales de esta gramática.) es vacío para t o d a i y j . y existe una derivación u *'"1 $. - Evidentemente.1 Repita los siguientes dos pasos para cada no terminal A y opción de protiucción A 4 u. Si A -+ u es una opción de prtiduccióri. agregue A . Ahora examinaremos algunos ejemplos de tablas de análisis sintáctico para gramática que ya vimos con anterioridad. .. Para todo no terminal A tal que Priniero(A) contenga a E.L(l) M [ N .. Si A . i f j. u l. Dejamos este cálculo para los ejercicios y simplemente establecenios el resultado: 1 . sín~bolo inicial y (1 es un token (o signo de $).15 Considere la gramática de expresión siniple que hemos estado utilizando como ejemplo estándar a lo largo de este capítulo. 1.

igi~ierites conjuritos Primero y Siguiente y lii inhla tle . pigina 156.inicro de esta grn~iiitica calcul:nvti en el ejemplo d.tlt') = I%iner«(tcrrrr) ( ( número] .] ..) - *.~.L.16 Considere In g r a ~ n i t i c r ~ siniplificr~da las sentcticias i f de Los conjuntos Pi. ohtcneinos nii.tonjuntos primero y riguiente Priinero(crp) -= ( (. § Ejemplo 4.3 de la 5 Ejemplo 4. t:) = j*1 { (.tcahainos de r1esci.4 de In plígiixi 163.s'rtr~~t~i.~tabla como la 4.ihir. Mostraiiios estos conjnntos tlc nueva cuenta rtcluí: 1a coiistruccií.J.{ +. 13. iios proporcioriti la tahla 4.iii:ilisis ~ i n t i c t i c o I I en la parte wperior de In pígin.O) . Primer»(c. x ] ~ri~nero(o. mientras t p e los se conjnntos Siguiente en el qjernplo 4. Priinero(trrr~r')=: Primero(o/mrrlti Prirnero(/iicro.. [.r \ig~iicriie.i): del Esta gramática tiene los .rt de la inhla de :rnilisis sirit:íctico 1. -.( . n ú m e r o ) 1 +.~ cjctnplo 3.4 (en la que se aplicí. n u m é r o ) Al aplicar la constrncci6n de la tahla de i ~ n i l i s i s siirtictico L L ( I i ync .17 Considere la graiiiitic. 10. f a c t o r i x t c i h por la i/quicrd.

emplear métodos ex profeso pana analiz sintácticamente gramáticas que no sean LL(k) para ninguna k. una gramática con recursión por la izquierd nunca es LL(k). Sin embargo.Aunque estas definiciones s menos "algorítmicas" que nuestras definiciones psra el caso k = 1.(k) no son comunes. correspondiente al HNF de la figura 3. la tabla mism de anáiisis sintáctico no expresa la potencia completa del análisis sintáctico LL(k). irnpriiiie una representación del &bol sintictico para e1 archivo de listado. adicionalniente. en parte debido a la complejidad agregada. podenros definir Primnerok(a)= ( wk 1 cx -J" 1. los analizadores sintácticos descendente~ recursivos pueden utilizar de manera selectiva búsquedas hacia delante más grand cuando es necesario. análisis sintáctico por medio de la tabla tal como la construimos se distingue del análisis si táctico LL(k) al nombrarlo análisis sintáctico LL(k) fuerte o análisis sintáctico SLL( [por las siglas de Strong LL(k)l. e incluso. . (Esto se puede contrarrestar has cierto punto utilizando métodos de compresión de tablas. De manera simil podemos definir Siguiente. en el análisis sintáctico LL(k) se presentan diversas complicaciones.) En segundo lugar. Por eje plo. sin iniportar cuán grande sea k. De este modo. Los análisis sintácticos LL(k) y SLl. donde u. pueden desmollarse a goritmos que permitan calcular estos conjuntos.43.4 UN ANALIZADOR SINTÁCTICO DESCENDENTE RECURSIVO PARA EL LENGUAJE TINY En esta sección estudiaremos el analizador sintáctico descendente recursivo completo para el lenguaje TINY que se muestra en el apéndice B.íA) = ( w k / S$ ** d w } .6 del copíttilo 3. es tina cadena de tokens wk = los primeros k tokens de w (o W . sobre todo porque no todas las cadenas Siguiente se presentan en todos los contextos. Remitimos al lector a la sección de notas y referencias para más infomlación. pero sobre todo porque si una gramática falla en ser LL(1) probablemente en la práctica no sea LLfk) para ninguna k.9. 4. En primer lugar. El analizador sintáctico construye iin árbol sintictico como se describió cn la seccicín 3. si w tiene menos de k tokens).7 del capítulo anterior y. la tabla de análisis sintáctico se vuelve mucho más extensa. como hemos visto. Por ejemplo. Sin embargo. Analizadores sintácticos LL(k) El trabajo previo se puede extender a k símbolos de búsqueda hacia delante. puesto que el núm de columnas se incrementa de manera exponencial con k.4 Extensión de la búsqueda hacia delante. El analizador sintáctico utiliza !a EBNF tal como se proporciona en la figura 4. y construir como antes la tabla de análisi sintáctico LL(k).

la i. esto se explicará en e l análisis del manejo de errores que en breve se realizará. al que llama getToken si lo encuentra y Jeclwd un error en casi> contrario. 1l. E l cúdigo del analimlor sintáctico también incluye una variable c\tática token que mantiene el roken de búsqueda hacia delante. ~ i r r i ~ ~ i ~ i . El procedimiento principal parse inicializa token para el primer token en la entr. Los procedimientos de análisis sintáctico recursivo utilizan tres procedimientos utilitarios.iina a stmt-sequence y postcrionmnte verific.9: uno p s a . .t ("secuen- cia-sent") stmt-sequence.sequence hay más tokenh. pero e reconocen coirio ~ .irhol sintiiciico c o n m i i d o por el .ida.uno pwü cada una de la\ cinco clases difede rentes de sentencias y cuatru para los diferentes nivele^ de prcceden~ia las cxpre&nes.sryyiirnc~r). yrlimu (progrum). ~ ~ ü rde sus expresiones a w i a d a ~T a r i i p o hay correspondencia de procedimiento p. puesto que un programa \51v es una secuencia de sentencias. Se coiiipme de 11 ~~mccdiinieritos mutuanienre r c c u n i v i ~ s cn el apéndice €3. i ~no tcrniitiitles del operador ni) se incluyen como procedimientos. el cual se escribió en una forma algo más compleja para mejorar el manejo de errores.ua p r t J te ..uno para snirrrrc~i<i (~turrrnrnr). de manera que la nitina parse ("análisis sintáctico") simpleniente Ilitniü .) El contenido de c.~do. l í n e ~ s que corresponden directamente a la graniiitica EBNF de la tigura 1. t r < . y un procedimiento SyntaxError que imprime un mensaje de error en el x c h i v o de1 li\t. . 1 . E l archivo parse.ida procedimiento recursivo tlchería \er rc1aiii.innliz:idor sintüctico.que define la rutina de aniilisis jinticrico principal parse. significa que exi\te un error.uul de\ i i c l \ e u11q m t a d o r al .iincnte :iutoeuplic.iii\i. . con la posible excepción de stmt-seguence. los cuales se reúnen por simplicidad en el archivo util c .\ r ~ Irirnt-. un prwedimiento match que busca un roken específico. U (Si dcspués de que rqresii atmt . c \e proporciona 9íN-l 111.~s i esti el Iinal del :irchivo iuente anres de r e g w . a1 árbol constniido por stmt-sequence.

Esto procediinientos son 1.8 y 3. el cual toma un parámetro indicando la cl. También se incluye en util c un procedimiento printTree (Iíneas 373-506) que e cribe una virsión lineal del árbol sintáctico para el lihtado. bajo el control de la variable global traceparse. Iíneas 350-526). el cual toma un parámetro indicando la clase d sentencia.10 . y copia la cadena. líneas 300-335). asigna es . 2. 4 i ANÁLISIS S I N T ~ T I C ODESCENDENTE íaphiice B. El procedimiento printTree funciona al imprimir información del nodo y luego efe tuar una sangría para imprimir la información del hijo.h (apéndice B. devolviendo un apunt dor al nodo recien asignado. y asigna un nuevo nodo de sentencia de esa clase. newStmtNode (líneas 405-421).CAP. de manera que podamos observ el resultado de un análisis sintáctico. devolviendo un apuntador a la co recién asignada. y 3 copyString (Iíneas 442-455). devolviendo un apunta dor al nodo recién asignado. El procedimiento copystring es necesario porque el lenguaje C no asigna de manera aut ~iiática espacio para cadenas y porque el anaiizador léxico vuelve a utilizar el mismo espaci para los valores de cadena (o lexemas) de los tokens que reconoce. El irbol real se puede reconstruir partir de estas sangrías.9. traceparse imprime el árbol sintáctico del progrania TINY de muestra (véase el capítulo 3. figuras 3. newExgNode (líneas 423-&O).ise de e presión. el cual toma un parámetro de cadena. y asigna un nuevo nodo de expresión de esa clase. cio suficiente para una copia. Este procedimiento se llama desde el programa p cipal. con la int&ar util . piginas 137-138)en el archivo del listado como se muestra en la figura 4.10 Exhibición de un irbol rintictico de TINY mediante el procedimiento printTree Write Id: fact . Fioura 4.

ir taiito c6digo coi-rio f t ~ e nposible.!je libre tle contexto generado por la grariiitica del lenguaje de prograrnacidn c11 cuestidn.tImnia. 2.tpropi. de y tambiéri intentar determinar de manera tan exacta como sea posible la ubic:tcii. U n analizador sintáctico debería intentar evitar el prublema de cascada de errores. con l o que coniprcimcte CI procosdr tanta iiiformnciirii de la ecitradu coirio w 3 posiblc. 4. 4. U n analizador sintáctico debería intentar determinar que ha ocurrido un crror m 1 1~wnfo r:ottio jwrci posihle. Esta corrección de errores de distancia mínima es por lo regular muy inecaso. en la cual un error genera tina larga secuencia de mensajes de error falsos.tplicar p r encontrar IIL programa correcto que en cierto sentido se parezca ni~icliu proporcionado (habiti~almente ténnirios del número de tokeris que dehen insertíirsc. un a n a l i m h r sintáctico intentará proporcionar uii n i e ~ m j e error importante. reparación de errores). de tal manera que un escritor de con+ piladores tiene que cfecttuir "convenios" durante la constnicción de un ~n:in-ja<lortic errorcs. U n arializador sintáctico sieriipre debería iritttntar .trtaliz.I I'~~III~I ~~ WLII-\IWS I ICICi ~ p a c i de mores e11 los m ~ l i ~ d o ~hit~ícticos L rIcX c h r~s ~ICSCCII(~CI~IL~\ bc ~ I c n c ) ~ l l111odode . la inversa. el analizador sintáctico debe selecciooitr un lugar probable para reaiiudar el análisis. L a mayoría de las técnicas para la recuperacidn de errores son de propdsito específico. A los escritores de compiladores les es te. cuando nienos para el primer error que encuentre. Esperar demasi. el ann1iz:rdor sintáctico debe iridicar que existe ul'qúti crror y. Más allá de este coinpctrtamie~itomínimo. en los que se genera una cascada sin fiti cte mensajes cte error sin consuriiir ninguna enirada. U n nrtalizador sintáctico debe tleterminar por l o nienos si un programa es sintáctica~riente correcto o ti«. 3.Retuperation de errores en analizadorer sinrátticos deiceodenter DE ERRORES EN ANALIZADORES SINTACTICOS DESCENDENTES 1 respuesta de un ~inalimdor a . Algtinos analizadores sintácticos pueden ir tan lejos coi110 para intentar el algum fojrnia de corrección de errores (o. F l cionitm i~l~i k r i w cid hecho ~ I I C . la reparacidtl ilc errores qne ficiente corno para aplic. Algunos de estos objetivos entran en cunflicto entre sí. es decir. Vale la p e ~ i a c\iablecer que cttalqtiier üiializatlor sintictico debe comportarse por l o menos corno un reconocedor. . 1.ilizador sintáctico intenta deducir un pntgrama correcto :t partir del incorrecto que se le proporciona. cn cu~lquier 3a corno resultado a menudo está muy lejos de lo qiie el pmgrcriiiador pretendía.irIor sintáctico iio debe afirmar que existe alguno. CII ii111xiont:s . Algun. Los priiicipios generales son difíciles de ohtcner.ts coi~sideraciones irnportnntes que se aplican son las siguientes. eliminaren se o modilicarse). U n analizador sintáctico que sdlo rcalizn esta tarea s2 denomina reconocedor. Por ejeinplu. puesto que se limita a reconocer cadenas en el lengti. el evitar los pritblenins de casc:iila de errores y bucle infinito puede cic~tsion.ido tienlpo arites de la declnracicíri del error significa que la iibicación clel error real puede hahersc perdido. que se vea eii a ~ i r i l i m l w x i n t k t i e o s muy difícil genera mensajes de error sigiiificativos si11 intenta hacer correccidn de enxres. si tiii programa no contiene errores de sintaxis.1 Recuperacion de errores en analizadores sintácticos descendentes recursivos ~:II. un analizador sintáctico puede mostrar rri~ichos ~iiveles diferentes de respuestas a Iiis ernms.ttla. In mayor p x t e de 1:)s veces se limitará sirlo a casos fáciles. Si intenta esto. W. a fin de encontrar tciritos errores i reales como sea posible durante una traducción simple. si un programa contiene un error de sintaxis. como la falta de LII~ signo de pontuacicín.trsc a cualquier error y.ir &jcrivo de q u w l :~naliz:idur sintictico oirii(:i algo de In entrlidii. para decirlo de manera quixís más . sinláctico a los errores de sintaxis a iiien~cdo uri f:ictor crítico es i n la utilicixi de un cornpilaclor. con iniique se aplican a letigriajes específie«s y a algoritmus de anilisis si~~táctico chos casos especiales para siturrcioiies ptrticiilares. Existe un grupo de algoritmos que \e aa I al ptieden .donde el aii. Después de que se Iia presentado un error.n en la que haya o c u i ~ i d o error. ya específico.5. U a~ializador I sintáctico debe evitar bucles infinitos en los errores.ibituülmcnte. i intonces el :inaliz. es nui. Por consigoien~ reales.

Sin embargo. y scanto. tenemos dos procedimientos más. Aquí el signo $ se refiere al fin de la entrada (EOF). lo que no es mejor qne simplemente después del error). no siempre deberían provocar un manejador de errores consunia tokens. que es el to consumidor en modo de a l m a propiamente dicho: procedure scunto ( synchset ) . Es importan darse cuenta que el modo de alarma funciona mejor cuando el compilador "sabe" cuándo alarmarse. cuando se implementa con cuidado. Si se encuentra un error. adem6. la ventaja de que virtualmente asegura que el analizador sintáctico caiga en un bucle infinito durante la recuperación de errores. Los conjuntos Primero también son importantes. Por lo general los conjuntos Siguiente son candidatos importantes p les tokens de sincronización. Naturalmente. desechando los tokens hasta que ve en la entr uno del conjunto de tokens de sincronización. Por ejemplo.GiP 4 1 ANALISISIINTA~IEO DESCENDENTE conlplejas. que realiza la verificación temprana de búsqueda hacia delante. Las decisiones importantes que se tienen que tomar en este método de recuperación errores consisten en determinar qué tokens agregar al conjunto de sincronización en cada del análisis sintáctico. Los conjuntos Primero también se pueden utilizar para evitar el manejador de errores omita tokens importantes que inicien nuevas construcciones princip (como sentencias o expresiones). Ilrirna :il tncido de :il:irma la rcgla de "no :il. puesto permiten que un analizador sintáctico descendente recursivo detecte pronto los errores en análisis sintáctico. procednre chrckinput (firmet. &canto(Jrrtset ufi~llowset . tales como los de punto y col y las comas. éste puede ser un m para la recuperación de errores mucho mejor que lo que implica su nombre. el a lirador sintiíctico explora hacia delante. Además de los procedimientos matcli y error. e incluso los paréntesis derechos olvidedos. se agregan a este conjunto conforme se presenta cada llamada. un paránietro extra compuesto de un conjunto de tokens de sincronizkión.2 (véase t bién la figura 4. begin if not ( token infirstset ) then error .ri-~m". iin . Ilustraremos la recuperación de errores en modo de alarma esquematizarido en pseudoc digo su implementación en la calculadora descendente recursiva de la sección 4.' Este mo a l m a tiene. end scanto . de hecho. - 4. end. El mecanismo básico del modo de alarma es proporcioriar a cada procedimiento recursiv .1).en donde se reanuda el análisis sintáctico. el manejador de errores consumiri un número posiblemente grande de token un intento de hallar un lugar para reanudar el análisis sintáctico (en el peor de los caso cluso puede consumir todo el resto del programa. che put. Las c cadas de errores se evitan (hasta cierto punto) al no generar nuevos mensajes de error mie tiene lugar esta exploración adelantada. s!lprieit:iincnte cn intente de mejorar si1 iiii:igcn.followsct ) . debe tenerse cuidado para aseg que no se presente un bucle infinito. ) end if . los tokens que pueden funcionar como tokens de $incronizaeibn . Wirih 11'1761. lo que siempre es útil en cualquier recuperación de errores. A medida que se efectúa el análisis sintáctico.1. que en esencia permane iguales (excepto que error ya no sale de inmediato). los símbolos de puntuación perdidos. be& n while not ( token E synchset u ( $ }) do getToken .

ser) sea el iokcn siguicrtte en lil salida. en getieral.ir6ntesis i~quierdo: llanta a ry3 con pitr6ritcsis derecho se sólo conro su conjunto siguiente (.~r.t (le tokeii se clchtl cwi-iiliiii.s~nt~li.o triia prtichii t r i i s t c ~ i i p ~ i iiiie~oi-e LY)~II~OI-i .3 ) *4-+5 geiierarti exactiirrreirtc dos nicnsajes de error (uno p i r a el priiricr signo de nienos y otro para el segundo signo de inrís).h. por cjcniplo. (Hicirlios csto de modo clue. Por desgruci. Esta forma de niodo de alarrito generar6 errores samnahles (niencajes de error útiles que tarnhién se pueden agregar coino parlinretsos a ~ h r c .iiiem apropiada.~.) Iki:irtios iin .icióit.1~1ir 1. se hace iiiia eucepción después de qiie se ve uii p. d(12 1:pe uria priicha riiis g~xci-.igrt.~ p s i b i I i ~ l .iI.iinadiis rcctil-sivas. Observitirios que.rtl. tlel .' de m o r y iectiperacirín de errore\.syidi.s. k i ~ r pyr terror).jeinplo. .iltiiciite torl. así ciiirio m i n i p l e ~ i w ~ t a c i ó n ( ~ .t l prtich. ~ ~ cl t~iiiiienitl ci-ror. Esto es iípico de la clase de iiiiilisis de prcipiísito específico que ircompaña a la reciipcracióri de errores en modo tle nlar~tta.gados (le ni.i cI partntesis tlcreclio.para ~ibiencro s iri-jorcs i i i c i i ~ j c i en p. l t i s +xicios. Por e.itriiei~tode wtc código.iet se descasia). En el cas« dejifirctor. la cadeiia de entrada ( 2 + .ir. cirrti.iniilisis {le1 coinport. con riuevcis tose kciis de siiicrorii~. I:i expresicíri ( 2+ * ) no gciierase ni1 I'ilso trieiisajc de crnir p.i y wi:r segurida ved para vei-ificar que un tokcii en c l cocij~into Siguiente í o .vnc.set pasa en las Il.Recuperacion de errores en analizadorer iintictitos dest~ndentes 185 Advierta ciímo cl~eckirrpiit Il:inta~lodos veces ci1 cada proccdintiento: una v c pasa veril'es ~ car que irri tokcn en el conjtinio Priiixro sea el token siguioite en la e~itr.

5. E' para exp'. Dejamos. se requiere de una nueva pila para manten los parámetros syrtchset. junto las acciones correspondientes que tornaría checkinput. en la entrada (los métodos de compresi de tabla pueden comprometer esto ligeramente). se evitarían la cascadas de mensajes de error. si E en Primero(Aj).las modificaciones para el algor' nio de análisis sintáctico de la figura 4. porque los tokens. página 155.9. ) Con estas convenciones la tabla de análisis sintáctico LL(I) (tabla 4. Dado un no terminal A en la p superior de la pila y un token de entrada que no esté en PrirnerorA) (o Siguiente(A). Podemos arreglar suprimir un mensaje de error en el segundo movimiento de error exigiendo.2 Recuperación de errores en analizadores sintácticos LL(1) La recuperación de errores en modo de a l m a se puede implementar en analizadores sintác cos LL(1) de manera semejante a como se implementaron en el aniilisis sintáctico descenden recursivo. de hecho. También utilizamos las abreviaturas E para exp.4) se verá como en la tabla 4. y así sucesivamente. Observe que hay dos movimientos de error adyacentes antes que se reanude con éxito el análisis sintáctico. pero rara vez es apropiad (analizaremos un caso más adelante). Seleccionamos la alternativa l si el token de entrada actual es $ o está en Siguiente(A) y la alternativa 2 si el token de entrada actual no es $ y no está en Primero(A) u Siguiente@) La opción 3 ocasionalmente es útil en situaciones especiales. Extraer de niariera sucesiva tokens de la entrada hasta que se vea un token para e cual se pueda reiniciar el análisis sintáctico. (Advierta que una acción extraer es equivalente a una reducción mediante una producción E . en gener scílo se insertan en la pila cuando se ven. El caso en que un token está en ta parte superior de la pila. Un caso simple de esto en el ejemplo que acabamos de dar es cualquier inicio de cadena con un paf réntesis derecho: esto causará que E se extraiga de inmediato y la pila se quede vacía con toda la entrada esperando ser consumi&.2. En esa tabla el análisis sintáctico se muestra sólo a partir del primer error (de manera que el prefijo (2+ ya se ha reconocido con éxito). que el analizador sintáctico haga uno o más movimientos con éxito antes de generar cualquier mensaje de error nuevo.4. y no es mismo que el token de entrada actual." Advierta que la situación de error primario ocurre con un no terminal A en la parte s perior de la pila y que el token de entrada actual no esté en Prirnero(A) (o Siguiente(A). Existe (por lo menos) un problema en este método de recuperación de errores que pide una acción especial. insertar un nuevo no terminal en la pila. . Indicamos la primera acción en la tabla de análisis sintáctico mediante la notación extraer y la segunda mediante la notación e. 2. 3. De este modo. si está en Primero(A)). normalmente no ocurre. todavía con alguna entrada para el análisis sintáctico. existen tres alternativas posibles: l.rplorar. dada la cadena ( 2 + * ) . y debe planearse una llamada a checkinput en el algoritmo antes cada acción generada por el algoritmo (cuando un no terniinal está en la parte superioi de &). Extraer A de la pila.10. se muestra en la tabla 4. Como muchas acciones de error extraen un no terminal desde la pila. Como el algoritmo es no recursivo. El comportamiento de un analizador sintáctico LL(1) que utilice esta tabla. después del primer error. para los ejercicios. Una alternativa para el uso de una pila extra es construir de manera estática los conjunt de tokens de sincronización directamente en la tabla de análisis sintáctico LL(I j. es posible que ésta se vuelva vacía.

sin los conjnritos de sincroniracióii.10 Movimientos de un analizador sintactito LL(1) utilizando la tabla 4. Adernás. estableciendo m i l tokeri que no esperahü encontró.l>~urna term exp' o~sutriu - explorar erp' -+ E Iern~ erp' opsumcr + opsuma i extraer term ex traer extraer explorar extraer term' explorar explorar . corno se da cri el apiridice 8.52 Recuperación de errores en el analizador sintáctico de TINY El manejo de errores del analizador sintáctico de TINY.!je (le error +1e SL.4) con entradas de recuperación de errores etp rrrm erp' - número extraer rerm eip' explorar r q ' -+ E op"l<m<' - extraer term -t firctor rerm' extraer tcrm -+ jirctor lerm' explorar extraer t r explorar explorar ccp' etp' . Tabla 4.9 Tabla de analislr rintactao LL(1) (tabla 4. El principal metis.-+E E'*& acepta 4. gcrict:~ . El procedimiento garse tanihi6n ileclara error si se cncuenrrü iiri ~okeri disiiiito id f i ~ de iircliii v o dcspit6s de que tcrminii cl :inálisis \iritáctico. El procediiniento match siriiplcmente ~ l e c l m m r . ferm' - E term' -t c: rerm' i tern~'-+ E opmirlr fitctor lerm' explorar cxtraer ( erp ) número i explora explorar i r~prnult * / extraer cxtraer extraer extraer extraer Tabla 4.Retuperacián de errores en analizadores sinticticos descendentes Una acción que puede tomar el analizador sintáctico en esta situación es insertar el sínibolo inicial en la pila y explorar hacia delante en la entrada Iiasta que se vea un símbolo en el conjunto Primero del sítnhulo inicial.es muy rudimentario: sólo esti iinplementada una forma muy primitiva de recuperación cn m«do de alarma. ~. los procedimientos e statement y factor t1eclrir:tn error cuando no se encuentra una seleccih correcta.9 Pila de análisis snátc itcio Entrada Acción %E'T1)E'T ~ E ' T ' E'T ) E' T ) E' $ E' T' ) a E' T $E' *)$ a )a ) )a $~ a cnplorur (error) extraer (error) E'-E $ a a concordar ' l.

lo (pie da corno rcsult:ido un comportarnicnto que es idéntico al de insertar .1 11: u n t i l x = 0. el analizador sintáctico no hace u n intento por evitar cascadas de error. 5: read x . El primero es que el procedinúento match no consume un Lokeii.CAP. i f O < x then fact:=l. el programa de muestra con un signo de punto y coma agregado después de 1 sentencia write . 4 1 A N A L ~ I SIINTACTICO DESCENDENTE es "unexpected token" ("token inesperado"). 5: 6: 7: 8: read x . 10: - :SIGNO DE PUNTO Y COMA ERRÓNEO! ) provoca que se generen los siguientes dos mensajes de error (cuando únicamente ha ocuin un error): Syntaxerroratline13:unexpectedtoken ->reservedword: end S y n t a x e r r o r a t l i n e 14:unexgectedtoken->EOF Y el mismo programa con la comparación eliminada en la segunda 1íne. 12: write f a c t 1 3 : end 14: * SIGNO DE COMPARACI~N PERDIDO AQUÍ! ). ocasiona que se impriman cuatro mensajes de error en el listado: Syntaxerroratline6:unexpectedtoken-> I D . 6: i f O x then { <7: f a c t := 1. name= f a c t Por otra parte..a de código . n a m e i x S y n t a x e r r o r a t l i n e 6 : unexpectedtoken-> re8ervedword:then S y n t a x e r r o r a t l i n e 6: unexpectedtoken-zreeervedword: then Syntaxerroratline7:unexpectedtoken-> I D . P ejemplo. Por ejemplo. repeat f a c t := f a c t x : = x . y el analizador sinráctico seguirá construyendo el árbol sintáctico correcto como si el signo de punto y coma hubiera estado allí desde el principio. x. (<1 3 : end 14: 9: * x. con lo que está realizando una forma rudimentaria de correccicín de error en este único caso.1 11: u n t i l x = 0.. 12: w r i t e f a c t . el cual puede no ser inuy útil para el usuar' Adcrnás. algo del comportamiento del analizador sintáctico de TINY es raionahle. 8: regeat 9: f a c t := f a c t 10: x : = x .. un signo de punto y coma perdido (rnás que uno sobrante) generará scílo un mensaje de error. Este comportamiento resulta de dos hechos de la codificaciíin. .

Esto no es un accidente.sign sent-cn11 / otro / srrrt-~~. se debe tener cuidado para asegurar que los apuntadores hernianos estén conectados dondequiera que se encuentre u n apuntador no nulo (los procedimientos del analizador sintáctico están tiiseñados para devolver un apuntador de árbol sintáctico nulo si se enciieutra un error).3 Dada la graniática rcrztenciri -+ srnt-<is.retitencia)y .un token perdido. En particular. buscar un token que no esté en el conjunto Siguiente. statement ( ) .s.rp .siyrzidenti f icador := e. Esto último es particularmente efectivo en la recuperación de errores. puesto que si se pierde un símbolo Primero.o bien. while (token==SEMI) match(SEM1). el análisis sintáctico se detendría.factor]. la manera obvia de escribir el cuerpo de stmt-seguence basado en el EBNF statement ( ) . 'También. 4. advertimos que el analizador sintáctico también se escribió de una rnanera tal que no puede caer en un bucle infinito cuando encuentra errores (el lector dehería haberse preocupado por esto cuando advirtió que match no consume un token no esperado). EJERCICIOS 4. 1 El lector puede advertir que los cuatro tokens en esta prueba negativa comprenden el con. mática mediante el método descendente recursivo. smt-crrll -e identificador ( crp-li.( A ) 14 I s escriba el pseudocódigo para el análisis sintáctico de esta gra. Dejarnos para los ejercicios un esbozo del comportamiento del programa para mostrar que un signo de punto y coma perdido en realidad provocaría que el resto del programa se omitiera si stmt-sequence se escribiera en la primera forma dada.y ambos consumen un token mientras generan un mensaje de error.junto Siguiente para stnit-sequence (secuencia-serlt). Esto se debe a que.1.1 Escriba el pseudocódigo para t e m y Jirctor correspondiente al psetidocódigo para esp de la sec: ción 4.2(página 150) que construye un árbol sintáctico para expresiones aritméticas simples.2 Dada la gramática A . 1 se puede escribir con una prueba de bucle inás complicada: statement ( ) . 4. while ((token!=ENDPILE) && (token!=END) && (token!=ELSE) && (tokenlsUNTIL)) i: match(SEM1). Finalmente. en una ruta arbitraria a través de los proeediinientos de análisis sintáctico.st ) . statement ( ) . ya que una prueba puede buscar un token en el conjunto Primero [como lo hacen los procedimientos para statement (. El segundo es que el procedimiento stmt-seguence se escribicí de mancra que conecte tanto del kbol sintktico como sea posible en el caso de un error. con el tiempo se debe encontrar el caso predeterminado de statement o factor.

2 (página 155) para reconocer las siguientes cadenas de paréntesis balanceados: a.7 4. e. Construya los conjuntos Primero y Siguiente para los no teminales de la gramática resultante. Construya los conjuntos Primero y Siguiente para los no terminales de la gramática resultante. d.8): lexp -+ utom 1 lirt atom -+ número / identificador list -+ ( lerp-seq ) lexp-seq -+ lexp . 3 * ( 4 . e. Construya la tabla de análisis sintáctico LL(I) para la gramlitica resultante.wq ) fexp-. Muestre que la gramática resultante es LL(1). lexp-seq / l e ~ p a.5 4.r~-seq) le. dada lacadenadeentrada(a ( b ( 2 ) ) (c)). (e)). 4.2.5 t 6 ) .y escriba el pseudocódigo para calcular el valor numérico de una lerp mediante el método descendente recursivo (véase el ejercicio 3. Construya los conjuntos Primero y Siguiente para el no terminal A.8 lexp -+ utom / list utonz -+ número 1 identificador kst 4 ( 1e.( 4 + 5 * 6 ) Muestre las acciones de un analizador sintictico LL(I) que utiliza la tabla de la sección 4.4 Dada la gramática lexp i número / op+t ( op krp-. b.CAP. c. Construya la tabla de análisis sintáctico LL(1) para la gramática resultante. 3 . Muestre que la gramática resultante es LL(1). ( ) ( O ) Dada la gramitica A .4 (página 163) pa ra reconocer las siguientes expresiones aritméticas: a. ( ( ) ) O b. Muestre las acciones de un'analizador sintáctico L. c. b. dada la cadena de entrada (a. 3+4*5-6 b.( A ) A 1 E . (b. c. ( 2 ) ) .kp-seq -+ lexp-seq lexp / Ie.13 del capítulo 3). d. pero no idéntica a la graniática dcl ejercicio 4.t. Factorice por la izquierda esta gramática. Elimine la recursión por la izquierda. ( 0 0 ) c. Muestre las acciones del analizador sintáctico LL( 1 ) correspondiente.6 4. 4. b.wq-+ lexp-seq Iexp / 1e. Muestre las acciones del analizador sintáctico LL(1) correspondicnle.9 Considere la gramática siguiente (similar. Considere la gramática /-/* 4.11) que utiliza la tabla 4.10 Considere la gramática siguiente de declaraciones simplificadas en C: .rp a. 4 1 ANALISIS I NTACTICO DESCENDENTE 4. Muestre que esta gramática es LL(1). i a.

si es que existen? a.14 4. Muestre que una gramática recursiva por la izquierda no puede ser LL(1). mero(B). a. PrimeroMB) = Primer»(A) @ Pri. b. a.19 4. dada la cadena de entrada i n t x. c. b. Defina el operador @ sobre dos conjuntos de cadenas de token SI y S?como se describe a continuación: S. ¿Es probable que una gramática de lenguaje de programación tenga un símbolo inútil'? Explique por qué.4 (página 163).y) / .11). Factorice por la izquierda esta graniática. entonces (Primero(a) @ Siguiente(A)) n (Printe. Construya los conjuntos Primero y Siguiente para los no terminales de la gramática factorirada por la izquierda del ejemplo 4.Cuálesson las desventajas de un esquema de esta naturaleza. Un no terminal A es inútil si no hayderivaciiin del símhulo inicial para una cadena de tokens en la que aparezca A.I. a Muestre que. wr-list / i d e n t i f i c a d o r t 4.Ejercicios rlecl~rrcrción . Muestre las acciones del analizador sintáctico LI. Se presentan posibles entradas predeterminad&s en renglones de no termiiial cuando un no terminal tiene una «pci<in de pr«ducción simple o cuando tiene una produccidn e .20 a. Constntya la tabla de análisis sintktico LL(I) utilizando los resultados que obtuvo en el iticiso a.16 4. Aplique estas ideas a la tahla 4. ¿Puede ser ambigua una gramática LL(1)7 Justifique su respuesta. b. ~-+ i d e n t i f icador. Construya la tabla de análisis sintáctico LL(l) para la gramática resultante.l i .1 5 4. b. m@) @ Siguiente(A)).B. (Véase el ejercicio anterior.4. Muestre que las dos condiciones del teorema en Ia página 178 se pueden reemplazar por la condici6n simple: si A + a y A . En inuchos casos todas las entradas en blanco en un renglón se pueden reemplazar por una sola entrada predeterminada. si ima gramática tiene un simbolo inútil. el cálculo de los conjuntos Primero y Siguiente como se dio en cste capítulo puede producir cmjuntos que sean demasiado grandes para construir con precisión una tabla de análisis sintáctico LL( 1). c. b.es vacío.1 1 4. para cualesquiera dos no terminales A y B. generalmente tiene muchas entradas en blanco que rcpresentan errores. Una tahla de análisis sintáctico LL(1).(I) de dos condiciones en sus conjuntos Pnmero y Siguiente.17 4.18 4.(I j correspondiente.12 4. .13 4. ¿Puede una gramática ambigua ser LL(I)?Justifique su respuesta. Demuestre el teorema de la página 178 que concluye que una gramática es L. y . ¿Una gramitica ambigua debe ser LL(I)'?Justifique su respuesta. Proporcione una formulación matemática de esta propiedad. Dé los detalles del cálculo de los conjuntos Primero y Siguiente que se exhiben en el ejemplo 4. e. @ S2 = {Pnmero(. 15 (página 178). z.r C S i . tipo vur-list tipo -t i n t / f l o a t w r . d. Construya los conjuntos Primero y Siguiente para 10s 110 terminales de la grun~dtica resultante. Proporcione un algoritmo que elimine no terminales inútiles (y prodiscciones asociadas) de una gramática sin cambiar el lenguaje reconocido. tal como la de la tabla 4. c. con lo que se disniinuye el tamaño de la tahla de manera considerable. Muestre que.16).) Muestre que si una gramática carece de no terminales inútiles. Muestre que la gramática resultante es LL.7 (página 165). y E Si ). lo opuesto al teorema de llpái gina 178 es verdadero (véase el ejercicio 4. ¿.

CAP. 4.1.25 La recuperación de errores en modo de alarma para la gramática de expresión aritmética simple.9 (pBgina 181). Por ejemplo. 4. según esta fórmula. b. También es necesario que el procedimiento A se escriba como una función booleana que devuelva un éxito o un fracaso.22 En la gramática TINY de la figura 4. y explore su operación en la cadena auau$. así como también una precedencia más baja que la de tudm los operadores aritméticos.23 Agregue los operadores booleanos md. else error . e incorpore esto a la solución del ejercicio 4. pero e1 rnanejador de errores consume el segundo kictor sin rcioiciar el análisis sintictico. Una es que los ciclos o bucles while que prueban operadores deberían continuar funcionando en ciertas circunstancias. Vuelva a escribir la respuesta al ejercicio 4.jereicio 4. y de modo que. Se puede escribir un analizador sintáciico descendente recursivo en reversa para este lenguaje.5. a continuación tenemos un programa TINY sintácticamente legal: if O then write 1>0 else x := (x<l)+l end Vuelva a escribir la gramática TINY de manera que s61o se permitan expresiones booleanas (expresiones que contengan operadores de comparación) como la pmeba de una sentencia if o repeat. Por ejemplo.23 para distinguir rigurosamente entre expresiones booleanas y aritméticas.23 empeoraron el problema que se describió en el e. la expresión ( 2 f ( 3 ) perdió su operador entre ios tjctores. Vuelva a escribir el pseudocódigo del ir~ciso b. A. Muestre que este procedimiento no funcionará comctamente. pero se necesita usar un proc. el código pueda continuar para intentar la alternativa A + E. Un intento de cscribir uri analizador sintáctico descendente recursivo para esta gcmiátic'a estd representado por el pseiidocódigo siguiente. c. pueda probar si hubo un éxito antes de consumir otro token. else i f token o $ then error . procedure A . y sólo se permitan expresiones aritméticas en sentencias write o assign o corno operandos de cualquiera de los operadores. end A .21 Dada la gramhtica A i u 1 E. de manera que cuando A se llame a sí misrna. uA a. if token = a then getToken .24 Los cimbios a la sintaxis de 'MNY en el ejercicio 4. Asígneles las propiedades descritas en el ejercicio 3. 4.9 (página 181) no se hace tina distinción clara entre expresiones booleanas y aritméticas. si la selección A -+ u A o fracasa. Vuelva . Muestre que esta gramática no es LL( 1). 1 1 ANALISISSINTAUICO DESCENDENTE 4.22.5 (página 139).edimiento urrCetToken que tome un token como parámetro y devuelva ese token al frente del flujo de tokens de entrada. como se describió en la sección 4. todavía tiene algunas desventajas. o r y not a la gramática TINY de la figura 4.i escribir el pscudoc6digo para niejorar su comportamiento cn este cabo.22. . 4. begin if token = u then gerToken . Asegúrese de que cualquier expresión pueda ser booleana o entera.

icticu pro(lrici<lopor su ci>digti Jel inciso a y ileviielv. c.30 Vuelva a escribir la calculadora descendente recursiva de la figura J.Ejercicios de programación 193 4.139) de manera que calcule coi1 números Je punto tlotante en lugar de mteros.~iincorrecto)?: c.Qué árboles sint&cticos se construyen para 10s programas en los incisos a y b iitiliiiindo esta versión del procediniiento stmt-sequence'? EJERCICIOS DE PROGRAMACIÓW 4..26 Explore les :icci»~iesJe ~ i o~rali~:tdor n \i~it.. 4. (Advertencia: este operador tienc inayor precey dencia que la ~uultiplicacidn es asociativo por la derecha.32 a.3 ) *4-+5.29 Agregue IO siguiente a 121 eaicuiat~orasimple descendente recursiva de nritiii~tica entera cie 13 figura 4. ritilizan<lo .) 4.i- b. : pesar del 4gno Je punto y cwnn per<Jido: para iiititlo de . l .í 1) ile ia figura J. 1 de nianera que <ii. Menos unitario con el sítnbolo -. 1 b. Escriha una St~ncitin que tome corno pariirietro c l :irbol sint.) 4.17.9 (págin. en vez de sinrpleme~ite ciilcular todii como números enteros o de pnntti tlotante.? ipigirra 1%) 1 .r crricrtlado al recorrer el irbol.~nte. (Véase el ejercicio 3. 1 . c .rci(íri tlcl ptoce~liinienttistmt-sequence cn c l . la recuperación <le errores dada en la tabh 4. I (phginas 148. statement ( ) . Vuelva a escribir la calculadora descendente recursiva de la figura 4. Suponga que en su lugar se escrihiti c l procedimiento stmt_sequence utilizando el código m i s simple esbozado en la pigina 189: statement ( ) .3 ) * 4 .~ l v.+ 5 .i marca que indica si es erttero o de punto tliX. .icuerdo con las declarrtciones de la iecci~ín 3 . Exponenciación entera con el símbolo ". Explore la oper. while (token==SEMI) f match(SEM1). (St~gerencia:un "valor" es ahora utr rcgistro con un. Módulo entero con el síinb«lo %.inalimdor sintictico T l N Y parü verificar cjue se construya tl i r b o l si11t5cticiicorrecto para e1 siguiente programa en I TlNY. I i~riple~iientar reciipcracitin de errores C ~iiiento L entr:ida ( 2 + .ilr.y cxamirie su cwriprirt..28 a.3 1 Vuelva ii escribir la calcul&ra descendente recursiva de ia figura J.Qué i r b o l sintrictictl se construye para LI siguiente prograni. b. 4.27 Vuelva ii escribir el . División entera con e l símbolo / .) d.ictic» 1ti l )en I:i eiitriida ( 2 + .i 1x7).iIarnr.i con~pleto. página 140. en a 4.ilgi~rirniode an5lisis sintk'tictl L1.siingu entre valores de punto flotante y valores enteros. páginas 148-1. 2 (p:ipinii 1 I 1 ). I de t1i:itiei:i que de\'i~elva iin :irbtil sintktico de .49 (asegúrese de que tengan precedencia y asociati\ idad correctas): a.

36 4. Esto requerirá volver a escribir el procedimiento syntaxError.1 (phginas 148-149) para implementar la recuperación de errores en modo de alarma como se esquematizó en la sección 4. Por ejemplo.4.22 al analizador sintáctico de TINY. a.4 (página 160). Escriba un procedimiento que tome la estructura de datos producida mediante tu procedimiento del inciso b y encuentre concordancias de subcadenas más largas en un archivo de texto según el DFA que representa.34 Considere la gramática siguiente: ~' lexp -P número / ( op l q . Agregue conjuntos de sincronizwión de tokens y recuperacik de errores en modo de alarma al analizador sintktico de TINY.35 4. y que ningún mensaje de error especial se pasa al procedimiento match desde sus ubicaciones de llamada.40 4. en vez de imprimir tanto el token actual como el token esperado.5. Una de las razones por las que el analizador sintáctico de TINY produce mensajes de error deficientes es que el procedimiento match está limitado a imprimir s6lo el token actual cuando se presenta un error.4 1 4. página 138 (con una eliminación apropiada de ambigüedades) para construir un analizador sintáctico descrndent recursivo que lea una expresibn regular y realice la construcción de Thompson de un NF (\'&aseel capítulo 2). b. Escriba una función que torne como parámetro el árbol sintáctico producido por su códig tiel inciso a y devuelva el valor calculado al recorrer el árbol. 1. Incorpore sus cambios gramaticales del ejercicio 4. (¡Su programa ahora se ha convertido en una versión "compilada" de grep!) Agregue los operadores de comparaciún < = (menor o igual que).junto.s e q ) op++/ /* lexp-srq + krp-seq le.23 al analizador sintáctico de TINY. > = (mayor o igual que) y < > (distinto de) al analizador sintáctico de TINY. la expresión 3 4 . Escriba una calculadora descendente recursiva p&ualas expresiones dadas mediante esta gramática. pero utilice la gramática de la figura 4.42 4.rp / lexp - 4. c.33 Escriba una calculadora descendente recursiva para aritrntitica entera simple semejante al de la figura 4.así como los cambios a las Ilaniadas de match para incluir un mensaje de error apropiiido. (De Wirth [107h]) Las csuucturas de datos basadas en diagranias sintáctiios puede11ser utilizadas por un analizador siritáctico descendente recursivo "generico" que analizará sint6ctic.37 4.4. Agregue mensajes de error útiles a su manejador de errores del inciso a. b. a. Escriba un procedimiento que tome una estructura de datos NFA producida por el analizador sintictico del inciso a y construya un DFA equivalente según la construcción de subcon.) Incorpore sus cambios gramaticales del ejercicio 4. Utilice la gramática para expresiones regulares del ejercicio 3. (Esto requerirá agregar estos tokens y modificar también el analizador léxico. Vuelva a escribir el procedimicnto match de manera que imprima el token esperado además del token actual y que también pase un mensaje de error al procedirniento SyntaxError. de la manera que se describió en la página 184. a.34 ( * 3 4 2 ) ) .39 4. Invente una estructura de árbol sintáctico para la gramática del ejercicio anterior y escriba u analizador sintáctico descendente recursivo para la misma que devuelva un árbol sintiictic b.1. pero no debería requerir un cambio en el &rbol sintáctico.tmente . > (mayor que). 4. Vuelva a escribir el programa de la calculadora descendente recursiva de la figura 4.43 Esta gramática puede imaginarse como representación de expresiones aritméticas enteras sim ples en forma prefija tipo LISP.38 4.3 * 4 2 se escribiría en esta gra máticacomo ( .

Escrih:~un gcriernilor Jc ~i~i.i estructura de íl.) b. la estructura de datos para la regla gramatical 1 FALCE NULL TRUE NULL 1 NULL donde los campos de la estrtrcttira de registn~se muestran de la m. ) struct rulerec *rule. attr. . ün.Ejercicior de programación 195 cualquier conjunto de rcglas gratii. union ( Token name.f. mierttrüs que cl campo other ("otror'i se eniplea para apuntar a las alternativ:is l. int isToken.o que utilice cstas ehiriicturas de datos para rcwiiocer i m i c:irlena de cntrad:i.~li/ador iint~ctico Ie:i rcgliis WNF (yü sea d e t i c iiii :irchivo qiie i~ desde I. c. (Sirgerrrrcirz: neccsitxd i i n tokcn especial para represcmir iriicriiainente s cn la cstriicairü <ledatos. gioa 160.i ciitrada i.i111l. pi14 . El citmpo next ("siguiente") se utiliza para apuntar al sigiiiente clementci en la regla pniittical. 1 Rulerec.. Dibuje Iiis zstriicturiis de datos para el resto dc las reglas graiiiaticalcs de la figura .*other. dadas pos el t~ictaiiir~boloL>e este modo.( I ).itos adecuiida esti ~l:i&i por las siguicntes cIccl:ir.inera siguiente I isToken otro siguiente 1 I a.\r. Esrihii iiii procedimiwco de arr5lisis iintktico gcn6ric.iticales F.iciones en C : tygedef struct rulerec ( struct rulerec *next.iriy geiierc las ewiictiwas dc datos precetieiiits.

Los an:ilizadores si~itácticos descendentes recursivos en revers se han vuelto populares en lenguajes funcionales lentos tiierternente tipificados. principalmente porque la herramienta más atnpliani utilizada. donde esta fornia de . muchos métodos inás de a~iálisis sintáctico descendente apart los dos que acab~mos estudiar en este capítulo. véase P m . se estudiará en el siguiente &pítulo. Dietz y Cohen (19921. La recuperación de erntres en modo de alarma se estudia en Wirtti (19761 y Sti [1985]. Para un c+mplo de otro método m 6 de neral véase GrUham. 19631. Airtlr gene un aiialirador sintáctico descendente recursivo desde una descripción EBNF. vi'ase Hoare j19621. El análisis sintáctico LL(I) fue ampliamente estudiado en la década de los sesenta principios de los setenta. Harrison y Ruzzo 119801. Existen. DESCENDENTE NOTAS Y REFERENCIAS ~i análisis siniáctico descendente recursivo ha sido un método estindar para la construcc de anafizdore"h"'cticoda& principios de la tlécüdü de los sesenta y la inrroducci6n reglas BNF en el informe de Algo160 [Naur. Uno de ellos se llama Arttlr y es parte del co junto de henmientas de construcción de compiladores de Purttue (PCCTS. Para una descripción inicial del n todo. incluyendo un mecanismo integrado para construir árboles siiitáctic Para una revisión de un generador de anzilizador sintictico LL(1) deriominado LLGen.C P 4 i ANÁLISIS IINT~CTICO A. Una in tigacióti sobre el análisis sintáctico LL(k) se puede encontrar en Fischer y LcBlanc 119 donde puede hallarse un ejemplo de una gramática LL(2) que no es SLL(2). Dietz y Cohen [1992]. Yacc. . Purdue Compil Constructiim Tvol Set). En este capítulo no se analizaron herramientas automáticas para la generación de an zadores sintácticos descendentes. Véase una descripción en Par.rnálisis sintáctico descendente recursivo se de iniria ariálisis sintáctico de combinación. por supuesto. existen buenos genera res (te analizadores sintácticos descendentes. El uso de EBNF junto con el análisis sintáctico ce~idente recursivo hit sido popularkado por Wirth 119761. Para usos prácti del análisis sintáctico LL(k). tales co Haskell y Miranda. La recuperación de errores en analiradores sintácticos LL(k) s<testudia eri Bu Fischer [1987]. Para una descripción inicial véase Lewis y Steams ( 19681. vé Fischer y LeBlanc [lY91]. Para una ~íescripción este método véase a Pey de Joties y Lester [lY92] o Hutton f19921. Tiene diver características M e s . Sin embargo. Métodos de reparación de errores más sofisticados se estudian en Fisc y LeBlanc f19911 y Lyon [1974].

Capítulo 5 Análisis sin tác tico ascendente 5.3 5. Una consecuencia de la potencia del análisis sintáctico ascendente es el hecho de que también es significativo hablar de análisis sintáctico LR(O). y si esto ocurre no cuenta como búsqueda hacia adelante. donde no se consulta ninguna búsqueda hacia adelante al tomar decisiones de análisis sintáctico.) Una mejora en el análisis sintáctico LR(0) que hace algún uso de la búsqueda hacia adelante se conoce como análisis sintáctico SLR(1) (por las siglas en inglés de análisis sintáctico LR(1) simple). nos limitaremos a estudiar aquellos algoritmos de análisis sintáctico que utilizan como máximo un simbolo de búsqueda hacia adelante. Como con el análisis sintáctico descendente. Esto incluirá la construcción de los DFA de elementos LR(0) y LR(I). Con una terminologia similar a la de los analizadores sintácticos LL(1). "análisis sintáctico LR(1) de búsqueda hacia adelante'). LR(l) y LALR(I).6 5.4 Perspectiva general del análisis sintáctico ascendente Autómatas finitos de elementos LR(0) y análisis sintáctico LR(0) Análisis sintáctico SLR(I) Análisis sintáctico MLR(1) y LR( 1 ) general 5. "Rightmost derivation". (Esto es posible porque un token de búsqueda hacia adelante se puede examinar después de que aparece en la pila del análisis sintáctico. "Lek-to-right". mientras que el número I indica que se utiliza un símbolo de búsqueda hacia adelante). así como la construcción de las tablas de análisis sintáctico que se asocian con ellos.7 Yacc: un generador de analizadores sintácticos UJILR(1) Generación de un analizador sintáctico TINY utilizando Yacc Recuperación de errores en analizadores sintácticos ascendentes En el capitulo anterior contemplamos los algoritmos de análisis sintáctico descendente básicos del análisis sintáctico predictivo y descendente recursivo. un generador de analizador sintáctico CALR(1) y con él generaremos un analizador sintictico para el lenguaje TlNY que cons- . o sea. pero incluiremos algunos comentarios sobre cómo extender los algoritmos. Abordaremos las construcciones necesarías para cada uno de estos métodos de análisis sintáctico en las secciones siguientes. el algoritmo ascendente más general se denomina análisis sintáctico LR(1) (la L indica que la entrada se procesa de izquierda a derecha. la R indica que se produce una derivación por la derecha. También describiremos el uso de Yacc. En este capitulo describiremos las principales técnicas de análisis sintáctico ascendente y las construcciones que se asocian con ellas.5 5.2 5. Un método que es un poco más potente que el análisis sintáctico SLR(I) pero menos complejo que el análisis sintáctico LR(L) general se denomina análisis sintáctico LALR(1) (por las siglas de "lookahead LR(1) parsing".1 5. descripciones de los algoritmos de análisis sintáctico SLR(I).

También continuaremos con el uso de algunos de I ejemplos recurrentes del capítulo anterior (expresiones aritméticas enteras. así como las propiedades generales de las gramáticas libres d contexto. Reducir una cadena »i en la parte superior de la pila a un no terminal A. los cuales utilizar mos a través del resto del capítulo. secciones 3. Desplazar o transferir un terminal de la parte kontal de la ciitrad. pero son muy adecuados para generadores de analizadore sintácticos como Yacc. Al principio del capítulo daremos dos ejemplos de esta clase.3). . tendremos que describirlas con cuidado y presentarlas por medio de ejemplos de gramáticas muy simples. la entrada está en el centro y las acciones del anali~ador sintáctico est'úi a la derecha (en este caso.~ hasta la parte sup r i o r de la pila.2 y 3.3 y 2. Por consiguiente.3 Comenzamos este capítulo con una perspectiva general del análisis sintáctico ascendente. de manera semejante a como lo hace un analizador sintáctico descendente no recursivo. ya que esto seria demasiado complejo. por lo tanto.4). El diseñador de un lenguaj de programación tambien puede beneficiarse de esta información.). 2. "aceptar" es la única accidn indicada). Un esquema para el análisis sintáctico ascendente es.1 -+ N. de manera que el escritor del compilador pueda analizar apropiadamente el comportamiento de un generador de analizadores sintácticos. puesto que un g rador de analizadores sintácticos puede identificar problemas potenciales con una sin de lenguaje propuesta en BNF. secciones 2. sección 4. y tambikn algu informacióri de estado adicional que analizaremos posteriormente. es importante comprender el funcionamiento de I métodos. Los temas vistos con anterioridad que serán necesarios para comprender el funcio miento de los algoritmos de análisis sintáctico ascendente incluyen las propiedades de autómatas finitos y la construcción del subconjunto de un DFA a partir de un NFA (ca tulo 2.1 PERSPECTIVA GENERAL DEL ANÁLISIS SINTACTICO ASCENDENTE Un analizador sintáctico ascendente utiliza una pila explícita para realizar un análisis sintác tico. todos los métodos ascendentes importantes son realmente muy complejos p hacer a mano la codificación. la recursión por la izquierda no es un problema en el análisis sintáctico ascendente. $ aceptar donde la pila de análisis sintáctico está a la izquierda. no realizaremos a mano ninguno de los algoritmos de análisis sintácti ascendente para el lenguaje TlNY completo. Los algoritmos de análisis sintácticos ascendentes son en general más poderosos qu los métodos descendentes. Ocasionalmente también serán necesarios los conjuntos Siguiente (capítulo 4. Sin embargo. sentencias I etc.'D hecho. Un analirador sinláctico ascendente tiene dos posibles acciones (aparte de "aceprar"): l. Sin embargo. dada In selccciiin BNF .) Como es de esperarse. La pila está vacía al pr' cipio de un análisis sintáctico ascendente y al final de un análisis sintáctico exitoso contendr el sírnbolo inicial. i.truye los mismos árboles sintácticos que el analizador sintáctico descendente recursivo que desarrollamos en el capítulo anterior. La pila de análisis sintáctico contendrá tanto tokens como no terminales. derivaciones y árboles de análisis gramatical (capítulo 3. (Por ejemplo. las construcciones invo lucradas en estos algoritmos también son más complejas.

t i i d m n al escrihir la pnlahrü ~ . (.i k ~ s pr. tel .2 Considere la siguiente grainitica aumentada para expresiones aritméticas rudinieritariüs (sin par-éntesis y con una operacicín): lJn anilisis sintictico itscentlenie tle I. se .ictic« :rscend.1 Cotisidcre la siguiente gramática stiinentada Imra paréntesis hül. por I-.i icciiiii <le.I~ L ~~cs ~III~IIII~C% Je gcneriiciiiti prir ~~onciird.i.1 / Pila de análisis sintáctico Entrada Acción - O$ )5 )S desplazatnicnto reducciót~ S 8: dcsplazatnietito - Ejemplo 5.izoties técnicas que se coiiicritardn rnás .as acciones de reducci<iiisi.S-+ ( S ) S j e U n análisis sintictico :tsccirdente de la cadena ( ) en el que se utilizn esra g ~ i i n i t i c a prose porciona en la tabla 5. al .izaririetito se itidicai~ escribir Ict !xil. Iss graniiticas siernpre se aumentan coi1 u n nuevo síinholo inicial.cioii. r e t l u c c i ó ~ i Otra c.inccodos: S' 4 S . e s i o ir« se m)\tiiinhra.<. i h . Por I I 111is1iii1r .ihra s h ~ $o ~!uslilu:rirnicrirr>.alelnnte. 1 3 el c ~de li t t i a r d ~ ~ c c i ~ í ~? . i ~ ~ . pi ~ iit~ ~c wlwci~iin wnii~ Iiicin~os p:li:i rin.1 Rtriones de análisis sintactito de un analizador rintactito arcendente para la gramatita del ejemplo 5.i nviin.Perspetrwa general del analis~rilntactico artendente 199 t'or esta F:r~ón ocasiones uit aiiülimitir siiir.iclii.as ncciones de tlespl.~iimediante estos cjernplos.~ c n la. . . ctin una ~irorliicción simple coi-respoiidiciiie al sítriholo iniLica i i r i IILI~. y tlespués conientcircinos ~ I g t i i i u s las pt-opicdades del ariálisis sintáctico asceiidcnte que se niuestr. i l i \ i ~in1. Tabla 5.grib. Ejemplo 5.r<l . los : ~ i x ~ l i m d <4nkícLictis dcsceiidetnks w p(itlrkin ~I~III. peri! 2.d r í a teswihir ~ i ~ n p l e i ~I~I~ ~ t e IIhF por h í III~III:I. e d r ~ w o rcvllrwkjti y pi-op«l.igregii a la grairiái irricial S'.2.o~ l e s w ? d ~ ~ i t e . + n en el que se titilira esta grnmáticn se l .iricia.ar I.i.' [.ir la seleccihi BNF titilimda . ~ c i i < ~ i ? i <cri.i cadena n 3 proporciona en 1 tabla 5.V» sí~iibtilo cial anterior: de Contiiiiiitrenirts de iiiitiediato con dos cjeiiiplos. Esto h i y i f i c ~que si S es el siinbolo inicial.tractcrística <le los ürtalimdores sintácticiis iiscentleiitcs es que.1.rnie se ctiiioce coino ai~alizadix en siniictico de reducci6n por desplazamiento ("shift-reduce"). :~c<~\klt~ihr.iii.

Sin em go. pero los pasos derivación se presentan en orden inverso. Por ejemplo. De este modo. De hecho. un analizador sintáctico ascendente puede necesitar examinar más allá de la parte supe de la pila para determinar qué acción realizar. E está en la pila y ocu un desplazamiento. debemos saber que la pila en realidad contiene la c ( S ) S en ese punto. la forma sciitenciol dcrwha E t n. dientes en modo inverso a los cuatro pasos de la derivación por la derecha: S'=+S* (S)S* ( S ) * (0 En la tabla 5. en la línea 3 de la tabla 5. Como es natural. reducción E . mientras que la Iínea 6 también tiene S en la parte superior de la pila. y el analizador sintáctico realiza uiia reducción mediante la pmd S --t ( S ) S. en la tabla 5. Esto no es ni con mucho tan serio como la entrada de queda hacia adelante. un anaíizador ascendente puede transferir símbolos de trada en la pila hasta que determine qué acción realizar (suponiendo que se puede deteni una acción que no requiera los símbolos psra ser transferida de regreso a la entrada). el análisis sintáctico ascendente requiere cie una "p búsqueda hacia adelante" arbitraria. mientras que en la Iínea 6.2 A(&ner de analirir rinrat&c@ de un analizador rintictico ascendente para la gramática del ejemplo 5. pero el lizador sintáctico efectúa una reducción mediante S' -+ S. E está nuevamente en la pila pero ocurre u reducciSn mediante E' -+ E. que se prcwita Por . i.2 la derivación corre5pondiente es Cada una de las cadenas interrnecfias de terminales y no terminales en una derivacihn así se conoce como furnia sentencia1 derecha. no basta con hacer el seguimiento del contenido de la pila para p determinar de manera única el siguiente paso en un análisis sintictico de reducción por plazamiento. De este modo.2. Por ejemplo. La diferencia es que en la línea 3 el token siguiente en la entra es +.jcntplo. obsewemos de nuevo que un analizador sintáctico de reducción por plazamiento sigue una derivación pur la derecha de la cadena de entrada. En primer lugar.Tnbla 5. cualquier al ritmo que realiza ese anáiisis sintáctico debe utilizar el siguiente token de entrada (la búsq hacia adelante) para determinar la acción apropiada. Para poder saber que S -+ ( S ) una reducción vjlida para el paso 5. El mecanismo que efectuará esto e autómata finito determinfstico de "elementos" que se describirá en la sig~tiente sección. también puede ser necesario consultar el token siguiente en la entr búsqueda hacia adelante. puesto que el analizador sintáctico mismo construye la pila y puede c e x la información apropiada para que esté disponible.1 la línea 5 tiene parte superior de la pita. Ante examinar los algoritmos individuales haremos algunas observaciones generales acerca de c pueden caracterizarse las etapas intermedias en un anhlisis sintáctico ascendente. En la tabla 5. Cada una de esas formas sentenciales se divide entre la entrada y l.1 tenemos cuatro reducciones. corre.2 Pila de análisis sintactico I Entrada Acción 3 4 5 h 7 desplazamienlo .i pila de análisis sintáctico cLnr:inte un análisis sintUctico de rcjuccihij por iiespla~amiento. mientras que en la Iínea 6 el siguiente token de entrada es $. Diferentes métodos de miilisis sintáctic reducción por desplazamiento~utilizan búsqueda hacia adelante de distintas manera& y da como resultado malizadores sintácticos con una complejidad y potencia variadas.n desplazamiento desplazamiento reducción E + E + n reducción E' E aceptar Los malizadores sintácticos ascendentes tienen menos diticultades que los descendentes la búsqueda hacia delante.

2.1 Elementos lR(0) .Automatar íioitas de elementos LR(0) y analirir s~ntacttco L@O) 20 1 i! AUTOMATAS FINITOS DE ELEMENTOS LR(0) Y ANALISIS si~dtrico LR(O) 5.

S S' -t S. el elemento A -+ f3 y construido de la regla gramatical A-+ ct (con u = @y) ya se ha visto p y que se pueden derivar tos siguientes tokens de entrada de y. Un elemento A + u. Ejemplo 5.)$ S-+ ( S ) . n E-+E+n. S I ANALISIS IINTAC~ICO ASCENDENTE metasfmbolo para no confundirlo con un token real). 5 2 2 Autómatas finitos de elementos L. . y si P y y son dos cadenas cualesquiera de símbolos (incluye~ido cadena va la e).iccrca cle la pila de . Éstos se conocen como el p mentos LR(0). .4 La gramática del ejemplo 5. S-+ .u significa que podemos estar cerca de reconocer una A mediante el uso d selección de regla gramatical A -+ u (a los elenlentos de esta clase los denominamos elernen iniciales). E . S S-+ ( S ) S .E E'-+ E.2 tiene los siguientes ocho elementos: E' -t .rmiento. La idea detrás del concepto de un elemento es que un elemento registra un paso interm en el reconocimiento riel iado derecho de una opción de regla gramatical específica. entonces A --.inálisis sintáctico y dcl progreso de un aiiilisis de retluccicín por ilesp1a~.E+n E-+ E. En término pila de anjlisis sintáctico. .+ .os eleirteiitos LR(0) se pueden emplear como los estados de rro nutcímata finito que mantienen inf»rrnacicín .y es un elemento LR(0).1 : S ' -+ S S-+ ( S ) S / e Esta gramática tiene tres reglas de produccicín y ocho elementos: S' -+ . De este modo. S ) $ S-+ (S. porque no contienen referencia explícita a In búsqueda hacia delante. S-+. Esto comcnr~ri como un autcíniata I'inito no detcrriiitiístico. si A a es tina regla producción. ( S ) S S-+ ( . E-+. En part significa lar. - Ejemplo 5. tales como py = u. significa que u reside ahora en la parte superior de la pila d análisis sintáctico y puede ser el controlador. esto significa que P deberá aparecer en la parte superior de la pi elemento A -+ . si se va a utilizar A -+ u para la siguiente reducción (tales elementos se conocen como elementos ~ompletos).CAP.+ n E-+ E + .3 Considere la gramática del ejemplo 5.n E-tn.

penr de . De este rnodo.~ no CII 1.S se corivierte en el estado inicial del N F A . t\hora. y el eleniento inicial S' .iccpr:~ciíin. - . En fortnn gráfica esto se escribe coino pi. si X es u11rio ter~ninid. donde S es el síinholo inicial de la gronráticii.R(O).ttle~rasdil-cctomente.. una Iransicicín de esta clase tc:ckivía correspontlcrá a la inserciiin de X en la pila clusanie un ariálisis siirtiíctic». y el %FAno rieccsita coiitciicr esa inforrniiciíin. ~i~ F i ~ coinpletii \ i r t i c w i p c i h ilcl UFA de ~ l e r i x r i t ~ ~ s .n Xq ilebeirios agregar uiia transicicín I-: .i t'orin:~de IIII estado . De hecho.is iIcI c:ipítcilo 2.i de !a . prinlo indica que estamos cerca de recitiiocer . el niialirnclor sirirrictico r i ~ i m o tleciclirá cuántlo iiccptnr. . entonces pura caciiielenienttr A . C'c~iiici~tareinc.\lioni \ o l r c r e i i ~:i las Srao t!(is cjcrnpliw :~ntcsii~sc~ !coiist~.t + cu y y suponga que y ctirtiienzii con el siniholo X.ilg~iii.Y nunca aparecerá ccirno un sítnholo de entrada.@ representa el coniicnzo de este proceso (el .) pila durmtc un análisis sintáctico.icinn . Entonces S' se convierte eii el estado inicial de la gramiitica aumentada.\ ciiat~clo c i .. Desgraciadamente. rio secoriocer c.le :iccpi.it. el cual puede ser nt1 fokcri « t i t i no terexiste una minal. i. m i n o están disefiados para h. cie iii:iiiera que el elcrrrerittt se piied~i escrihir conlo :l -+ «i Xq. Ésta es la raztjti por I:i que itunient. en cntln regla de protl~iccirín -+ f3 de X. .icerlo los citittirnat. La soltici6n es aumentar la gnitnática tnecliante una produccicín simple S' -t S.i I.i.o: construido ii partir de uitü regla (le prtwluccicíri p:ira S podría servir como estado inicial.iccptacitin en :~hsoluto. Por otra parte.E~itor~ces Y t r a n s i c i h en el sí~nholti ilcsdc el c\taclo rcpseseritado por este elcirieirto hasta el estado recx X 2.Cí.R(O). ilc r~i.~rir-crticis VI:\ de c I ~ i i i e i ~ . puede haber muchas reglas tic produccirín tle esta clase para S. E l estado inicial del N F A tlehería corresponder al estado inicial del arializador sintáctici): la pila es15 vacía.( E l NFX r ~ r d r (. puesto que . ianihi6n es fácil coristruir el U F A de con. inJicmtlo que se puetle pf»ducir X iiicdirir~ce seX el co~ii~citnierito ctlalquiera de los lacios tierechos de sus producciories. c o n ~ overeinos.p.Cuáles eskalos deherían coiivertirse cn estados ilc . no podernos. cualquier elctxicnto ittickil S+ .escntnd« por el eleinento A i \ . corno .entonces la iittcrpretacicíii de esta transicih es i ~ i á s coiiipleja.i b a r n o s los :IIesto h goriririos (le ariálisis iiiil.Autómatas finitos de elemestot IR(@! y analirir rintáctico LR(0) 203 partir de este NFA cle clcnienlos LIZ(O1podernos construir el D F t i ilc con~untos clecle rnetitos LR(0) utilizando la coiistrucción tiel suhcorljurito del capítulo 7.ici(ín.ictico (pie risa c l . i r Si X es un iokeii.iticraqiic e1 NFA <te 1iech0 no tcndr6 estados i inforrn. donde S' es un riuevo no terminal. entonces esta trnrisicirín corresponde n ~ ~desp1a~:irniento de X desde la eritratln hacia la parte superior de 1.t 6). .tirtii~n. Queda por analizar la seleccih del est:ido inicial y los estados tle aceptacirin del N F A . tie ocurrir tlurante una rcd~iccitin I la que intervenga ~ i i l a una reducci6n de esta clase debe estar precedida por el reconociinierito de una b. y el estado pr<prcionarlopor el elemento inicial X -+ .iccrc. pero esto srílu piieCI protluceih X .juntos de elementos LR(0) tic tiranera directa. (le Estos dos casos representan las únicas tratisiciorics en el N F A de clerirentos LR(0).Cuálch son las tratisiciorics del NFI\ de elerrientos L N O ) ? <'oiisitlere el eletnento .icept~icitíricri este NFit'?:\qtií ticbeo nios recordar que c l pr«pcí~irctdel NFt\ es tnantetier ~ i xgiiiiniento del estado ile w i análisis siiitáctico. De este iiiotlo.itnos las gycitnáticas de los ejemplos artteriores. y estanios por reciinocer un S.ino ptrdetnos decir cuál utilizar'! De hecho. 111ci1ici15 si~iiples de lt<~~ .

Advierta que c elemento de la figura con un punto antes que el no terminal S time una transición o para do eleiiiento iriicial de S.5 jemplo 5.4 cnurnerarrios los elementos L.6 En el cjcmplo 5. debemos cunsiruir el DFA de conjuntos de elententos correspoiidient al NFA de los elcriientos de acuerdo con la constritcciítn de suhcoiijuntos del capítulo L.1. (Esta situacicín se presetmrá en toda l. Advierta que el elerneiito inic E --+ E + n tiene tina transicicín E hacia sí mismo.5 En e1 ejeriiplo 5. Entonces podremos establecer el elgoritnto de análisis sinkíctico L.R(O).294 Ejemplo 5. Figura 5 1 El NFA de elementos LR(0) para la gramática del ejemplo 5.2.~s graniiiicas con rectirsión por la izquierda inmediata.1. Fig~iraí 2 El #FA de elementos LR(0) para la gramat~ca del ejemplo 5.6 Para completar la descripción del uso de elementos en el seguimiento del estado d aniiisis sintáctico.R(O) asociados con la gruinitica del eJe plo 5. lo tanto. como se niuestra eil la figura 5.3 enumeramos los ocho elementos LR(0) de la gratnitica del ejemplo 5. .2. el NFA tiene ocho estados. El NFA de los elementos aparece en la figura 5. Kealirar-emos la consiruccicín de1 s~ihcoi?jurito para I«s dos ejernplos de NFA que ~rcühamosde dar.) .

S en ( S ) S.S y ( .E + n E 4 .( S ) S ) ). I cst. . Ejemplo 5. ~3.i<ium E y l i m i a el estado DFA ( E --. . . .~ltpiera estos eletncntos.S .S --+ . l . S ) S.S ~ -+ . Fir~dmcnte. 1. Existe tina iransici~iri del junto de los tres elerrientos (E' .5 --* ( . 5 -+ (1. 1. i:r~rrcspot~tlicntcla tr.Authatas fin~torde elementos LR(0) y analiris r~ntactlcalR(0) 205 [S' . S ) . pero también hay uria transici6ri en E del clecietiretito E' .hpor 111 I : t i ~ í t ~ S~rr~iia c t ~ i i ~ t mu111 w l i t cIci~1ei110 d DFA. I s i e e~titdtr ciótr al est:tdo ( S ( S ) S.S-+ . F i ~ r a ~ i ~ cS i t i t i s i c t i I n 1 c t ~ 1 i F. E . - Cotisiclerc el NIy.S h:ista S' + S .S 4 (. por S610 hay uitn trarisiciii~i e t c i:<lt!jriiito..~I~uII~I r x ~ s i c i6:. c11rmptmtliente 3 la transici6n en el símbolo n de E 4 n ü E -+ n. rn E. Pricstit que no se tic: iicri tr. . i De este modo. .insiciones c tiel elenicilto E + n.n I:IIIIIW~O ticric to . 1 1 ~ i t 1r E + n. .)tic. eri S.+ ( S ) S .1 ----c .. t n / . existe una ir.(S) S -~-( . del Puesto : de q ~ t e hay transicioiies 6 ileidc c~t. . este eletiieirto es \ t i propia cerr..5 S . I en tltrntle nrilncrnmos l o i est:itlos corrio i-ekrerrcia (el estiido O se asigna t f i ~ d i c i o i ~ ~ t l r i ~ c r ~ t e al estnclo inicial). n. .S ---. ..E .S. S ) S. I'ainhiéii existe riwt rl-ünsicitiri en ( del cstaelo itrici.iI estado :irites ciriistrriiih / S -4 (.S. F i g ~ i i a5. El est. S --.tl al cstnclo 1>F:\ ( ..S ) .itht LWA ( S -+ (.i ccrr:itlur. .S -e ( . ..ti~siciorieitleitlc este estado.3 DfA de canjuntar de &mentas correrpandientes al NfA de la figura 5.S. N o Ira! tr. / - - -- . tiene uiia trarisitic tmrisiciorres hacía \itiiisrilo e11 ( y :t { 5 -+ ( S ) S / en .S --3 j y IIII.k .~ traiisicitiii en S . existe tina transici6n en E (Ir1estado inicial del DFA 3 IU ~erracliir~t cotij~ittto{E' --t E.. S ) . 1 : . de nrodo i p e las únicas ~ tr:tir~icitr~ics ser C:I~CLI~:I<~:~S t o d i ~ v í :vienen del conjunto {E? . / en ) ... ( ~ s ). E +.E . este conjunto es sil propia no : cerrndurit t y forina un estado DF1\ completo.3. t.Sf -+ S . + n / .irisición corrcspondierite clcl estaJo inicial . ( S ) S.~tisici~iri eleir~ciitir --de a dcl E I:' + n id clenicnto I: --a 1 +../ -.n/. Existe otra tr:msicióri de1 c m d o inicial. ~ . e\te rílti~rio estado tictre tri~nsicioiics ( .S --+ .S .E.iiio { S . - + --3 . E l »FA cornplcto se propi~rciona la figura 5. E l citado inicial del DFA asociado r c cimpone tic1 coii. I'uesto que existe m a trnrihición t l c d e S' -+ .t c: de { . a iiingun otro clcriiento).s.S ) S..S.iI estado DFA ( . iuento E -+ .2. El ~ ' l e ~ r i e nE ---.8 S . ( S ) S. n. .. . un t o u11 cn . y ' ~ ~ . 1 -9 (sin que haya traiisicionci c: desde S' -.E al t:leinetito -+ E . E 4 E. .\ tic la figura 5.E + n al eiertienkr E .n 2011 el ií1nh010 +.

mantendremos los símbolos en la pila por conveniencia y claritlad. todos los elementos cerradura son elementos iniciales. mientras que E .3. . stílo es necesario especificar los elementos de núcleo para caracterizar co pletamente el DFA de conjuntos de elementos. mientras que los últimos se conocen como elementos de núcleo. E' -+ . se el presenta una simplificación adicional. De acue . En el esta O de la figura 5. se calcula direck~mente DFA de los conjuntos de elementos. En realidad. los generadores de analizadores sintácticos como Yacc siempre calctilan el DFA directamente desde la gramática. do con la definición de las transiciones o de los NFA de elementos. los estados mismos contienen toda la información acerca tle los símbolos. en ocasiones se distingit los elementos que se agregan a un estado durante la etapa de la cerradura E de los que originan como objetivo de transiciones no E . n son elementos de cerradura.il O en la pila. dada gramática. los elementos de núcleo determinan unívocamente el estado y sus trünsicione De este modo. E es un elemento de núcleo (el único).E . E --. S ) S eleinento de núcleo.CAP. La importancia de distinguir entre elementos de núcleo y de cerradura es que. S -+ ( . Por consiguiente. " E'-+ E . Los primeros se denominan elementos cerradura. así que podríamos prescindir del todo de los símbolos y mantener sólo los números de estado en la pila de análisis sintácrico. lo que haremos también en lo que resta del c. .tpítulo. Sin embargo.n n o \ O. informar sólo de los elementos de núcleo (e cs cierto para Yacc. debemos modificar la pila de análisis sintáctico para no sólo almacenar sírrtbolos sino también números de estado. de riranera que al principio del aniílisis la situacicin se pucde representar como Pila de análisis sintáctico Entrada ~'<l<l<~lf~lf~~ftr~l<l<l '3 3O . De hecho. + T Al construir el DFA de los conjuntos de elementos LR(O).R(O). por ejemplo).4.3 E algoritmo de análisis sintactico LR(0) l Ahora estamos listos para establecer el algoritmo de análisis sintáctico I.. En el estido 2 de la Figura 5. Si en vez de calcular primero e1 NF.4 de los elementos y posteriormente aplicar la construccicin de subconjuntos.E . Para comenzar un anilisis sintáctico insertamos e1 n~itr~ad«r inferior $ y el estado inici.. mieniras que S -+ ( S ) S y S .E+n E ---+ E + n . Los generadores de analizatiores sintáctic que construyen el DFA pueden.2 -. 5 1 A N A L ~ I SSINTÁCTICO ASCENDENTE Fiqura 5. 52. Haremos esto mediante la inserción del nuevo número de estado en la pila del análisis sintactico después de cada inserción de un símbolo. por lo tanto. es fácil determinar de inmediato a partir de un conjunto de elementos cuáles son las transiciones E y a cuáles elementos iniciales apuntan. . Como el algo ritmo depende del seguimiento del estado actual en el DFA de conjuntos de elementos. y E-.son elementos de cerradura.4 El DfA de tonluntos de elementos correspondientes al NfA de la ligura 5.

se declara un emtr.l (2 o H -+ (3). donde S es el estado inicial. entonces el nuevo estado . (Advierta que esto corresponde a seguir la t r m contiene el elemento H sici<in en A en e1 DFA.Nuevamen-te. Si el estado s conticite cualquier elemento co~npleto elc~nento la forrna A -9 (un de (Y. f3. Si este X token es X. s i un estado de esta clase contiene otro elemcnto comal r plcto I j -+ P.iyairicts . ya sea que se realice una .Iií0) s i y slilo s i cada estado cs un c.S de la f t ~ r i n a se acaba de describir. Si el estado s contiene ccialquier elemento de la forma A - Se dice qrie tina gramática es tina gramática LR(0) s i las ritglas anteriores son no arnbigcias. De iiianern iiriiil.ir:ii~iic~tto"i un cst:~tlode 1-educcicín q ~ i c o . mediaiite la constrticción del DFA. Definición El algoritmo de análisis sintáctico LR(0).p. X p (dortde X es un tcrmiiial). retroceda en el D F A hacia el estado en el cual comenzó la cotistrlicción de u (&e debe ser el estado descubierto por la e l i m i n x i ó n de a ) .(YX. Esto significa que si iin estado contiene un elemento completo A 4 u . De este ir~ocio. 2. Elirniiie la cadena (Y y todos sus estados correspondientes de la pila de análisis siritáctico (la cadena tu debe estar en la parte stiperior de la pila.. Esta situxiiiti i c coimce corno conflicto (le reduccihn-reduccihn. es equivalente a la aceptación. E n efecto. surge una ni~ibipiictlütl decidir qué produccicíri r i t i l i ~ a p r a la rediiccicíri i. Entonces las acciones se definen como sigue: -+ a.:~~!iticiiestilo iin elcinctito ctxnpiecii. puesto que estanios insertando A ert la pila. entonces surge una amhigüed.4 y n sea el tor~ k r n sigiiierite en la entrada). de l cuerdo con la nianeru en qiic esté construido el DFA). E n todos los otros casos e l nuevo estado se calcula como sigue. iio puede P contener otros. e inserte (conlo el nuevo estadoj el csta<lo cpe LYA. lo cual es en realidad faronable.X P.id. .sindo (le ilcsplazartiicnto ! rin cst:1110 i p e contieiie síilo c l c i i ~ e i ~ k ~ s de "tlcspl.t cs I.A p. Sea s el estado actual (en la parte superior de la pila de análisis sintictico).tr. P.i1 estado 2 (esto ocitrrirá en realidad cuando el D F A esté corno en la f i g ~ i 5. que se insertará en la pila es el estado que contiene el elemento A . . inserte A en la pila. que es siempre el estado que aparece en la parte superior de la pila. y el estiido s contiene el elemento A -+ u.) l.u .. accicín 1 ) o una accion 2 ) . tliarite l a regla S' 4 S.una gr. - .Autómatas hitos de elementos LR(0) y analiris rintáctico LR(0) 207 Siipmganios ahora que el siguiente paso es tlesplazar un tokcii n a la pila y v. esle estrtdo dehe contener riri elernerito de la forma B -+ a. Si este que token no es X para algún eleniento en el estado . donde X es un terminal. si un estado así también contiene un clcinento "ctesptazado" A i r . Una redoccióii ine). entonces la acción es desplazar el token de entriida actnal a la pila. Esta situación se conoce como conflicto en r e d u c c i h por desplazamiento. siempre que la entrada esté vacía. Esto se representa de la manera siguieiite: Pila de análisis sintáctico Entrada E l algoritnio de análisis sintáctico LK(0) elige nnit acción basado e11el estado D F A actual. entonces la acción es re(1ucir mediante l a regla A . De la misma Inanera. y a un error s i no es así.tiiiátic.

El análisis e niienza en el estado 0. preseiitarnos el ejemplo siguiente de una gramática es L.4 el estado 1 contiene un cantlicto de red ción por despla~aniiento. 1 +~ . Este estado también es un esta& de desp mniento.CAP. Aquí se presenta una reducción ntediarite la regla/%-+ ( A ) . ) se desplaza a la pila y se inserta el estado 5.de la figura 5. Como ahora la entrada está vacía. El análisis sintáctico está ahora en el estado 3. O ( f <' ( * \ A \.3 los estados O. 3 y los símbolos A. ( de la pila.9 Considere la gramática A-+(A) / a La gramática aumentada tiene el DFA de los conjuntos de elementos que se muestran e figura 5.5 El DFA de conjuntos de elementos para el ejemplo 5.--/.3. que es LK(0).(A) A --+ . 4. Ot reducción mediante A -+ ( A ) elimina la cadena ( 3 A 4 ) 5 (hacia atrás) de la pila. y la transición con ( regresa con estado 3. de modo que el siguiente ( se desplaza a la pila. . De nuevo. de modo que ) se desplazü a la pila y la transición tt-m ) lleva el análisis sintáctico (11 estado 5.' . .a . Ejemplo 5. Ahora se inserta A y se lleva a cabo la transición de A del e tado O al estado l . t'iglira 5. mientras que en el DFA.5. El estado 4 es un estado de ilesplazarniento. y nuevamente A y el esta 4 se insertan en la pila.(<1) A ----- .9 -t .1 -+ L ( a.el cual es un estado de desplazatniento. Para ver cómo funciona el algoritmo de análisis sintictico LR consideremos la cadena ( ( a )) . Un aniílisis sinthctico de esta cadena de :tcuerdo con el goritnio de análisis sintáctico LR(O) se da mediante los pasos de la tabla 5.J ! c . 2 y 4 contie todos conflictos de reducción por desplazamiento para el algoritnto de análisis sintáct LR[()).Esto no es de sorprender. .A A . S 1 AN~LISIS~INTACTICO ASCENDENTE Advertinios que ninguna de las dos gramáticas que hemos estado utilizando co nuestros ejemplos son gramáticas LR(0). y la transició~i a del tado 3 se va a1 estado 2. Sin embargo. Luego. Entonces se insertaA en la pila y se lleva a cabo la transició de A del estado 3 al estado 4. al extraer los estados 5. conlo el DFA indica una transición del estado O al estad en el símbolo (. Al desplazar de nueva cuenta se pone a a en la pila. de modo que el primer to ( se desplaza a la pila.a . El estado 1 es el estado de aceptación. lo deja el análisis en el estado O. Aquí e1 estado 2 y el sín~bolo se extraen de la pila y en a proceso se regresa al estado 3. En la figura 5. puesto que casi todas las gramáti "reales" no son LR(O).A) -. Ahora nos encontramos en el paso 4 de la tabla 5. acepta el algoritnio de análisis.A'--. y alcanzamo el primer estado de reducción.R(O). el estado 3 se inserta en la pila.3.

En el caso de un estado de despl:izarniei~toel sírribolo por ser desplazxlo determina el cstiido sigtiicriL (ti~cclinritc DP\).9..ira que verificliie que 6sta conduce a . sis sintáctico L K ( 0 ) son estados "de rleiplazamiento" 0 estados "de rcducci6n".0. í dcspla~:iiiiient~~ redticcicin . rttiiiqrie el analizador sititáctico actúa conio si estrivict-an tlespl:ir. Tibln 5. cuyos eiitradai son los e el niievos cstndos por intrtidueirse cii un despl. y tradicionnlriete estas coliiiiiiias se enlist:in en kina parte sepraila de la tabla cleiit~rnitioila sección Ir a (Goto).e reserva un:i coltiriioa piira indicar esto en cada estado.i redricciíin. \ ' -+ A reduccicin cIcsl~luLnniicric<> s p 1 1 1 i 1 i-c<liicciijn TI-+(.as transiciones tle 110 terminales (que se insertan rlirfiiote una reducción) representan un caso especial. En el caso de un estado "de retlucción" se titi-. Se alienta al lector p.itfo por tabla. U n qjemplo de tina tabla tle análisis siiitictico de esta naturaleza es la tilhl:t 5. y así tlclie haber ritra colurrina para cada token.tmniento de ese tokeri.rcciories espccific. y las colurr~iias etiqrieten coriio sigue. puesto qiic no se ven realniente rri la entrada.icci«ires de ariilisis sintáctico para ese ejernplo corno I:is dudas en la tabla 5. eri un estado "(le ctesplarnriiierito" tairibibn se riccesila t w e r una columna p:m cada no terminal.11(0) se convierta eri riti rnitotlo de iinálisir siritáctico control. De este r n d o .E l DFA de los conjuntos tic cleiiientos y las . l i l a otra colt~rnna para iridicnr la regla grainatical que se usará en I.\) I 4 1 5 .9 Estado Acción Regla Entrada Goto A - O I 7 - 3 -1 . tina org:inilacióri típica es qiie los renglones de la tabla m i r t etiqcietidos con los se estados del DFA.itlns por el cilgoritriio de :inálisis sintáctico L.R(O) se pueden coiiibinar en iina t:ihla de máiisis sintáctico. Pircsto que los estados del análi. !. que es la tabla para la gramática del ejeniplo 5.ttlos. .1.4 Tabla de análisis rintactito para la gramática del ejemplo 1. 'le tiiancra que el atiálisis sintáctico 1.

La primera consulta el token entrada cintes de un tlesplazaniiento para asegurarse de que existe una transición 11 apropiada. Inserte r l en la pila.. una eraniática es SLR(I) si y sólo si. incrementa de manera im portante la potencia del análisis sintáctico t. 3. entonces la acción es de plazar el Loken de entrada actual a la pila. B-ta'4. para decidir si debería efectuarse una reducción.. Una rediicción mediante la regla S' -+ S.p.entonces la acción es reducir median la regla A -t y. Pospondremos este análisis hasta una sección posterior 53 ANÁLISIS SINTÁCTICO SLR(1) 5.S. donde X es u . Si el siguiente token de entrada es de natur&m tal que no se aplique ninguno de lo dos casos anteriores' se declara un error. y el token siguiente en la e . y el nuevo estado que se insertará en la pil es el estado que contiene el elemento A -+ aX p.R( 1 ) simple. el nuevo estado se calcula como sigue. Si el estado s contiene cualquier elernento de la forma A + a X P.31 El algoritmo de análisis sintáctico SLR(1) El análisis sintáctico L.3.A P.R(íff como se construyeron en la sección anterior. Sea s el estado actual (en la parte superior de pila de análisis sintáctico). Lo hace de dos maneras. condiciones: . La segunda utiliza el conjunto Siguiente de un no terminal. Sin embargo. Sorprende que este si ple uso de la búsqueda hacia úelante sea tan poderoso que permita analizar casi toda con truccitín de un lenguaje que se presenta comúnmente. 5 1 ANALISIS SINTÁCTICO ASCENDENTE iiecesitaremos especificar con precisión qu6 acción torna el analizador sintictico para ca una de estas entrados en blanco. De la misma manera. como se constru en la seccitín 4. para cualquier estado .sis sintáctico SLR(1). 2 Si el estado s contiene el elemento completo A + y. donde S es el estado in cid. terminal. Entonces las acciones se definen como sigue: 1.R(O) al utilizar el token siguiente en la cade de entrada para dirigir sus acciones. dena de entrada está en una Siguiente(A). Elimine cadena a y todos sus estados correspondientes de la pila de análisis sintáctico. retroceda en el DFA hacia el estado en el cual comenzó la cons trucción de a.Por construcción. e inserte el estado que contiene el elemen . se satisfacen las siguientes dos ' . este estado debe contener un elemento de la fo nia B -t y. o SLR(1) utiliza el DFA (le conjuntos de element I.CAP. En particular. Decimos que tina gramática es una gramática SLR(1) si la aplicación de las auteriores reglas de análisis sintáctico SLR( l j no producen una ainbigiiedad. y X es el siguiente token en la cadena de entrada.-es equivalente a la aceptación: esto ocurrirá sólo si el siguiente token de entr ! da es $ En todos los otros casos. El algoritmo de análi.

se iiidica una reducción niediante la pr«ducción E -+ n. Deniostraremos la construcción de la tabla (le análisis sintáctico SI. en el estado I en la entrada + se indica un desplaza~niento. + ) La tabla de análisis sintictico SLR(1) se proporciona en la tabla 5. Estas dos coiidiciones son seriiejantes en esencia a las dos condiciones para el análisis sintáctico LL(1) establecidas en el capítulo anterior.8. Para cu.os piisos n tlcl niiilisis sintáctico se proporcioii.5 Tabla de análisis sintáctico SLR(I) para el ejemplo 5.Y con X en Siguiente(B). Las diferencias son las siguientes.tlquier elcmento A - Una violación de Iii primera de estas condiciones representa un conflicto de reducción por despIazamicnt«.R(I). excepto que.t 5. las decisiones sobre c u i l regla gramatical utilizar se pueden posponer hasta el úttinio rnoincnto posible.[<( 1) junto con nuestro primer ejemplo de anilisis S[.iccicin "S?".4. . De este junto con una transición rnoilo. Corno establecirnos. Ejemplo 5. 2. tio hay elcrnciito co~ripleto B -+ y.6. y R -t (3. l. cuyo DFA de conjuntos de eleiiieiitos esti dado en la figura 5. pero es SLR(1.int:ictico ir~tlica . al estado 3.10 Estado n Entrada Ir a $ + s3 r (E-+ n) E I o 1 - s2 aceptar r ( E + n) rtE-K+n) I 3 4 s4 r(E-tE+n) Fi~ulizanios este ejcinplo con un m d i s i s sintáctico de la c:~der~a + n + n. E).i de . csta gramática rlo es I.i(lo y Iii t.~irdisis . de Una tabla de análisis para el análisis sintáctico SLRi 1 j türnbi6n puede constn~irw inariera siniilar a la del análisis sintáctico LR(0) descrita en la sección anterior. Como un estado puede tener tanto desplazamientos corno reducciones en tin analirador sintáctico SILR(1) (dependiendo de la búsqueda hacia tielanre). Para cualesquiera dos elcnientos conipletos A -+ (x.~tloO cn el token ile c~itr. Eso también provoca que las colurnnas de accióti y reglas sean innecesarias. lo que da cotrio resultado un aiidirador sintáctico m i s poderoso.tt)l. y las selecciones de regla gramatical también deben scr colocadas en entradas tnllrcadas "reducción". En la tabla se indica un desplazamiento mediante la letra s en la entrada.10 Consideremos la gramática del ejemplo 5. Tabla 5. Los conjuntos Siguiente para los no terminales son Siguiente(E') = ($1 y Sigtiiente(LJ) = ($. en . y una reducción mediarite la letra r.También escribimos la acción "aceptar" en el estado l en la eritrada $ en lugar de r (E' --. como con todos los métodos cle análisis sintáctico de redticcicín por desplaramiento.).5. El paso I de esta 1igur:i coniienza en n. en S .iri cn la i. Puesto que el símbolo $ de fin de entrada también puede ser uno de búsqueda hacia delante Legal.. En el estado 2 en la eiitratlii +.(Y. se dehe crear una nuevd columna para este símbolo en la seccióri de entrada.R(O). la cl cst. X B cn s con un terminal Y. f.thl. Sigtiie~ite(A) n Siguiente(B) cs vacía. cada ingreso en la sección de entrada tlebe tener ahora una etiqueta de "<lesplararriiento" o "reducción". Una violación de la segun<lade estas coiidiciories representa un ron- tlicto de reducción-reducción. por otra pirte.

Tabla 5 6 Atc~ones de analis~r rintáctico para el ejemplo 5. y se debe evitar si es posible. Esta reducción se cumple al extraer de la pila la cadena E + sus estados asociados. En el paso 3 el analiza está en el estado 1 con el token de entrada +. Un cálculo directo produce Siguiente(S8)= { $ J y guiente(S) = {$. S I ANALISIS SINTACTICO AICENDENTE es decir. Se inserta el S ' tioto E y se toma la acción Ir a para E del estado O al estado l . el . Observe cómo la pi continúa creciendo hasta las reducciones findes. y la tabla indica un desplazamiento y una t sición al estado 3.E + n . ) J . En el estado 3 en la entrada n la tabla también indica un desplazarni y una transición al estado 4.1 1 Estado ' Entrada ( ) Ir a F 5 . Indicatnos esto en la tabla 5.1 l Considere la gramática de los paéntesis balanceados. y la tabla indica una reducción mediante la regla E -r n. Advierta cómo los estados no-LR(O:) 0 . Los pasos restantes en el análisis son semejantes. dcsplazainiento 1 desplarairiiento J sedncciiín E -+ E + n aceptar 5 6 7 8 9 - Ejemplo 5. exponiendo al estado O. cuyo DFA de elementos LK(0) est dado en la figura 5. 2 y 4 tienen tanto desplazamientos como reducci nes mediante la producción E S -+ E.6 con frase "desplüzar 2".CAP. En este L so el estado 2 y el token n son extraídos de la pila.3 (página 105). En el paso 2 de la figura. La tabla 5.10 Pila de análisis sint&ctico I 2 3 -1 Entrada Accidn desplazamiento 2 reduccih E + n <lesplazamiento3 desplazatniento 4 reducción E . desplazar el token a la pila e ir al estado 2. En el estado 4 sobre la entrada + la tabla indica una reducci mediante la regla E -+ E + n. insertando E y Ilevaiido Ir a estado l . rabli 5 1 [&la de analsir rtntáa~co ilR(1) para el ejemplo 5. la recursión por la derecha puede ocasionar la sobrecarga de la pila.8 proporciona los pasos que el algoritmo aiiálisis sintáctico SLR(1) lleva a cabo para analizar la cadena ( ) ( ).tnalizador sintáctico está en el estado 2 c el tokcn de entrada +. La tabla de ariálisis sintáctico SLR(1) se proporcionan en la tabla 5. exponiendo nuevamente el estado O. Esto es caractenstico de los analizadores sin tácticos ascendentes en presencia de reglas recursivas por la derecha tales como S -+ ( S De esta manera.

tdel. el capítulo 3. Ejemplo 5. Para ver esto en LIIIaiialitador SL.2 Reglas de eliminación de ambigüedad para conflictos de análisis sintactico 1.0s conflictos de anilisis en el análisis sintáctico SLK(li.+ / ] otro I . rle manera autotnitica los contlictos de reducción por desplaraniiento prefiriendo el despla~amiento a la reducción. 1.Análisis iintactico SLR( 1 ) Pila de analiris sintactico Entrsda Accion <icspl:irarnicnto 2 redtic~i6n i S i .tnte se proporcionan ejemplos de este tipo (le conflictos.4. Incluso eliniinainos tiel todo la cxprcsi6n de prueba y escribimos la gmmitica como sigue ítotlavía con la ambigiictlad del clse): S .3. pueden ser de dos clases: conflictos de reducción por desplazamiento y conflictos de reducción-reducción. por lo tanto. debe haber un conflicto de análisis etl alguna parte en cualquier ctlgoritmo de análisis sintáctico.a mayoría de los analizadores sintácticos de reducción por des&a~miento resuelven.12 Considere la gramilica de sentencias if siinplificrdas utilizada eii los capítulos anteriores (véase. como se irtuespara dejar que la ambigüedad permanezca en tra en el siguiente ejemplo. que consiste en preferir siempre el desplazantiento en ver de la reducciíin.iriálisis sintdctico de rcducciíin por desplazamiento. dcspic~ratniento 4 desplatamiento 2 reducción S -i rr dcsplararniento 4 reJucci6n S -+ rr rediicci6n S -+ (.t i f S / if . lo qiie hace mis nimejable la coiistriicci6ri dcl DFA de conjuntos de clenleiitos.R(I ) rirnplific. En el caso tle conflictos de reducción por desplazamiento existe una regla de eliminación de artibigiiedad riatural.) Preferir el desplazxniento a la reditcciún en u11conflicto de redticción por desplazamiento incorpora :iutomática~neritela regla de anidación más próxima para la ambigüedad del else en sentencias if. S .c«ntlictos con frecuencia (pero no siernpre) indican un error en el diseño de Iü gramática. El caso de los conflictos de reducci6n-reducción es mis difícil: tales . por ejemplo. sección 3.rmos aún n i k In grarnática. (Mis . Esto es una r a ~ ó n una gramática de lenguaje de programación. como con todos los métodos de . S e l a e .Y) S rrtiiiccidn S -t (S)S aceptar 5.3): Puesto que ésta es una gramática ambigua.

- La tabla de anzílisis sintáctico SLR(1) que resulta de esta graniitica se muestra en la tabla 5. En esa tabla utilizamos un esquema de numeración para las selecciones de regla gramatical en las acciones dé reducción en lugar de escribir las reglas misinas. De este modo. i f S 4 S .t otro ( 3 ) 1 4 if S (1.otro I 4 . indica que va a . else) Ahora podemos ver el conflicto de análisis provocado por el prohlenia del else amhiguo. donde el elemento'completo 1 .S S . i f S 1 -if.. una regla de el - ción sohre el desplazamiento. el elemento 1 if S else S indica que va a ocurrir un desplazamiento del toke .S e l s e S + 1 -----+ i f S e l s e .12 S ---. el else ambiguo resultará en un conflicto de reducción desplearniento en la tabla de análisis sintáctico SLR(1).) Fiqura 5.S -- . [. no habna rnanera de introducir los estados 6 o 7 en el DF lo que resultaría en errores espurios de análisis sintáctico.6.if.if S. Éstos son Siguiente(S) = Siguiente(0 = {S.9. Evidenteinente.S 1 .otro 1-.6 DFA de conjuntos de elementar ¡&(O) para el ejemplo 5.4. S I ANALISISI NTACTICO ASCENDENTE El DFA de conjuntos de elementos se muestra en la figura 5.a numeración es (1) S-+¡ (2) S . presenta en el estado 5 del DFA. if S e l s e S .otro 1 .CAP.) ¡ if S else . Para constrtiir las accio del tinzílisis sintictito SLR(1) necesitaruos los conjuntos Siguiente para S e I. entrada en else.if S 1 ---.

lo cltie indica un desplazainieuto y una traiisicicin al estado 6. Ejemplo 5. esto nos coiiducirá a estudiar el análisis sintáctico LAI.13 Considere las siguientes reglas gramaticales para sentencias. en el estado 5 la entrada bajo el encabeiatlo de entrada else es s6. En la tabla 5.if S .R( 1) y LR(1) general coi1 más poder. existen algunas situaciones en las que el análisis sintúctico SLR(1) no es lo suficientetnrnte pwleroso. mientras que la entrada bajo el encabezado de entrada $ cs r3. puesto que tina reducción mediante esta regla corresponde a la aceptacicín y se escribe como "acepta$' en L tabla.Advierta que no es necesario numerar la produccicín de aurnento S' -t S. a Advertimos al lector que los núrneros de producción utilizados en entradas de reducciitn son ficiles de confundir con los números de estxlo empleados en las entradas para dcsplazarniento y direccionaniiento (Ir a). extraídas y sirnplific:idas de Piscal (una situación seinejante ocurre en C): sent -+ llamad-sent / sent-trsignac Ilrrrnud-sent 4 i d e n t i f i c a d o r st~nt-asignric w r := cxp + r i r [ e . (1) para el ejemplo 5. 1 / i d e n t i fi c a d o r <'. Por desgracia.9 también elimin~nws cooflicto de rediicciiín por desplazamiento en la el tabla en favor del desplazamiento. en la labla 5. Por ejcinplo. E l ejemplo siguiente es una situación típica en la que falla el análisis sintáctico SLK(1). Sortibreamos la entrada en la tabla para mostrar dónde se habría presentado el conflicto.Y!> --t wr 1 número . ) reducción inediante el número de producción 3 (es decir. lo que iiidica una .12 - Estado Entrada - Ir a $ if 0 I 2 3 4 5 6 7 S& else otro ~3 S 1 1 1 1. 1.l r2 s4 y6 aceptar rI r2 $3 5 ri 2 s4 s3 r4 r4 7 2 § 533 Límites de la potencia del análisis sintáctico SLR(1) E l análisis sintáctico SLR(1) es una extensión simple y efectiva del an6lisis LR(0) que cs tan potente que puede controlar casi cualq~iier estructura prictica de lenguaje.9.

Efectivamente.S S-. e hicimos rn ples las opciones de sentencias. .id Este estado tiene una transición por despl~tzamiento sobre i d al estado V-+ i d . Ahora. i d S . y el nuevo estado que se insertará en la pila es el estado que contiene el elemento A -t a X P. entonces la itcción es reducir mediante . (Éste es un conflicto de reduccicín-reducción. la regia A . y rv E Sig~iente~íil) los son siguientes k tokens en la cadena de entrada. puesto que una variable nunca puede presentarse al final una sentencia hasta después que el token := se haya visto y desplazado. S 1 ANALISIS SINTACTICO ASCENDENTE :<:.S .I k. SiguienteíS) = ($1 y Siguiente(V) = { :=.iirmcnta cuponrncinliiiente su L. . :- sentencia es una asignación o una llamada.R(k) es rnás poderoso que el anilisis SLK(I) cuando k > 1: pe1. un analizador sintáctico SLRlk) utiliza las sigui dos reglas: 1. cl algoritmo de análisis sintáctico SLR(I una reducción en este estado tanto por la regla S -+ i d como por la regla V -+ i d h símbolo de entrada $..3s c«nstri~ccioncc lenguaje tipic. ya que la i:iblii de anilisis siritictico .+ V := E .(Y. . . entonces la acción es desplazar el token de eutrxia actual a la pila. V := E V-t . la reducción mediante V-+ i d nuncci debería hacerse en estado cuando la entrada es $.0con un costo sustancial en cornpkjidad. 2. Simplificamos esta situación para la grarná siguiente.. porque un E puede ser un V ) . sin modificar la situaciún básica: S-+idIV:=E V-+ i d E-. (donde .vln Para mostrar cómo esta gramática produce un conilicto de análisis en el análisis sintác SLR( 1 ) considere el estado inicial del DFA de los conjuntos de elementos: S'-+..a .4 Gramáticas SLR(k) Como con otros algoritmos de análisis. Utilizando los conjuntos Primerok y Siguientek c se definieron en el capítulo anterior..ts y e no son de . Si el estado s contiene el elemento completo A -+ a. el algoritmo de análisis sintáctico SLR(1) se p extender al análisis sintáctico SLR(k) donde las acciones de análisis están basadas en k sírnbolos de búsqueda hacia delante.) Este conflicto análisis sintáctico es en realidad un problema "falso" ocasionado por la debilidad del todo SLR(1).~* .Y es un token) y Xw E Primerok(XP) son los siguientes tokens k en la cadena de entrada. El análisis htáctico SL. 5.irnaño sespecto . donde eliminamos las opciones alternativas para una variable. ...+ .A< CAP.. De este modo.r contiene un elemento de la forma A -+ a.X p.. . Si el estado . En las dos secciones siguientes mostraremos cómo se puede eliminar este problema de análisis mediante el uso de métodos de análisis sintácticos más poderosos.3. 1.:. $ ) f : = debido a la regla V .

.

9.S (donde S . en la Lerminología del análisis anterior).B y.B y. ti]. Sólo las transiciones E "crean" nuevas búsquedas hacia delante de la manera si guiente. entonces Primerofya) C SiguientefB).A. El estado inicial (estado 0) es entonces e l conjunto de estos tres elementos: . es el símbolo inicial original). s i esto sucede obtenemos el caso especial de una transición e de [A -+ a. $1 (es decir.) Advierta tanibién que la búsqueda hacia delante original a :tparece como una de las h sólo s i y puede derivar la cadena vacía. $1 y [A + . Dado un elernento L R ( l j [A a. y las h en los elementos [N -+ . existen transiciones E para elementos 1B -+ .CAP.14 Considere la gramática del ejemplo 5. Corno en el caso LR(O). (Un analizador SLR(I) tonta esencialmente las búsquedas hacia delante b del con. En efecto. existen transiciones F para 10s elerne~itos[A -+ ( A ).-< :. fjemplo 5. . $1. Efectivamente.junto Siguiente completo. punto del análisis sintáctico podemos querer reconocer una pero .B. S 1 ANALISI$ SINT~CTICO ASCENDENTE Observe que cn este caso la misma a de búsqueda hacia delante aparece en ambos elem tos.. - Advierta có~no estas transiciones E se mantienen al tanto del contexto en e l cual se necesi ta reconocer la estructura B. el elemento 1 -+ a. y 4 . Definición Definición de transiciones L R ( f ) (parte 2). La cerradura s de este elemento es el estado inicial del DFA. esto nos dice que comenzar i m reconociendo una cadena derivable de S.g .& h] para cada produccicín A -+ p y ptrm cutfu tokeiz b en Primero(yu). donde el signo monetario $ representa e l marcador de terminación (y es el único símbolo en Sigiiiente(S1)). Gran parte de las veces (especialmente en situaciones prácticas). La potencia del método I. s i n se construye para estar en Siguiente(A). y tales cadenas deben conienzar con un toke en Prirnero(yu). De este modo. Puesto que la cadena y sigue a B en la producción A -+ aNy. Ahora examinaremos varios ejemplos de la construcción del DFA de elementos LR(I).R(I) general reside en el hecho de que el conjunto Prirnero(yu) puede ser un subconjunto propio de Siguiente(@. de B es un no terminal. Comoit no está seguida'por ningún símbolo en este elemento (en la t&rninología del análisis anterior acerca tle transiciones. esto ocurriri sólo s i y es s misma. Entonces el símbolo inicial del NFA de elementos LR(1) se convierte en el elemento [S' -+ . Para completar la descripción de la construcción del DFA de los conjuntos de elementos LR(I) resta especificar el estado inicial. a ] . al a [B -+ $3. seguida por el símbolo $. .S. b] siempre estarán en Siguiente(3).xílo s i esta B está segu da por una cadena derivable de la cadena y(¡. hacemos esto aurnentando la gramitica con un nuevo sínibolo inicial S' y una nueva producción S' . estas transiciones no provocan la aparición de nuevas búsquedas hac delante. don. u1 dice que en est 4 . Pri~nero($) = ($1.a. la cadena y es E). -. Iniciamos la constnicción de este DFA de conjuntos de elementos LR(I) y atnnentando la gr~imática formando el elemento LR(I) inicial [A' + .f3. $1.

1' siguiente estatlo qtic l i c w ~III. ) / y L -+ . t2civicrtrt que cste estatio es el mismo que el estado 2. $1. A. se está recoiiocientlo en el lado derecho en el cor~tr..) A l regresar al estado 0. Finrilniente.rto los ~~<trhrfr. el conjunto Siguierite de In A del lado derecho es PrinreroO $) = ( ) 1 De cste tnodo. $1. ) 1. que es 1111 estado con un elemento: Aquí tanibién geneTtinibién existe una trrinsicih coii ( a la eerraJi~r:i e de [A + (. A ). Es decir. Esto e debe a que.\tado .a.) . por las i r i i s m s razones queen la coilstrucci6n del estado 2. tihtoviinos un ~ i u e v o tokeii de hiisquetla hacia ilel. . teiiemos una trmsición con el token a del estado 2 al estado 6: Ohserve de nueva cuenta que esto es casi l o misino que el esrado tres. esta c e r ~ t d u r a IO trivial. $1.a. ) 1 y [ A + .~ traiisici011es el cstiitlo $. $1. al Regresamos una vez m i s al ~ s r ~ i t0. en el de eleiiicrito [A-t (. existen transiciones e desA de este elemento n los elcirientcis [A 4 ( A ) . es u n estatlo lle un elemento: Ahora volveinos al esrado 2.vis. )l. excepto por la búsqeda hacia delante del primer elerrrerito. En efecto. Desde este estado existe uria transición cori /\ para Iri ce: rradtira + de [ A -+ ( A .( A ).ilgoritiiio de air5lisis sititáctico LR(1 ) generririí las x c i o ties de aceptaciúti.4 ). Por corisiguiente.iir\icih~ ciin el tokcn ) al c. rmios los elenientos tle cerradura [ A -t ( . conjui~to e s t i conipuests del elernenio [ A .Rnáiis~rs~ntácticoMLR(I) y LR(I) general 21' ( h t e cs c l estado tlcsdc d cual el . Debirlo a que existen transicioque : es nes c desde este elemento. ) 1. excepto por I:i húsqiieth Iiacia deliiirte. existe tariibién una trarisicih con el token ( para la cerradura del . que tictte utia ri. ubtcnenios el nuevo estado: .inte en esta situación. y e l nuevo estado DFA se compone de los elementos siguientes: . Y ya que esto es un elerrreiito corripleto.. tlonde enconvanitts una última tK~risici6ii eslatlo lo gerteradu por el clernerito [A -+ a.A ).

) ] y una transici6n al estado 6 ya construido con a.7.2 El algoritmo de análisis sintáctico LR(1) Antes de considerar ejemplos adicionales necesitamos cornl~letarla exposicih acerca del trndisis sintActico L R í I ) rnediantc cl rcestableciiriiento del :rlgoritmo de an5lisis sinticrico basado en la nueva construccih DFA.R(l) puede exceder el número de estados LR(0) por un factor de 10 situaciones complejas con niuchos tokens de búsqueda hacia delante.4. número de estados I. Al comparar esto con el DFA de los conjuntos (le clementos LR(0) para la misma gramática (véase la figura 5.da hacia (lelante en los eletnenros L. En realidad. excepto porque utiliza los tokens de húsqut. Esto no es inusual. S 1 ANALISIS IIHTÁCTICO ASCENDENTE Al regresar al estado 5 .. Finalmente.) .Rt 4 ) en lugar de los conjuntos Siguiente.de extenso. observamos q el DFA de elementos LR(1) es casi el dohle. El DF conipleto se muestra en la figura 5. ) 1. Esto es frícil de hacer. el DFA de elementos LR(1) para esta granática tiene diez estados. tenemos una transición de este estado hacia sí ntisrno con transiciiin con A al estado Estado 8: (.5. De este modo. puesto que sólo es un reestablecitnieiito del algoritino (le aiiálisis sintríctico SLR(I). existe una transición en ) del estado 8 a Estado 9: [A -+ ( A ) . un [ A+ ( A . :igura 51 El DFA de conjuntos 1 elementos M(!) para e ?I ejemplo 5.CAP.14 5. pagina 208). .

.

222 CAP. V : = E . := E. De este modo. a menos que sean ambiguas. S ] [ V . Ejemplo 5. S 1 ANALISIS S ~ N T ~ C T ~ C O ASCENDENTE sintactico LR(1) general la construcción explícita de la tabla de análisis sintáctico.. se pueden construir ejemplos de gram los ejercicios).id. En realidad. el tvken := aparece como la búsqueda hacia delante del elemento inicial V-. Tales ejemplos tienden a ser artificiales y por lo regular se pueden evitar e situaciones prácticas. Contiene los elementos [S .V . el estado inicial se compone de los siguientes elementos LR(I): E~tado O: [S' + S. también SLR(1)). puesto que S -+ .:-1. . . para ser SLR(I).S.id. .. el ejemplo que utilizamos p tanto.13 en forma simplificada es como se presenta a continuación: Construimos el DFA de conjuntos de elementos LR(L) parü esta gramática. :=1 . Este último eleniento también da origen ü1 elemento de cerradura 1V-t . pero sólo si está seguida por un token de asignacibn.$1 [S-.V := E indica que se puede reconocer una V . porque se puede obtener muy cilmente a partir del DFA.id.i . De hecho.id. un lenguaje de programación rara vez necesita el poder q proporciona el análisis sintáctico LR(1) general.$ [S.id.$1 y [S -+ . LR(I).16 La gramática del ejemplo 5. $1. El estado inicial es la cerradura del elemento [S' -+ . Naturalmente. $1. En resumen.

debido a que ya se vio un token de asignación.$¡ S [y [S * . %]y [ E -+ n. El DFA completo de los conjuntos de elementos LR(1) se muestra en la figura 5. E. Finura 5 8 El DFA de conjuntos de elementos LR(1) para el ejemplo S 16 -/!S'- . $1. esta cerradura incluye los elementos [ E -+ .":=E. el elemento [E-+ .$1. Éste era el estado que daba origen al conflicto del análisis sintáctico SLR(1). Los estddos y las transiciones restantes son fáciles de construir. $1 conduce al elemento de cerradura [V-+ .s.id.$i 0 . pero existe una transición del estado 3 con := a la cerradura del elemento [S-+ V :=. Finalmente. Ahora consideremos el estado 2. Aquí V no puede estar seguida por una asignación. $1 . Como E no tiene símbol«s siguiéndolo en este elemento. y las tlejarnos para que el lector las efectúe. Por consiguiente.V.Análisis rintáctico LALR(1) g LR(l) general A partir de este cstado existe una trmsición con S al estado de un elernento Estado I : [S' -+ S .. donde una Vestaba seguida por una asignación.V.) El estado completo es entonces .$1 y una t r m s c i h coi1 id al e m d o de dos elementos También existe una transicibn con V del estado O al estado de un elemento Estado 3: [S + V := E . Desde los estados I y 2 no hay transiciones. Los elementos LR(1) distinguen clüramente las dos reducciones mediante sus búsquedas hacia delante: reduccibn mediante S -+ id con $ y mediante V --t id con :=.$] [S' -s.$1 . .8. esta gramática es LR(1). (Compare esto con la situación en el estado 0. $1. ya que V tariipoco tiene símbolos siguiéndolo en este caso.id.

pero ionsidcr.Al. donde cs iiiís :ipropia<lo. 7 3 y 6. tos sobre las partes de búsqueda hacia delante de los elementos. y únicamente en la búsuuedü hacia delante de tal elemento: el estado 2 tiene el rner elemento [A + ( . 4 5. A ) . que forman la base parta la construcción del análisis sintáctico LALR(1). El algoritmo de antílisis sintáctico LALR(1) expresa el hecho de que tiene sentido i tificar todos esos estados y combinar sus búsquedas hacia delante.R(O). A ) . 4 y 8. . ponga que hay una transición con el símbolo X desde . mientras que difieren sólo en sus segundos componentes (los sím los de búsqueda hacia delante). Efectivamente. Por ejemplo. De este modo. el DFA de los elementos LR( 1) de la figura 5. en la figura 5. por ejemplo. mientras que el estad tiene el primer elemento [A -+ (. a los estados 2 y 5.4. en muchos casos.. S i ANALISIS IINTÁCTICO ASCEMDENTE 5. el análisis sintácti LALR( 1) retiene algunos de los beneficios del análisis LR(1).rl hasta un estado rl. cada eleniento LALR(1) en este DFA ten& un elemento LR(0) como \u pnmer componente y un conjunto de tokens de búsqueda hacia delante como su segundo componente. 'Tomados juntos. PRIMER PRINCIPIO DEL ANALISIS N T ~ C T ~LALR(1) S~ CO El núcleo de un estado del DFA de elementos LR(I) es un estado del DFA de elementos LR(0). mientras que el DFA correspondiente de los elementos LR(0) (figura 5. except cada estado se compone de elementos con conjuntos de búsqueda hacia delante. Estos dos estados se distinguen sólo en su pn elemento. siem debemos finalizar con un DFA que sea idéntico al DFA de los elementos LR(O').junto de primeros componentes en sus eleme (los elementos LR(O)). a / h / c ] tiene un conjunto de búsqueda hacia delante compuesto de los sírnbolus a. Entonc existe también una transición con X del estado s2 al estado t2. $1. con ) como búsqueda hacia delante. estos dos principios nos permiten construir el DFA de los element LALR(I). el t a ño del DFA de conjuntos de elementos LR(1) se debe en parte a la existencia de mucho krdos diferentes que tienen el mismo con. Así.3 Análisis sintactico LALR(1) El análisis sintzíctico LALR(1) es16 basado en la observaci<inde que. ditkren del otro sólo en los componentes de búsqueda hacia delante de sus element Considere.ALR( 1) [A -+ u$.CAP.X( 1 ). y los estados tl y tZ tie el niisnio núcleo. Al hacerlo así.irnos conveniente einplear esta representüciím para la coiistruccih l. Los DPh de elcrneritos LR(I). b y c.7 cada estado de los pares de estados 2 y 5 . De este modo. sus e por .5 En ejemplos posteriores denotaremos las búsquedas hacia dclante múltiples escribiendo una / entre ellas. ) 1.7 ti ne 10 estados. obtenemos los siguientes d hechos. el núcleo de un estado del DFA de elementos LR(1) es el conjunto de e rnentos LR(0) que se compone de los primeros coinponentes de todos los elenientos LR(1) el estado. mientras que mantiene el t inaño más pequeño del DFA de los elementos LR(0). SEGUNDO PRINCIPIO DEL ANALISIS SINTACTICO LALR(1) Dados dos estados sl y s2del DFA de elementos LR(I) que tengan el mismo núcleo. En el de elementos cornuletos estos conjuntos de búsqueda hacia delante son frecuentemente pequeños que los correspondientes conjuntosSiguiente.5) tie s6lo 6. de hecho. Puesto que la construcción del DFA de los elementos LR(1) utiliza transiciones son las mismas que en la construcción del DFA de los elementos I. tnmbiéii podrían utilirar conjuntos de sírnbolos de húsili~cúa ha& adelante para rcpresmtar clenientos niúltiplrs en el mismo estado cn qiiz comprten sus primeros coinponerircs. excepto . Formalmente. el cual se construye a partir del DFA de elementos LR(1) al identificar todos 1 estados que tienen el mismo núcleo y formar la unión de los símbolos de búsqueda hacia d lante p a a cada elemento LR(0). el elemento I. con $ como búsqueda hacia delante.

dada la cadena de entrada errónea a ) . 3 . Conservamos en esta figura la numcracicin de los estados L. en la figura 5.14. no obstante.i~ce<o ceso (le propagacih de híisqiredas f~acia . 4 y 7. 4 y 8. si una gramática es SLR(I). 8 y O.R( 1 ) general declarará el error inmediatamente después de un tlespla~ariiiento token a. Considere la gramática del +mplo 5. excepto por las búsquedas hacia delante. del L a combinación de estados LR(I) para forriiar el DFA de los elementos LALR( 1 ) resrielvc el prohleina (le las t.0. como en este ejemplo. Para la constnicci6n LALR(1) es posible crear contlictos de análisis que no existen en el análisis sintáctico LR(1) general.ALRI I ) directamente del D F h de elementos LK(O) a irav6s de u n psodelante. pero esto rara vez ocurre en la práctica. . la graniatica ya es LALRf l). La identificación de los estados 2 y 5. y los analizadores LALR(1) a menudo funcionan tan bien como los analizadores LR(1) generales en la eliminación de conflictos típicos que se presentan en el análisis sintáctico SLRí 1).Zuiiqiie no clcscsihircti~os tste pi. En realidad. si tina gramática es LR(l).8 es también el DFA de los elementos LALR( 1). Corno csperábainos. cuy<)DFR de elementos LK( I ) se dan en la figura 5. pueden hacerse algunas reducciones espurias antes que se declare un error. entonces la tabla de análisis sintáctico LALK(1) no puede tener ningún conflicto de reducción por desplazamiento. y agrccamos las bíisquedas hacia delmte de los estados 5. este DFA es idéntico al DFA de los elenie~itos LRíO) (figura 53). puede haber conflictos de reducción-reducción (véase los ejercicios). 7 y 9. Sin embargo. la gra~tiática SLR(1) del ejemplo 5. es posible calcular el DF:\ dc Iiis clcnientos 1. denontinarnos gram6tica LALR(1) a una gramática si no surgen conflictos de análisis en el algoritmo de análisis sintáctico LALR(I). Si. iiiieritras que u n analizador sintáctico I. Como antes.7. figura 5 9 fl DiA de conjuntos de dementor MLR(l) para el epmplo 5 17 El algoritmo para el análisis sintáctico LALR(1) utilizando el DFA condensado de los elenientos LALR(I) es idéntico al algoritmo de análisis sintáctico LR( 1) general descrito en la sección anterior. pero todavia requiere del »FA coinpleto de elenientos LK( 1) paracalcularse.16 es LALR(1): el DFA no de los elementos LR(1) de la figura 5. en presencia de errores.9 observamos que.6. un anaiizador sintáctico LALR(I) efectuará la reducción A -+ a antes de declarar el error. Por ejemplo. De hecho. entonces por supuesto es LALR(L).thlas de análisis sintáctico grandes. la única consecuencia de utilizar el análisis sintictico LALR(1) en vez del análisis sintáctico LR general es que.Aniliris iintácttco LALR(I) y LR(I) general Proporcionareiiios un tjcnq?lo para demostrar esta construcsióri. 3 y 6 proporciona el DFA de los elementos LALR(I) en la figura 5. Por ejemplo.

. Un nerador de analizadores sintácticos ampliamente utilizado que incorpora el algoritmo análisis sintáctico LALK(1) se conoce como Yacc ("Yet Another Compiler-Compile decir. obtuvimos el DFA d mentos LALR(I) de la figura 5. y es el archivo de entrada).~ Ferir en alguna forma de la versión que presentamos aquí.A en el estado O.CAP.' : i 5. Continuando con el estado 2.0. el $ se propaga a lo mentos de núcleo de los estados 1. c. utilizanios varias versiones diferentes para gelirir. 7. Swnia parte del ~oftware <liledistribuye la Frce Software Griii Foundotion: véase la secci6n de notas y referencias. además de varias versiones del dominio público comúnmente denomina ~ i s o n existen nunicrosas variaciones en los detalles de su operación. tab.5.5 Yacc: UN GENERADOR DE ANALIZADORES SINTÁCTICOS iALR(1) Un generador de analizadores sintácticos es un programa que toma corno su entrada una '.ir los cjjeniplo\ posccriolrs. las cuales pueden . los generadores de. la transicih con ( del estado 2 h mismo causa que el ) se propague a la búsqueda hacia delante del elemento de núcle se explica por qué el elemento de núcleo tíene tanto $ como ) en su conjunto de b hacia delante). 5. S / ANALISIS SINTACTICO ASCENDENTE de manera formal. tab. más recientemente <nombre d archivo>. En esta sección daremos una pectiva general de Yacc. 'También. De este modo. debido a que todos los pasas de compilación eran realizador de manera tradicional como acciones inclui dentro del analizador sintáctico. G n u Bison.y)y produce un chivo de salida compuesto del código fuente en C para el analizador sintáctico (por lo neral en un archivo denominado y. Considere el DFA LALR(I) de la figura 5. "otro compilador de compilador mis" en inglés). y produce como SU salida unr:2 procedimiento de análisis sintáctico para ese lenguaje. Debido a que existen varias implernentüciones d rentes de Yacc. 3 y 2. especificación de la sintaxis de un lenguaje en alguna forma. . Ahora el conjunto de búsqueda hacia delante $0 se propaga al estad terionnente d estado 7. es formativo ver c6mo se puede hacer esto de manera relativam l'icil. ünn vcrsi6n popular. los element rradura obtienen la búsqueda hacia delante). analizadores sintácticos fueron llamados compiladores de compilador. c o ytab. A es segiri la cadena vacía). de modo que este término se volvió obsoleto. por las reglas de la cerradura E. Ahora la transición en a al estado 3 causa que el ) se propague a la búsque cia delante del elemento en ese estado. Históricamente.) Entonces. a través de este proceso. Al seguir las tres transiciones desde el estado O. y en la sección siguiente lo utilizarenios para desarrollar un ana dor sintáctico para el lenguaje TINY.1 Fundamentos de Yacc Yacc toma un archivo de especificación (por lo regular con un sufijo . Camcnzarnos construyendo búsque cia delante al agregar el rnarcador de finalización $ a la búsqueda hacia delante del el to de aumento A' -+ . nuevamente por generación espontánea a que A en el lado derecho del elemento de núcleo A -+ (.9 directametite del DFA de elementos LRJO). donde <nombre de archivo>. (Se dice que la búsqueda hacia delante $ se espontáneamente. Un archivo de especificación de Yacc tiene el formato básico 6. Efectivmricrite.A ) viene antes de un p derecho). La visión moderna es considerar al itnalizador cotrio s una parte del proceso de compilación.. el $ se propaga a Los iientos de cemidura (la A en el lado derecho del elemnto de núcleo A' -+ . c o.

de r~itinasauxiliares.anclo LI t$miplo siiiiple.irchivos #include no y que son nwemrias pare complctnr el analizador sintlictico y10 el compilaclor. Esta secciiíir del itrchivo de cspeciiic:ii:icíii prietle estar v~rcía.i. iiii . E. L .a tercera sección.inios (le maliziir la gr. se emplee en tina reduccitin.iles que Yace ~ieccsita para c o t i t r u i r el . sección de reglas contiene reglas gr. existen tres secciones (la sección cle definiciones.inali/ador sintcicticct. y s i éste es el c:iso se puede otnitir el scgitndo ~iietnsínrholo porceiiiiije doble del archivo de especificación. junto coi1 acciones cti ccídigo C que se ejecutarciri sicnipre que se reconozca la regla graiiiatical asociada (es decir. teriornieiitc e11 esta sccci6n). Estii sección clc puede estar viicía. [. De esta iiiancra. Coriio es habitual. tipos de k i t o s y reglas grarnatic.Yaci: un generador de analizadorei itnrácator LALR(1) (definiciones) %% {reglas) %% {rutinas auxiliares) [>e este niodtr. Tainhién un signo tle ptiriio y ctrriia dehe finalizar cada regla grlirn~tic:tl. IA seccitiri de definicicmcs contiene itiliirinación acerca de los rokens. 'Tatiibi6ri incluye crialqriier ctidigo en C que debería i r direcianiente en el archivo tlc sdicla a su inicio (sobre todo directivas #include de otros archivos de ctidigo firente). tlo lema que se tmtn pos-.rt. la barra verticd se r i t i l i ~ a para las altertiativas (las alicrnatic~is tatnhi6n se puedcti escribir por separado).tiiiaticnles en una k x m a BNF modificada.ítica . y.ini:ític.ille utili. de acuerdo con el algoritnio de tniilisis sin~:ícticoLAL.i de expresiotles ariirn6tictis enteras sin>ples con la gn\iii.R(I)). en cualquier punto donde no intcrfierari con el Sorniaio hisico. 1.as convenci«ries de riietasínibolos iitilizcidas en Iiis reglas graniaticides wi de la nrntierli siguiente. coritiene tleclnraciiriit:~ de procediitiientos y frincioiies que (le otra Iniinera p~tederi esi:tr disponibles 21 través de . la seccitin de reglas y la icec i k de rutinas auxiliares) scparüdas niediante Iíncab que coniicneii doble signo de prircciilei~e. tcnnbihi perniite insertar conientzirios al estilo de (? en el archivo de espccificacih A. El ejemplo es una calcu1ador.irchivo de cspecificacióri inínitiiti de Yace consistiría scílo (le %% seguidos por reglm grarriaticales y acciories (las :icciones t:unhién se pueden rrntitir s i sólo ir. E1 sitiiholo de flecha -+que licnios enipleado para separar los lados izquierdo y derecho de una regla grariinrical se reenplaza en Yace por un sigtio de dos puntos.~plicarcnicisel contenido clel :irchivo de especificzrcicín de Yücc con mcis dci.

$l).228 CAP.) : int yylex(void) í int c. 5 1 ANÁLISIS I NTACTICO AICElDENTE co. return(NüMI3ER).&yylyal).) + - $3. scanf("%d". / * hace que se detenga el análisis sintáctico * / return(c). "%s\n".s) . 1 . } / * permite la impresión de un mensaje de error * / . / * permite la impresión del resultado * / exp : exg ' + ' term { $ $ = $1 I exg ' .) term term ' * ' factor ( $ S = $1 * $3. while((c = getcharo) == ' ' ) .' term ( $ $ = $1 I tem ( $ S = $1.mmand : exp I printf(""ad\n". 1 if (C == '\n') return O.) I factor ( S $ = $1. void yyerror(char * S ) ( fprintf(stderr.stdin). / * elimina blancos * / if ( isdigit(c) ) ( ungetc(c.) $3.

Tipicaiiicnte. Yacc inserta estas definiciol~es tokcn como sentencias # d e f i n e en el c<itligo (le saliilii.il o del signo de punto y conia). Así. representan los valores de cada sínibolo en s~iccsión bles $1. A l escribir las acciones podemos ciprovecharnos de las pseudovariables de Yacc.ilor después del nombre del token en II dec1ar.ición tle : asignará al token escribientlo L I ~ token. Se valor [le símbolo en la pila utilizando una pseudovariable puede hacer referencia n c~tda que coiriierrce con u n signo de tfr. De tiranera característica. Eii primer lugar. como los tokens de operador +. Sin embargo. cada sínrbol~)cn la regla posee u n valor. De de e~icoiitr.y a la ctial asocianios la x c i ó r i de irnse toma corno presicín. al escribir R se asigna a ~ B E el valor trurriérico I S (en lugar de 258). Eii segundo lugar. cor~~ttzcir~rl el síiribolo inicial de la gramática. $ $ representa el valor del no termind que se cicaba de reconocer. A tales tokens se les asigna un valt~r nuinérico iiiediante Yacc que ~ i entre en cotitlicto con ningún valor de o cüricter. los tokens siiribólicos se pueden declxar en una declilracibn % t o k e n de Yacc. en vez (le iriiportar una tlefinición de alguna otra parte. Como también querernos irnprirnir el valor de una expresión. y así sucesivamente.10 (así corno los tokens de pari-ntesis). Las iicciones se especifican e11 Yace al escribirlas c o ~ i i o ccídigo en C real (dentro de llaves) con cada regla grüinatical. es posible especificar el valor nutiiérico que se v. en el lado dcreclio de ia regla graniaticai.iiriatic. ) . los lokens de un solo caricter se pueden iricluir directamente en reglas gramaticales de esta manera. Por ejemplo. Las pseodovaria$2.Yatc: u generador d analizadores s~ntácticosLALR(I) n e 229 Yacc tiene dos ritaneras de reconocer tokens.10. De nianera alternativa. D e esta ri~anerü. $3. Yacc insiste en definir todos los tokens sirribólicos por sí rnismo. En la seccicín de reglas de la figura 5.~rínmos línea la este nod do. Cuando se reconoce una regla gramatical. tcrrrl y j%c. el código de acción se ubica a1 fiiial de cada seleccicín de regla gr. Esos valores se conservan en una pila de valor niediante Yacc.10 «bscrvamos las reglas para los no terminales r.rp. y * de l a figura 5. cotilo el token m B E R ("número") de la figura 5. que denoininaremos cr~rnrriirnd.en la figura 5. la cu:d se rtrantieiie paralela a l a pila de análisis sintictic«. en cuyo caso no tendríatilos que poner la regla para roritrticiiitt primero. po<lríam«s ti:iber+iiicluido la línea en la seccibn de definiciones. c~ialyuier a ~ í c t e encec r rrad« entre comillas sintples en una regla gramatical ser6 reconocido conio él tnisrno.il (pero antes de la barra vertic. 11) la regla p r r ~ a tical y :iccicín exp : exp '+O term ( S $ = $1 + $ 3 . tenemos una regla adicional. es decir. Yacc coiriienza asignando valores de tokeii con el núrncro 158. en el archivo de salida probabler~icnte - como respuesta de Y:icc n la declaración % t o k e n NüMBER en el archivo de especificación. que se supone es un enteru a menos que sea cambiado por e l programador (posteriormente veremos cdmo hacer esto). el sirtibolo en el 1:ido izquierdo de la regla grarnotical. Debido a que la regla para cornrnimd se enuniera primero. aunque también es posible escribir acciones incrustadas en una selecciúii (comentaremos esto en breve).tor.

) . Es fácil proporcionar acceso a Yacc a estos procedimientos escribiendo archivos de encabezado apropiados y colocando directivas #include en L sección de definiciones de la especificación Yacc. Yacc tiene una opción disponible que automáticamente producirá un archivo de cabecera que contiene esta información.L única excepción a a esto es cuando el analizador Iéxico ha llegado al final de la entrada.10 también incluye una definición para yylex. h. a menos que este carácter sed un dígito.y.En esta situación particular el procedimierito yylex es muy simple. diiniento de atálisis sintáctico que produce.que es el nonrbre que Yacc proporcima al pro. De este modo.10 (la sección de nitinas auxiliares) contiene la definici de tres procedirnientos.10 está contenida en el . Es más difícil dejar disa ponibles las definiciones específicas de Yacc para otros archivos. Yacc utiliza este procedimiento para imprimir un mensaje de error cuando se encuentra un error durante el análisis sintáctico (Yacc imprime específicamente la cadena "syntax error". si I:r especificación Yacc de la figum 5. pero que deben esrar disponibles para muchas otras de un compilador (en particular el analizador léxico). la cud entonces se puede incluir en cualquier otro archivo que necesite las definiciones. pero este c ~ m p o ~ m i e npuede ser motlificado por el usuario).CAP. Finalniente.. en la regla gramatical y acción factor : WNBER { $ S = $1. las cuales. el final de la en entrada se indica mediante un carácter de "retorno de Iínea" o "nueva Iínea" ('\n' C). Esto es particularmente cierto para las definiciones de token. pero esto se hace durante e pr L ceso de análisis Iéxico.5. De esta manera. La primera es una definición de main. Este airchivo (le cabecera por lo regular se denomina y.y éstos a menudo se colocan en archivos externos en lugar de directamente en el archivo de especificación Yacc. La tercera sección de la fígura 5. Este procedimiento se declara para devolver valor entero. Todo lo que necesita hacer es devolver el siguiente carácter que no sea bian co. y 1 si no es así (es decir. la especitlcación Yacc de la figura 5. esto es una convención compartida con Lex). entonces el coniando Yacc -d c a l c . ei valor $1se refiere al vaior del token NOMBER que fue ~tsignado previamente a yylv cuando se reconoció cl token. to <. y .~rciii\w calc. se define un procedimiento yyerror. y debe ser asignada cuando se reconoce token. En este caso. en cuyo caso debe reconocer el token simple de cadcter múltiple N'üMBER y devolver su valor en la variable yylval. Yacc espera que el tina1 de la entrada sea señalado mediante una devolución del valor nulo 0 por yylex (nuevamente. Yacc insiste en generar por sí mismo (en lugar de importarlas). El procedimiento yypar generado por Yacc llama a su vez a un procedimiento de analizador Iéxico. como hemos dicho. Yacc supone que el valor de un token se asigna a la variable yylva la cual se define de manera interna por Yacc.ex (véase el capítulo 2). que es siempre O si el anáfisis tiene éxito. y es producitlo con la opción -d (por el térniino :irchivo de cabecera en inglés: "heaDer filc")..Y X -:< 5. ya que estamos suponiendo que una expresión se introduce en una sola Iínea. . que esta incluida de nta que la salida resulímte de Yacc se pueda compilar directamente para un programa ejecuta El procedimiento main Ilania a yyparse. Por esta razón. que se sup tiene el nombre yylex para propósitos de compatibilidad con el generador de analizador léxicos [.f : . 5 1 ANALISIS IINT~CTICO ASCENDENTE Todos los no terminales obtienen valores mediante tales acciones suministradas por usimio. h o bien ytab. si se Iia p sentado un error y no se ha realizado recuperación de errores)..2 Opciones de Yacc Por lo regular habrá muchos procedimientos auxiliares a los que Yacc necesitará tener acceso además de yylex y yyerror. Los tokens también pueden tener valores asignados. Por ejemplo. tab.

idor.Yacc: iin generador de analizadores iintacticos UILR(I) 23 1 prodrtcirá iaileiiiis del itrcliivo y.riializador sirttictico genera&> por Yacc cri ciialq~iier situ:iciúri.y Figura 5 ll U a espeafil::acion e n n eiqueleto Ya« para su uso ton la opcrbn . püra asegurarse de que el analizatior sintácticn gencr:tdo por Yacc futici«n. cuyo co~itenido varía. tab.icicín Yacc de la figura S . Esta opciún prodtlce totlavía otro archivo.h Itin non~hre similiir). cs una hucna idea cjeciitar Y x c coi1 esta i)pciiiii sobre la gramática por sí misma.V %token NIJMBER 0 0 6' 4 command exp exp 't' term term l exp I term term ' * ' factor I factor NIJMBER term factor I ' ( ' exp ' ) < . tab. Ambas especificaciotles generará11 el niisirio archivo de salida cuantlo Yace se utilice con la opción verbose: yacc -V ca1c.11. En realidad.c) el archivo y. Este archivo contiene una descripcicíir textual de la tnhla tle análisis sintáctico LAI. i r ~ h i v o . pero por lo regular incluye cosas colno la sigriicrite: #ifndef YYSTYPE #define YYSTYPE int #endi£ WBER #de£ ine 258 extern YYSTYPE yylval. output (o uii nombre semejante).rgregar las acciones r i procedimientos nitsiliares a la especificación. (Ikscribiremos el significado de YYSTYPE con m i s detalle en breve.) Con este .v e n In línea de comando.irchivo se puede cctlocar el ccídigo para yylex cri un archivo diferente insertando la lírica cri ese . antes de .ir~í en realidad coriio se espera. LO.R( 1) que es utilizada por el ari:ilil. L:i lectura de este archivo permite al usuario determinar exactamente qué aceicín toniari el . Cotiio ejernplo corisitlere k i especificación Yacc de Ia figurrt 5. con c l rioinhre y. ~ Una segunda o p c i h imuy útil de Yacc es la opción verbose (opcibn descriptiva) itctiv d a niediarrte la señal . Estü es súlo una vcr: si611escueta de II especific. y &t:i puede ser una manera muy efectiva para rastrear üirihigiied:i<les e irrtprecisiones en mta grnntitica.

para utilizar durante las reducciones de los no terminales dad tils acciones están exactamente como aparecerían en una tabla de análisis sintáctico truida a mano empleando los métodos de este capítulo. con el listado de salida state 2 connnand : exp(1) e x p : e--+ term exg : exp-. Yacc comienza en el estado O exhibiendo el elemento inicial de la producción de au to. se desplaza a1 estado 6 en el token de búsqueda hacia dela y declara error en todos los otros token de búsqueda hacia delante. Examinemos brevemente la sección de acción del estado 0. en lugar del punto que hemos estado utilizando en este capítulo. Los estados se enumeran conienzan el O. shdft 8 reduce 1 . utiliza el punto en cambio para indicar una opción predeterminada. Especí mente. luego las acciones correspondientes a di búsquedas hacia delante.También lista el pseudot de final de entrada de manera explícita como $end. que sigue a la lista d mentos de núcleo: NüMñER s h i f t 5 ( shift 6 error connnand goto 1 exp goto 2 term goto 3 f a c t o r goto 4 . outgut se lista en su totalidad para esta gratnática en la figur Analizaremos la interpretación de este archivo en los párrafos siguientes. este elemento se-escribe como Saccegt : -command Send Esto corresponde al elemento commund' -+ comrnand en nuestra propia terminología. La lista anterior especifica que el DFA se desplaza al estado 5 con el token de búsqu hacia delante NUMBER. proporciona al no terminal de aumento el nombre Saccegt.CAP. Consideremos ahora el estado 2. . En el arc de salida de nuestro ejemplo. que es siempre el único elemento de núcLeo en el estado inicial del DFA. Yacc utiliza un carácter de subraya o guión bajo para marcar la posición disti da en un elemento.S 1 ANALISIS IINTACTICO ASCENDENTE Un archivo típico y. Bajo cada estado el archivo de salida enumera primero los elementos (le núcl elementos de cerradura no se enumeran). El archivo de salida de Yacc se compone de un listado de todos los estados en el seguido por un resumen de estadísticas internas. o un token de hús hacia delante "sin importancia" en la sección <?eacción de cada listado de estado. y finalmente las acciones ir a en varios no terminales. También se enum cuatro transiciones Ir a.term + shift 7 .

state 3 shift 7 shift 8 reduce 1 NUMBER ( . reduce 6 shift 5 shift 6 error goto 13 factor state 5 factor : N~BER- reduce 7 . ( . shift 5 shift 6 error . shift 9 reduce 4 term : NUMBER term *-factor state 4 term: factor- ( .term term goto 11 f a c t o r goto 4 state 8 exp : exp --term + . exp: termterm : term-* (4) factor 3tate 9 shift 5 shift 6 error term goto 12 f a c t o r goto 4 * . shift 6 error state 2 command : expexp : exp-+ term exp : exp-. shift 5 shift 6 error command goto 1 exp goto 2 term goto 3 f a c t o r goto 4 state 7 state 1 $accept : command-$end exp goto 10 term goto 3 f a c t o r goto 4 exp : NUMBER exp +-term shift 5 Send accept error .Yacc: un generador de anal~radoreri~ntáct~tos LALFi(1) state O Caccept : -comand NUMBER ( state 6 $end factor : NUMBER ( (-exp ) . .

shift 9 reduce 3 state 13 term : term * factor- (5) state 11 (2) exp : exp + termterm : term-* factor .term factor : ( exp-) state 12 e x p : e x p . parser 11/4000 9/601 distinct lookahead sets 6 extra closures 18 shift entries.7). de niodo que habrá una reducció mediante la selección de producción asociada en la sección de acción. Yace siempre enumera las producciones en el orden en el que se exhiben en el archivo de especificación. En este caso el número de producción es 1. shift 9 reduce 2 . 36/2000. shift 7 shift 8 shift 14 error . maximum offset: 43 Aquí el elemento de núcleo es un elemento completo. Yacc muestra el número después del elemento completo. En nuestro ejemplo existen ocho producciones (una para command.10 utilizando la opcion "verbose" * + ) . O reduce/reduce conflicts tegorted 9/601 working seta used memory: states. 1 exceptions 8 goto entries 4 entries saved by goto default Optimizer space used: input 50/2000. 5 1 ANÁLISIS IINTÁCTICO ASCENDENTE Figura 5.term(3) term : term-* factor de la figura 5. tres para exp.t~arniento).la cual indica una reducción mediante la producción commtrnd + exp. y hay una acción reduce 1. Esto significa que los mensajes de crror pueden no ser tan iliformaiivos corno deherían. dos para tcrm y dos parafactor).-. reduce 5 state 14 factor : ( exp )- (8) * . Advierta que la acción de reducción en este estado es una acción predctertnintida: se realizará una reducción en cualquier búsqueda hacia delante diferente de + y -. . 15/1000 states O shift/reduce. Un analizador sintáctico Yacc generalmente hace varias reducciones sohre errores antes de declarar finalmente un error (lo cual debe hacer en algún morneiito antes de que ocurra otro iiespl. Aquí Yacc difiere de un analizador sintáctico LALR(1) puro (e incluso de un analizador SLR( 1)) en que no se hace ningún intento por verificar la legalidad de las húsquedas hacia delante en las reducciones (tnás que para decidir entre varias reducciones).output generado por la especifitatión Yacc state 10 exp : exp-+ term exp : exp.12 íronilusión) Un archivo ripico y. pero la rahla de anilisis siritáctico se vuelve corisider:ibleinente inás sencilla. output 218/4000 218 table entries. 202 zero maximum spread: 257. etc. puesto que se preieiitan titenos casos (este puiito \e volverá a cotnentar en la sección 5.CAP. reduce 8 8/127 terminals. Para recordarnos el número de la producción que se está utilizando para la reducción. 4/600 nonterminals 9/300 grammar rules.

Yacc tiene reglas de clirninación (le ambigüedad integradas en él mismo que le perrnitirin producir tin analizaclor sintáctico incluso en la presencia de conflictos de análisis (por consiguiente.tndizador sintáctico pro<lucido por Yacc los resolverá de manera correcta. hasta para graniáticas ambiguas)..iib.10 no había conflictos de análisis s i ~ i t á c k n Yacc inlory imí de este hecho en la infbrrnaci6n resnrnicln al final del archivo de salida como O ehift/reduce. euiictainerite como lo huhiéra~rios de cstt: c.iriodeterminar cu:iles son los conflictos de análisis sintktico.ipíttilo. y si el .5. . .*. pero en ocasiones no es así.. L a tabla tle .ti el cjeniplo de la figura 5.12 5.icc.. Tabld 5 11 Estado Entrada Ir a Tabla de anál~sar~ndct~to torrpspondientea la salida yace de la figura 5.3 Conflictos de análisis sintáctico y reglas de eliminación de ambigüedad t i n o (le los usos importantes de la opción verbose es i~ivestigar conflictos de análisis 10s sintáctico. ".. . .. .tjo. output.: 5. . Yacc: un generador de analizadores sintatlitos IALR(I) 235 c. ..~de Y.. O reduce/reduce conflicts reported . . Con frecuencia estas reglas de eliminación de ambigüedad hacen bien su ti. de los que Yacc informará en el archivo y. F. A l examinar el archivo y .inilisis sintictico aparece en I.i tabla 5 . 1 1. . Concluiremos este ejemplo riiediante la construcción de tina tabla de anilisis siniicticci escrito a ni:irio al principio clel iirchivo de saliti..output se permite al ust~..

9. Esto es más probable que sea.18 state O Saccegt : -S Send a shift 4 error goto 1 goto 2 goto 3 . ya que la cadena legal simple tr tiene las dos derivaciones S * A * a y S * B * a. O reduce/reduce conflicts regorted En el caso de un conflicto de reducción-reducción. Ofrecemos el siguiente ejemplo simple. Yacc elimina la ambigüedad al preferir la reducción por la regla gramatical que se mostró primero en el archivo de especificación. En la información resumida Yacc también informa del conflicto simple de reducción po desplazamiento: 1 shift/reduce. Esto da como resultado que nunca se utilice esta tíltima regla en una reducciiin (lo que indica claraniente un problema con la gramática) y Yace informa de este hecho al final con 1a 1' inea Rule not reduced: B : a ¡gura 5. S A J .UP. aunque también p i d e resultar en un anali~ador sintáctico correcto.13 rchivo de ialida de Yacc ara la gramática del emplo 5. excepto por las reducciones precieterminadas que Yacc secta en las entradas de error. 13. output citmpleto para esta gramática se proporciona en la figura 5.output (los tokens se definieron en letras ~nay Iüs e11 el archivo [le especificaciiin para evitar conflictos con las palabras reservadas lenguaje C): 5: shiftlreduce conflict (shift 6. Yacc infornia de las acciones del estado 5 (1 la manera siguiente en el archivo y. En realidad. el cual es resuelto al preferir la regla A -+ n en lugar de la regla 5 --+ cr. Observe el conflicto de reducción-reducción en el estado 4. red'n 3) on ELSE state 5 1 : IF S(3) 1 : IP S-ELSE S ELSE shift 6 reduce 3 .un error en la gramática. fjemplo 5.S I ANÁLISIS SINTACTICO ASCENDENTE exactamente en los mismos términos. Por ejemplo.18 Consideremos la gramitica siguiente: S 4 A B A -t a B+a Ésta es una gramática'arnbigua. y resuelve la anibigüedíd mediante la misma re de eliminaciiin de ambigüedad. la tabla de análisis sintáctico mostrada Yacc es idéntica a la tabla 5. El archivo y.

In tiibla de nixÍlisis sintáctico asociticla taiithiét~ y de ser rnis peq~ieña el analizador s i n t i c t i w rcsulratite.18 accept .idarnerite cie L~I: vcnt+jas.~..i prccedcncia y :iwci.i de c l i m i t ~ a c i ~ í ~ .1 wcciiin clc ilcfiiiicioncs :iI cscrihir las lincas .inihio. En esa t'igrira l a p titiítica esrli csctita en k ~ r r t i a mhigu. En ~?riiricr lugar. (le Esto tiene diversas dores separ. LALRI 1) Figura 5. 3/600 nonterminals 5/300 grammar rules. varios de i iiteca~iisnios p r o k s o para la cspeeificaciciti de la tisociatitidad \.13 ronlinudtinn 'i P : i state 1 $accept : Send S-Send ~ ~ ~ h dev salida de Yacc i o la gramática del tjempIo 5. por cjerirplo. I.I de :I c. state 2 error S : A- .rtlotw ~c especificri en 1. 5/1000 states O shift/reduce. la gramitica no necesita cortteiier coristruccioncc explícitas que ser especifiq~icnrisociatividad y precedencia. ~. En segiiitdo l u p r . 1r15seficiente.)uctle ~ i i l i s priecoiicisit y irlis siiiiple..r~rihigiiedarIya mcncioriad. 13. 1 reduce/reduce conflicts reported Y x c tie~ic. espccifictrcih Yacc de la f i g r w 5 .i~ivitl:icI dc li)s oper. 1. state 4 A : reduce 2 on Send 4: reduce/reduce conflict (red'ns 3 and 4 aa- B : reduce Rule not reduced: B : a 3/127 terminals. y esto significa que la graniitictr ~. Considere. state 3 reduce S : 8 - (2) .i sin prccetlericia o asociittivid~tl los opeririiores. .Yacc u generador de analiradores sintácticos n . prccedcncia de operaex I grarrititica q ~ i e otro triotlo es m r h i g ~ i a .itle~iiisde la rcgI.

' exp I exp ' * ' exp i ( S $ = $1 + $3.) %% / * auxiliary procedure declarations as in Figure 5.5.h> #include <ctype. extern int yydebug. al agregarse las líneas siguientes al principio del procedirniento main de la figtira S.) f $ $ = $1 * $3. también es posible obtener un analizador sintáctico generado por Yacc pa- ra imprimir un rastreo de su ejecución.outgut. tab-c con el sírnbolo YYDEBUG definido (utilizando la opción de compilador -DYYDEBUG. yydebug = 1.) ($9 = $1 . Por ejemplo. 10.(ya que se nuestra después de estos operadores en &raciones). .por ejemplo) y estableciendo la variable entera yydebug de Yacc a I en e1 punto donde se desea la irtformiición de rastreo. incluyendo una descripcicín de la pila de anilisis sintáctico y acciones del analizador semejantes a las descripciones que ya dimos antes en este capítulo.10 * / 5.CAP.h> %) gradttca amb~gua y reglar de precedencia y asociatividad para operadores ton %token command : exp i exp : NüMBER I exg ' + ' exp I exp ' .$3. Las otras posibles especificaciones de operador de Yacc son %rig %nonassoc ("nonassoc" significa que no se permiten operadores repetidos en el in nivel) F~oura5 14 Espetificacion de Yacc para una taltuladora smpk %(: #include <atdio. Esto se hace compilando y.4 Rastreo de la ejecución de un analizador sintktico Yacc Además de la opción verbose que exhibe la tabla de análisis sintáctico en e1 archivo y. S ANÁLISIS IINTACTICO ASCENDENTE precedencia inás alta que + y .

la entrada 2 + 3 Next token is NüMBER Shifting token m B E R . Reducing via rule 1.5.> exp state stack now O Entering state 2 Now at end of input.5 Tipos de valor arbitrario en Yacc En la figura S. Entering state 7 Next token is NüMBER Shifting tcken NUMBER.t expresitíii L Y ~ la w n a tle los O . factor . Invitanios al lector a que construya un rastreo a mano de las accio~ies analizador sintáctico utilizando la tabla de tiriálisis sintríctico de In del tabla S. Reducing via rule 2.> term state stack now O Entering state 3 Next token is ' + ' Reducing via rule 4. Entering state 5 Reducing via rule 7.> command 5 state stack now O Entering state 1 Now at end of input.> term state stack now O 2 7 Entering state 11 NOW at end of input.scrihiinos $ $ = $1 + $3 para cilahlecer i.> exp state stack nav O Entering state 2 Next token is ' + ' Shifting token ' + ' . exp ' + ' term .> factor state stack now O Entering state 4 Reducing via rule 6.1 mlor de iiii.il. term . 10 especificrimos las acciows de uiia cdculirdora cmplemtlo las [)scrido\:ariahles de Y x c awciadas con c . NüMBER . . ~ síiriholo gi-aintitical en una regla gratnniic.Yacc un generador de analizadores rintáctitos LALR(I) 239 se provocará que el analizador sintáctico prodtizca una salida parecida a la de la Figura 5. Por. Entering state 5 Reducing via rule 7. factor . NüMBER . i ~ ~ n p l o . exg .> factor state stack now O 2 7 Entering state 4 Reducing via rule 6. l I y lo comp:rre con esta salida.15. 5. y dará la expresión 2 + 3 como entrada. h i.

y estos dos tipos de datos son diferentes. Por ejemplo.CAP. es inadecuado si. exp -+ exp opsurna term / term op. queremos una calculadora p h x e r c&lculos con valores de punto flotante. si queremos una calculadora que haga cálculos de valores en to flotante. las cuales cam mos para reconocer los operadores directamente en las reglas para exp de la figura 5.xp debe devolver valor calculado (digamos. S I ANALISIS SINT~CTICOASCENDENTE valores de sus dos subexpresiones (en las posiciones 1 y 3 en el lado derecho de la re gramatical e. En situaciones más complicadas podemos necesitar valores diferentes para reglas rnaticales diferentes. y esto se co sigue mediante el uso de la directiva % t m e en la sección de definiciones: %tyge <val> exg t e m factor %type <og> addog mulog Advierta cómo los nombres de los campos de unión estan encerrados entre picopnrénte sis en la declaración % t m e de Yacc. char op. debemos agregar la línea #define YYSTYPE double dentro de 10s deli~nitadores %{ .Furnadebe devolver el operador (un carácter). I:. 1 . puesto que el tipo predeterminado de Yacc de estos valores es pre entero.rp -+exp + renn). Sin embargo. > . la especificación Yacc de la figura 5. como en reglas . se modificaría para comenzar de la siguiente manera (y dejaremos los detalles para Imi ejercicios): %union ( double val.p. De este modo. supongamos que queremos separar el reconocimie de una selección de operadores de ia regla que calcula con estos operadores. Este t (le datos siempre se define en Yacc mediante el síniholo de preprocesador en C YYST La redefinición de este símbolo modificará apropiadamente el tipo de la pila de v de Yacc. Entonces. char og. Esto está hien mientras que los valores con los que es tratando sean enteros.Ahora Yacc necesita estar informado del tipo de retorno de cada no terminal. En este caso debemos incluir una redefinic del tipo de valor de las pseudovariahles de Yacc en el archivo de especificación. mientras que e. Podem hacer esto de dos maneras. un double)..1 Ahora op. por ejemplo. Lo qu necesitarnos hacer es definir YYSTYPE corno una unión de double y char.mnm -+ + / - (Éstas eran de hecho las reglas originales de la gramática de expresión. Una es declarar la unión directamente en la especificación Ya utilizando la declaracih Yacc %union: %union { double val.%} en la sección de definiciones del archivo de esp ficación de Yacc.

} . > 1 I term ( $ S = $1.irchivo iriclrtido por sep:trado (por ejemplo.$l). 1 . var-list : var-list ' . '-'.6 Acciones incrustadas en Yacc Ert ocasiones es iiecesario ejecutar algún cótligo nritcs de coinplet. rtn archivo de cabecera) y posreriormerite tlefinir YYSTYPE coriio tie este tipo.} I ID i setType(tokenString. $$ = S . IJri ejeniplo de esto es el analizador sititictico TINY tle la secci6n siguiente. break. 5. ' ID i.* .tcciítii aswiado. citando se r~conocier~i vur-iist. ) var-list tVpe : INT f $ $ = INT-TYPE.current-type).) op : ' + ' ( ( $$ = .) : exp op term ( switch ($2) ( case ' + . ctiquctar cada identificatlor de variable nrin con el tipo act~ial (entero o de punto flotante). $ $ = $1 - $3.ntatticos LALR(I) %type <op> addog mulog %% command : exp exp ( grintf("%d\n". setTypeitokenString. $ $ = $1 + $3.current-lype). En Yacc podertios hacer esto del tlioclo que se muestra a continuación: decl : type ( current-type = $1. Considere corno ejeriiplo el caio (le las cteclnraciones simples: Nos gnstaría. '-' I ' 1 La segunda alternativa es definir un nuevo tipo de datos en rtri . Entotices los valores apropiados se deben coristruir a mano en el código de .tr el rccortocirniento de una regla gratrtatical durante el aixílisis sintictic«. break. case ' .5. I } I FLOAT ( $$ = FLOAT-TYPE. + ' .Y3cc un generador de analizadorer i.

E : ( / * acción incrustada * / } . pemitiendo valores de tipos diferentes t n la pila del mnliraclor sintáctico I>cfinecl tipo de rini~invari:inte ilevtielto por u n siiriholo Define 1. realiza la acción incrustada: A : B E C .h yyparse yylval werror error yyerrok yychar YYSTYPE yydebug Nombre interno de Yacc Archivo de cabecera generado por Ylicc que contiene definiciones de token Rutina de análisis sintktico de Yacc Valor del token actu~l la pila en Jnipresión de mensaje de error definido por el usuario ritilizado mediante Yacc Pseudotoken de error de Yace Procedimiento que restablece el analizador sintáctico después de un error Contiene e1 token de hlísqueda hacia delante que causó un error Símbolo de preprocesador que define el tipo de valor de la pila de anúlisis sintáctico generación de información eri tiempo de ejecución ace %token %start %union %type % l e f %right %nonassoc t Define símbolos de prcproccs:idor de token Define el símbolo iio terminal inicial Define un YYSTYPE de uni<in. cuando se reduce.12 los nombres internos y mec de definición de Yacc que hemos a~ializado. Yacc interpreta una acción incrustada A : B ( / * acción incrustada * / ) C .s :isociütivid. como equivaletite a la creación de un nuevo no terrni~ialreteriedor y una prodiiccicín e ese no terminal que. Para cerrar esta sección resumimos en la tabla 5. definición de Yacc y.CAP. S 1 ANALISIS IINTACTICO ASCENDENTE Observe cómo se establece current-type (el cual para ser declarado necesita una ble estática en la sección de declaraciones) mediante una accirín de incrustación ante reconocin~ierito las variables en la regla decl.id y preccOencia (por posicih) de opmdores . En las secciones siguientes se verá de ejemplos de acciones incrustadas.tab.

los acciones correspondientes a write-stmt de T l N Y (líneas 4082 y siguientes) son con)« se ve a continuacibii: . TlNY UTILIZANDO Yacc En la s e c c i h 3. Entonces asigna el valor previamente construido de exp (la pseudovoritihle $2 de Yacc. L a variable savedLineNo se utiliza para e l rnismo prcipiisito. C»tnenz. stmt-seq y assign-stmt Lr. ¡a sección de tlefirticiortes tiene otras crtatro . y se tiecesitan riodos hijo apropiados del nuevo nodo de árbol para ser asignados.AS acciones para program.7 se expuso la sintaxis de TINY. Procedereinos a un análisis de las acciones asociadas con cada una <le las reglas gramaticales de T l N Y (estas reglas son ligeras variaciones de la gramática RNF expuesta en e1 capítulo 3.junto con los cambios necewrios a las definio ciones globales globals h (tomiimos pasos para minimil-ar cambios a otros archivos. write-stmt : WRITE { exg $ $ = newStmt~ode(WriteK) . líneas 4000-4 162. de riiaiiera que los números de línea apropiados del código fuente estarán aoci. de T I N Y .jo tiel nodo de árbol de la sentencia de escritura.it. Esto permite al analizador sintáctico de Y.. Acliií descrihirenios c l :irchivo de especificaciiín Yacc tiny.Generacion de un analizador sintáctico TINY utilizando Yacc GENERACION DE UN ANALIZADOR SINTACTICO .y está listado en el . que es un aptintador al nodo de árbol de la expresibn por imprimirse) para que sea el primer hi. S$->child[Ol 1 i 1 $2. Específicatnerite. La primera (línea 4014) es una d e f i n i c i h de YYSTYPE que define los vaLores devueltos por los procedimietitos de análisis sintáctico de Yacc corno apuntadores a estructuras de nodos (TreeNode mismo está definido en globals h). L a primera instrucci6n Ilaina a newStmtNode y asigna su valor devuelto corno el valor (le write-stmt. y en la seccicín 1.rmos con un análisis de la secciiín de definiciones acerca de la ecpecificació~i y. En el caso de l a reglti 21-imatiI cal [):ir:' program II occi6ri asociadti (Iíriea 4020) es . remitimos al lector a esas descripciones.6). y. Por +rnplo. En la mayoría de los casos estas acciones representan laconstrucción del árbol sintáctico correspondiente al árbol de unálisis gramatical en ese punto.4 se dcscribiií un analizador siritáctico escrito a iiiatio. L que t m b i é n se comentará).icc conslri~ir árbol siiitktico. figura 3.idos con los identificadores. I.rri coti pcqueiíos prublerrias asociados con ca<lir iina de cstas ccrnstrucci<tries. declaracioties. Tenertios cuatro archivos #include representando la inforrnaciiíti que ~iccesita Yacc de otra parte en el programa (líneas 1009-4012). se necesitan nuevos nodos para ser asigi~ndos mediante llarriadas a newStmtNode y newExpNode desde el paquete util (éstos se describieron en la página IX2).ipCndice H.GILL . L a segunda declaracicín es de una rin cndetias variable estática global savedName que se ernplea para almacenar temporrrlmc~ite de identificador que iiecesitan ser insertadas e11 nodos de árbol que todavía no se han con+ truido cuando las cadenas se ven en la entrada (en T I N Y esto es ttecesario s6lo en asignaciones). E l código de accitin para otras scritencias y expresicviei es muy similar. E l uso de l a marca YYPARSER (línea 4007) se ctescrihirá posteriorineiite. Finalmente. savedTree se utiliza para almacenar de manera teinporiil el árbol si~itáctico proclucido por el procedimiento yyparse (yyparseniisnio s61« puede devolver uiia rnarca de valor entero). El archivo cotitpleto tiny.

Esto asigna el ái-bol construido para la stmt-seq a la variable estática savedT to es necesario para que el árbol sintáctico pueda ser devuelto posteriormente t11e procedimiento parse. . a medida que se igualen vos tokens. Finalmente. Utiliza una viiriable yychar intem que contiene e1 número de token correspondieute al token que ocasionó el error. ya rnenciouarnos que necesitamos almacenar la c del identificador de la variable que es el objetivo de la asignación de manera que est ponible cuando se construya el nodo (así como su número [le Iínea para una exploración po terior). yyerror.name = savedName. ( La cadena de identiticador y el número de Iínea se deben guardar como una acción in tada antes del reconocimiento del token ASSIGN. El procedimiento yylex es necesario debido a que Yacc supone que éste es et no procediniiento del andizador léxico.) En el caso de stmt-seq (líneas 403 1-4039). S$->child[Ol = $ 4 . $$->attr. Como la regla para secuencias de sentencias se escribe de manera recursiva por quierda. En el caso de aseign-stmt. líueas 1200--1.el problema es que las sentencias se en un árbol sintáctico de TlNY utilizando apuntadores hennanos en lugar (le apuutador jos. esto requiere que el código rastree la lista de hermanos ya construida en busca la sublista izquierda a fin de unir la sentencia actual al final. Resta describir los crinibios a los otros archivos en el analizador sintáctico de TINY que se hacen necesarios por el uso de Yacc para producir el :inalizador sintáctico.h. El . pero esta solución ti su propio problema. pretendimos mantener estos cainbios en un mínimo. iniprime cierta infoimació tal como el número de Iínea. Esto se a que Yacc cuenta las acciones incrustadas como ubicaciones adicionales en los lados chos de las reglas gramaticales: véase el análisis de la sección anterior. y éste Fue definido de mmem externa como Escribimos esta definición demanera que el analizador sintáctico generado por Y cionara con el compilador TINY con un mínimo de cambios para otros archivos del c puede desear hacer los cambios apropiados al analizador Iáxico y eliminar esta de ticularmente si se está utilizando la versidn de Lex del analizador léxico. Como observamos. Incluso el nuevo nodo para la asignación no puede ser completaniente construido que la exp sea reconocida.320. Esto no es eficiente. al archivo de listado. Esto se consigue utilizando las variablcs estáticas saveüName y saveLineNo neas 4067 y siguientes): assign-stmt : ID savedName = copyString(tokenString): savedLineNo = lineno.irchivo revisado he muestra en el :rp6ndice B.puesto que. Advierta que también el valor exp es referido como $4. ) ASSIGN exp I $$ = newstmt~ode (AssisnK). y puede tarse volviendo a escribir la regla de manera recursiva por la derecha. llama al p to de análisis sintáctico definido en Yacc yyparse y luego devuelve el árbol sint6ctieo do.el cual se invoca desde el programa principal. El p yyerror es llamado por Yacc cuando se presenta un error. y confinamos trxdos los minbios :11 archivo globals . De aquí surge la necesidad de saveüName y saveLin (El uso del procedimiento utilitario cogystring asegura que no se comparta la me entre estas cadenas.yylex y pa El pmcediiniento parse. que consiste en que la pila de análisis sintáctico se acrecentará a medi que se procesen secuencias de sentencias más largas. los valores de tokenstring y lineno son modificados por el analizador xico. la sección de procedimientos auxiliares de la especificación Yacc (1 414-4162) contiene la definición de los tres procedimientos.

puesto que repetiría las definiciones internas. Esto oculta la fuente precisa del error y puede conducir a mensajes de error poco informativos. Una característica adicional del análisis sintáctico ascendente es que la potencia del algoritmo particular utilizado puede afectar la capacidad del analizador sintáctico para detectar los errores de inmediato. .7. quizás después de un número de reducciones "erróneas". tiene sentido que loserrores deberían detectarse tan pronto corno fuera posible. ya que todos los tokens de Yacc tienen valores enteros. Por desgracia esta meta está en conflicto con otra de la misma importancia: la reducción del tamaño de la tablü de análisis sintáctico. Recuperacion de errores en analizadores sintácticos ascendentes 245 El prohlerna básico es que el archivo de cabecera generado por Yacc. mientras que el analizador LR(0) reducirá otra vez medianteA -+ a antes de declarar un error. la tabla de análisis sintáctico LR(1) de la tabla 5. que contiene las dcfiniciones de token.iricr. YYPARSER (línea 4007). dada la cadena incorrecta a)(5.R(I).7 RECUPERACIÓN DE ERRORES EN ANALIZADORES SINTÁCTICOS ASCENDENTES 5. ~ .. El cambio final al archivo globals h es volver a definir TokenType corno un sinónimo para int (línea 4252). tab. Ya vin~os (tabla 5. .2 Recuperación de errores en modo de alarma Como cn 21 :tniílisis sintijctico clcsceiidente. pero no puede ser incluido directamente en el analizador sinráctico generado por Yacc. . Utilizantos esa marca (líneas 4226-4236) para incluir de manera selectiva el archivo de cabecera generado por Yacc y. el algoritmo LR(0) (así como el atgoritmo SLR(1)) reducirán mediante A -+ a antes de descubrir la ausencia de un paréntesis derecho para completar la expresión. Yacc supone que este token siempre tiene el valor O..ii~ninando sínibolos de in. IJn segundo problema se presenta con el tokcn ENDFILE.10 (página 222) para la misma gramática. un analizador sintáctico LR(1) puede detectar errores en una etapa más temprana que un analizador LALR(1) o SL.1 Detección de errores en el análisis sintáctico ascendente Un analizador sintáctico ascendente encontrará un error cuando se detecte una entrada en blanco (o de e m r ) en la tabla de análisis sintáctico. ya que los mensajes de enor pueden ser más específicos y significativc.'.puesto que Yacc'tio la necesita internamente.4 (página 209) con la tabla de análisis sintáctico LR(I) de la tabla 5. De manera similar. \e puede conseguir una iwxper~ciiiiide errores ixonablcmente buena en :nialimdorcs asccritlenies i. Dada la cadena de entrada incorrecta (a$ ..11) que Yacc rellena tantas entradas de L tabla como puede con reducciones predeterminadas.> . : ..7. el . .. . Obviamente.rr ptuilcrirc. de manera que se pueden producir un a gran número de reducciones en la pila de anáiisis sintáctico antes que se declare un error. una tabla de análisis sintáctico deberia tener tantas entradasen blanco conlo fuera posible. y estos últinios pueden detectar errores más pronto que un analizador LR(0). De este modo.. . Esto evita reemplazos iiinecesarios del tipo enumerado TokenType anterior en otros archivos. de modo que se informará de un error. cualquier analizador sintáctico ascendente siempre terminará por informar del error.que genera el analizador 16xico para indicar el fin del arclrivo de entrada.h en globals h. En contraste. Como un ejemplo simple de esto compare la tabla de análisis sintáctico LR(O) de la tabla 5.. . debe ser incluido en la mayoría de los otros archivos de código. 10 desplazará ( y a hacia la pila y se moverá al estado 6.. 5. .. La solución es comenzar la sección de declaraciones de Yacc con la definición de tina marc.. En el estado 6 no hay entrada bajo $.. . Ninguno de estos analizadores desplazará jamás u n token en estado de error. 5. Por ejemplo. Suministramos una definición directa de este token (líneas 4234) y la incluimos en la sección selectivamente compilada controlada por YYPARSER. .is.malizador sintáctico LR(1) general desplazará a y despu6s declarará un error del estado 3 en el paréntesis derecho. Naturalmente. la cual se incluirá en el analizador sinráctico de Yacc e indicará cuando el compilador de C a t é dentro ctcl analizador sintáctico.

y si el mismo estado repite. Extraer sucesivamente tokens desde la entrada hasta que se vea uuo de ellos p cual se pueda reiniciar el análisis sintáctico. pue que es similar al modo de alarma descendente descrito en la sección 4. $ 0 ( 6 E 10+7 . En ese pun modo de alarma podría ocasiotiar que se produjeran las siguientes acciones en la pil análisis sintáctico: Pila de análisis s~ntactico Entrada Acción . Un niétodo particulrtrmente efectivo para elegir cuál de estas acciones realizar cuando presenta un error es el siguiente: 1. error: inscrtar T.. Si en cualquier momento se presenta una ac de desplazamiento. el analizador vuelve a establecer la marca y continúa con un análisi táctico normal. Ejemplo 5. esto puede ser demasiado restrictivo. estas reglas pue dar como resultado un ciclo infinito. Si existen v de tales estados. Considere ahora la entrada errónea ( 2 + análisis sintáctico continúa normalmente hasta que se detecta el asterisco *.CAP.. 2.19 considere la gramática de expresión aritmética simple cuya tabla de análisis sintáct' Yacc se da en la tabla 5. Otra solución es. Entre las accione reducción preferir una cuyo no terminal asociado sea menos general. análisis sintáctico o de l. Desgraciadamente. si el siguiente movimient gdl es una reducción.5.i entrada.1 1 (página 235). *)$ S O ( 6 E 10+7 7 ' 1 1 $0(6E10+77'11 ' 9 '30(61:'10+7T1l * 9 F l i *)S )$ )$ .... 3. como el paso 2 inserta nuevos estados en la pila. y comience de nuevo con el paso l . establecer una marca que provoque que el analizador sintáctico sig pista de la secuencia de estados durante las siguientes reducciones. Sin bargo. Si éste es el caso. Insertar un nuevo estado en la pila. Si existe una accitín legal sobre el token de entrada actual desde uno de los est Ir a. preferir un desplazamiento a una reducción. s 1 ANALISIS SINTACTICO ASCENDENTE ya sea de la pila de. y reiniciar el análisis sintáct' inmediatamente a partir de entonces. Si no existe acción legal en el token de entrada actual desde uno de los estados avanzar la entrada hasta que haya una acción legal o se alcance el final de la entr Estas reglas tienen el efecto de obligar al reconocimiento de una construcción que viera en el proceso de ser reconocida cuando ocurre el error. Extraer un estado de Ia pila. insertar ese estado en la pila y reiniciar el análisis sintáctico. existen tres posibles acciones alternativas que se pueden contem 1. existen varias posibles solucio Una es insistir en una acción de desplazamiento desde un estado Ir a en el paso 2. 3. extraiga estados de la pila basta que se elimine el estado original en el que ociirri error. o bien de ambas. Extraer estados de la pila de análisis sintáctico hasta encontrar un estado con ent das Ir a no vacías. La rec~tperación errores que utiliza éstas o unas de glas semejantes se podría denominar recuperación de errores en modo de alarma. ir a 13 reducir T -* T * F . 2.ir a I 1 desplazar 9 error: insertar F. Como en los a dores sintácticos LL(I)..

.

10. ya que de otro modo. La situaci resultante es ahora como se muestra a continuación: PILA DE ANÁLISIS SINTÁCTICO ENTRADA +3$ $0 cornrnund 1 En este punto la única búsqueda hacia delante legal está al final de la entrada (indicado aq por $. sólo que ahora podemos proporcionar nuestro propio men de error. Entonces intentará reconocer otro corrrtntrnd Aquí es necesaria la llamada a yyerrok para cancelar el "estado de mor" después de que se tietccta el retorno (le Iínea. En este punto el análisis da la sigui te pila de análisis sintáctico y entrada (utilizamos la tabla 5. el analizador léxico se debe modificar para devo ver el carácter de retorno de línea (en vez de O). Así. el analizador sintáctico saltará todos los tokens hasta uno de retorno de Iínea. y el fin d marcador de Iínea es el único sensible.1 1 para describir esto. f Considere también la entrada errónea 2 + + 3 . ha ta que se descubre el estado 0. e inmediataniente reducirá a command. y se desplazará a la pila de anál sintáctico. { { grintf ( % d \ n w .1 $1) yyerror ("expresión incorrecta"). se presenta un nuevo error justo después .) 1 error '\n' ( yyerrok. cuando ejecutará la acción representada por yyerrok y la sentencia printf.$l). S 1 ANALISIS SINTAC~ICO ASCENDENTE Ejemplo 5.248 CAP. correspondiente a la devolución de O por yylex). En este punto la producción de errores para commund esti la que error es una búsqueda hacia delante legal.20 Considere e1 siguiente reemplazo a la regla para commmd de la figura S. y el analizador sintáctico climin rá los tokens de entrada restantes +3 antes de salir (aunque todavía en el "estado de error" De este modo. y con esta moditic~ción podemos escribi (pero véase el ejercicio 5. cuando ocurre un error. . la adición de la producción de error tiene esencialmente el mismo efecto la versión de la figura 5. printf("reintroducir expresión: " ) .32): comand : exp * \ n l { printf("%d\nlv.exit(0). Un mecanismo de error aún mejor que éste permitiría al usuario reintroducir la Iínea de pués de la entruda errónea. El análisis de esta cadena continúa norma mente hasta que se alcanza el segundo + erróneo. En este caso se necesita un token de sincronización. causando que se ejecute la acción asoci (lo que imprime el mensaje "incorrect expression" o "expresión incorrecta"). 10: c o m n d : exp 1 error . } comnd i Este código tiene el efecto de que. aun la adición de la producción de error resultará en realidad en una tabla de análisis ligera te diferente): PILA DE ANÁLISIS SINTÁCTICO ENTRADA Ahora el analizador sintáctico introduce el "estado" de error (generando un mensaje de er tal como "syntax error" o "error de sintaxis") y comienza a extraer estados desde la pila.

)unque la producción de error adicional da como resultado una tabla ligeranlente diferente. Ahora el analizador ha Ilegatlo al punto siguiente: PILA DE ANÁLISIS SINTÁCTICO ENTRADA Ésta es una situación normal. provocando que el valor O sea devuelto. y el analizador continuará su ejecución normalmente hasta el final.fizctor estipillará que e r r o r es una húsqueda hacia ~Ielante legal en el estado 7.dos númerus con un operador perdido). 1 1. y e r r o r será inmediatamente desplazado a la pila y retli~cido afiíiicror.cl cri . aun cuaricio un número no es un símbolo siguiente legal para (mmrund.) I error ( S $ = 0. y e1 e\tatlo I se cxirae tle la pila de análisis siniiíciico.Recuperación de errores en analiradores rinta<tiros ascendentes 249 del retorno de línea. E1 analizador alcanza entonces la posición Ahora se detecta un error.10 de la iiianera siguiente: factor : N[MBER ( $ $ = $1. i Allora el .~ la cnlrada! . con el niiiricro 3 todnví.decir. Ahora consideremos la entrada errónea 2 3 (es.tiilílisis ~i~itittico produciendo otra cascado de reducciones y la iriipresi~iri valor O (el v. (Continuamos usando la tabla 5.) ~ n i s n ~ n posiciiin en el :inilisis sintictic». y por la acción para la producción (le error.\f1)rtci1iatlarl1e1itc. descuhrientlo el estado 0.) Corno antes. .ilor decic1 \rielro por la procliiccih de en-or).) I ' í ' exp ' ) ' ( $ S = $2.rnnliiador está de regrcso i. Aquí el analizador sintáctico alcanra la posición En este punto (si la regla para comrnund no se ha cambiado). y e r r o r se dcsplam a la pila tle . e r r o r se visualiza como equivalente a un factor con valor O). § Imaginemos lo que pasaría si se agregara una producción de error a la definicih Yatc de la Figura 5. el analizador reducirá (erróneamente) mediante la regla ccirnnrand -t exp (e imprimirá el valor 2).) Considerenios en primer lugar la entrada errónea 2++3 conio en e1 ejemplo anterior. el analizador sintáctico alcanzará el punto siguiente: PILA DE ANÁLISIS SINTÁCTICO ENTRADA $0 erp 2 + 7 +3S Ahora la producción de error para. Yacc eliminará silenciosamente los tokens hasta que encuentre una secuencia de tres tokens correctos.11 1. El efecto es interpretar la entrada como 2 + 0 + 3 (el O entre los dos sínibolos de + está allí porque allí lire donde se insertó el pseudotoken e r r o r . En este punto la producción de error pamfirctor permite que e r r o r sea una hílsqneda hacia tlelante legal desde el estado 0.

2 Considere la gramática del ejercicio anterior. Estas producciones de error ta poco proporcionan sincronización especial para el reinicio del análisis sintictico. Construya el DFA de elcnientos LR(0) para esta gramática. describa el conflicto LR(0). Construya la tabla de aniílisis sintActico IL.(a. d. pero desechará el número 3. b.7. .a)).) 5.?cticoy las acciones de un aiializatlor SLR( 1 ) para la cadena deentrada ((a). 5. c. con lo cual el analizador sintáctico sale de escena. EIE a.ida del usuario en la primera línea): > 2 3 2 error de sintaxie expresión incorrecta o Este comportamiento nos da una visión fugar de las tlificult&s de una buena recuperaci de errores en Yacc.CAP. Construya l ü tabla de análisis sintáctico LK(I) general. c.a. ( L )! a L+L. descubriendo la búsqueda hacia delante correcta el estado 1 .4 Recuperación de errores en TINY El archivo de especificación de Yacc t i n y y en el apéndice B contiene dos producci de error. construya la tabla de análisis sintáctico LR(O) y describa cómo puede diferir un análisis sintáctico de un análisis SLR(1).1 Considere la gramática siguiente: E-. Si lo es."' El resultado es que el lizüdor imprime lo siguiente (repetimos la entr. b. Muestre la pila de análisis siiit..ALK(I). d. a. Cotistruya la tabla de anrílisis sintáctico SLR(1). cuyas acciones ciadas son para devolver el árbol sintáctico nulo. de mo que en la presencia de errores múltiples se pueden omitir muchos tokens. Estas producciones de error proporc un nivel de manejo de errores semejante al del analizador sintáctico descendente recur de TINY expuesto en el capítulo anterior. S I ANALISIS SINT~CTICOASCENDENTE analizador sintáctico esti ya en el "estado de error" y no desplazará de nueva cuen error. . EJERCICIOS 5. Construya el DFA de elementos LR(1) para esta gramática. s6lo que no se hace ningún intento de constr un árbol sintáctico significativo en la presencia de errores. ¿Es esta gramitica una gramática LR(O)? Si no es así. Construya el UFA de elementos LALR(I) pira esta graniática. una para stmt (línea 4047) y otra para factor (línea 4139). (Véanse los ejercicios para más ejemplos.

Construya la tabla de análisis ~intáctico SLR(1).e. describa el conflicto LR(0). LESesta gramática una gramática LR(O)? Si no es así. b. 5. d. b. Construya el DFA de elementos LALR(1) para esta gramática. c. Construya la tabla de análisis sintáctico SLR(1). d.3 Considere la gramática siguiente: a. c. Construya el DFA de elementos LR(0) para cstt gramática. Construya el DFA de elenleotos LR(0) para esta grrtrnitica.6 Considere la gramática del ejercicio anterior. Conshlya la tabla de análisis ~intictico LALR(1). 5. Muestre la pila de análisis stntáctico y las acciones de un analizador SLR(1f para la cadena ba el conflicto LR(0). Si lo es. Construya el DFA de elementos LR(I) para esta gramática. Muestre la pila de análisis sintáctico y las acciones de un analizador SLR(1) para la cadena de entrada s i s : s . Describa cualquier diferencia que se pueda presentar entre las acciones de un analizador sintáctico LR(1) general y un analizador sintáctico LALR(1). a. Construya el DFA de elementos LR(0) para esta gnmáiica. Construya la tabla de análisis sintactico LR(I) general. b. . Si L es. o ferir un análisis sin- e un analizador ~ i n - a. De~criba cualquier diferencia que se pueda presentar entre 1 s acciones de un analizador sintáctico LR(1) general y un analiz 5. b.7 Considere la gramática siguiente: a. c. Construya la tabla de análisis sinticrico SLR(1). consiruya la tabla de análisis sintitctico LR(0) y describa cómo puede diferir un análisis mtáctico de un análisis SLR(1). e.

e. Construya el DFA de elementos L. Muestre que un analizador sintáctico I. Considere la gramática siguiente: ~ieclarución tipo var-list -t tipo -+ int / f loat vur-list -+ identificador. Describa una descripción formal del algoritmo para constniir el DFA de elementos LALR(1 propagando búsquedas hacia delante a través del DFA de elementos LR(0). Muestre la pila de análisis sintáctico y las acciones de un analizador SLR(1) para 13 cad deentrada ( (alata a) ) . Muestre que la gramática siguiente no es LR(I): A . .:\L. d. Construya el DFA de elementos LALR( 1) inediante la propagación de búsquedas ha lante a través del DFA de elementos LR(0). y. Construya la tabla de análisis sintáctico SLRf1) para la gramática reescrita. ¿.+ U A U ~ E b. d.R( 1 ) antes qiie tieclarar u n error'! Justifique su respucstn. (. a.3. si el siguiente token de entrada no piiede ser desplazado con el tiempo. Construya d DFA de elementos LALR(1) propagando las búsquedas hacia delante a tra del DFA de elementos LR(0) del inciso h.R(I) general no hará reducción antes que se declare un error.Esta gramática ei ambigua? Justifique su respuesta. Constntya la tabla de análisis sintáctico LALR(1) para la gramática reescrita. Sin embargo. . Muestre la pila de análisis sintáctico y las acciones de un analizador SLR(1) para la caáe de entrada int x.Puede haber una gramática SLR(1) que no sea LALR(I)? Justifique su respuesta. c. e. la pila de análisis sintáctic necesita sólo almacenar los números de estado: los tokens y no terminales no necesitan ser a macenados en la pila.CAP.4.R(O) para la gramática reescrita. f. (Este algoritmo s describió de manera informal en la sección 5. z utilizando la tabla del inciso c. vur-list / identificador a.Puede un analizador sintictico SLR(li hacer rn6s o menos redacciones que un analizador I. Construya la tabla de análisis sintáctico LALR(1j. Describa el algoritmo de análisis sintáctico SLR(I) si sólo los números de estado se mantienen en la pila.. Muestre que la \tguiente gramática es LR(1) pero no LALR(1): Muestre que una gramática LK(1) que no es LALR(1) debe tener sólo conflictos de reducciónreducción. Vuelva a escribirla en una forma más adecuada para el análisis sintáctico ascendente. ! I 1 ANALISIS I NTACTICO ASCENDENTE c.) Todas las pilas de análisis sintáctico mostradas en este capítulo incluyeron tanto núnieros de estado como símbolos gramaticales (por claridad). Muestre que un prefi~o una forma sentencia1 derecha es un prefiJo viable si y sólo si no se de extiende más allá del controlador. b.

.

3.10 para agregar los siguientes nlensajes de error útiles: "paréntesis derecho olvidado". por lo tanto. páiina 249. Vuelva a escribir la especificrición de la calculadora Yacc de la figura 5. Explique las diferencias eii comporiamieiito. S I ANALISIS I I N T ~ T I C O ASCENDENTE 5. Escriba una fuiición que tome como parátnetro el &bol sintáctico producido por su código del inciso a y deviielva el valor calculado a1 recorrer el árbol. c.) 5.3. Menos unitario con el símbolo -.) d. b.27 Viselva a escribir la especificaiión Yaec de ia figura 5. Vuelva a escribirla para eliminar este problema. 5.30 Vuelva a escribir la espécificación de la calculadora Yacc de la figura 5. se reeinplara por la regla siguiente: factor : NWMBER ( $ S = SI. División entera con el símbolo /. (Véase el ejercicio 3. suprimir el token NiJKñER): número -t número dígiro / dígito d í g i t o + O / l l 2 i 3 i 4 / 5 / 6 1 7 / 8 / 9 5. generado por la cadena ( 2 + 3 "paréntesis izquierdo oIvid:id«".10 de manerq que disringa entre valores de punto flotante y valores enteros en vez de simplemente calcular lodo como enteros o números de punto flotante. Exponenciación entera con el símbolo ".) 5.i<leiw( 2+ ) .5.33 Vuelva a escribir la especificación del calculador Yacc de la figura 5. EJERCICIOS DE PROGRAMACI~N 5. Explique el cornporiümiento del anali~ador Yacc dada la entrüda errónea 2++3. generado por la cadeden:~ 3 2 "operando olvidado". Módulo entero con el símbolo %.31 a. 5. Explique el comportamiento del anali~ador Yacc dada la enuada crrónea 2 3. LO de iiianera que devuelva un árbol sintáctico de acuerdo con las declaraciones de la sección 3.10 (asegúrese que tengan la asociatividad y precedencia correctas): a.10.28 Agregue lo siguiente a la especificación de la calculadora de enteros Yacc de la figura 5. 5. grner:rdo por la c.10 de manera que acept números de punto flotatite (y realice ciilculos en modo de punto flotante).12. b.21. generado por la cadena 2 + 3 ) "operador olviddo".2. para u t i ~ i ~ias sigui ar tes reglas gramaticales (en lugar de scanf)en el cálculo del valor de un número (y. página 235. (Sirgerencia: un "valor" es ahora un registro con tina marca que indica si es entero 0 de punto flotante. $S = 0.32 La técnica de recuperación de errores simple sugerida para el programa de la calculadora en la página 248 era errónea porque podía causar desbordamiento de la pila dzspués de muchm errores. (Adverlrncia: este operador tiene una precedenci mayor que la inultiplicación y es asociativo por la derecha.) i a.25 Suponga que la producción de error Yace del ejemplo 5.29 Vuelva a hacer la especificación dc la calculadora Yacc de la figura 5.26 Compare la recuperación de errores del analizador sintáctico TlNY generado por Yacc con del analizador sintáctico descendente rectirsivo del capítulo anterior utilizando los programas de prueba de la sección 3.) I error {yyerrok. b. 5. I ' ( ' exg ' ) ' ($S = $2.CAP.].

rs siniplcs eri una not.) 5.i\ <leiIri:i \iot.ir los estados y transiciones tlcl NFA asociado.5.) .35 1.2 ) 3 4 ) tiene el valor . un progranla que totnará una expresión regular coirio eritnida y corno salida un programa en C que. para rcpresent.mientras que erp y otros no terminales devuelven :ipuni.24.37 Agregue 10s oper:idores de comparación < = (menor 0 igual que). y b. Extienda sil especific:ición del inciso a para incluir todas las acciones y procedimientos auxiliares neces.34 La granxÍiicii siguiente I-eprcsentaenpresi~mcs aritriiétic.\gregue 111sc~pcl-:riiores ho~ile:iiiosand. 5. sin acciirnes) que exprese la asociatividad y precedencia correctas de las operaciones. :ulcni.ictico de TINY. Escriba una especificación Yacc para iiri programa que calcril..i~nática(mhigiia) representa las expresicines tegdares sirnples .qrrt.idor. El NFA se puede entonces simular utilizando una cola para al~nacenar estadcis. cuando se compile. lii~eas 4000-4162) cii tina forma rnBs compacta en cudquiera de las siguientes dos maneras: a.sci.3 sigtiiente gr.idores hacia los nodos de 6rbol).nciir: esto reqi~criri volvcr a escribir la gramítica y usar un rnec. buscará tina cadena de entrada para la primera ocurrencia de una subcadena que iguale la expresión regular. es decir. (Esto rcqtieriri agrcgiir c s t o ~ tukens y rtiiidilicar el itnali~.inaliLadasen el capítulo 2: a. > (mayor que). V6ast: el c:ipítul« 2.38 . colapsando el reconocimiento de operadores en una sola regla.ir:í e irriprimirá el valor de las expresiones en esta sintaxis. Sólo se ~iecesita gencnir la tabla mediante las acciones Yacc.inisni« para pasar el operador it una .iri»s para prmti~cirun "coiiipilndor de cxpresiories regulares". (Sugerencia: puede utili~arse tabla.le\ l:i\ pi-i~picildes .intGctico. >= (niayor o igwl que) y < z (distinto de) 21 la especificaci6n Y:icc para el ariali~ador sititlictico TINY. el resiti del código siempre ser2 el roisiiro.icc [ x ~el . n arre1rna glo hidiinensional. la expresih ( * ( .Ejercicios de programación 5.ailr>r Iéuiw tamhién.36 Vuelva a escribir la especificación Yacc parir 'TINY (apéndice B.iciím Y.or y n o t ir la c?pei'ific. Asígiit. como en y iirilizan<lola declaración Yacc %union(dejando que op deviielva el ~tper.-irip. b.tipo LISI': Por qjeriiplo. haciendo tiso de una gr:iniátice nrnhigua para expresiones (ain reglas de eliirii~iriciónde xnbigüedüdes Yacc para la precctlencia y la asociatividad). Asegárese de que su . pero no dehería !sequerir i ~ r ic.iiiali~ador sintictic« produrca 11)sririsrnos árboles sintácticos que antes. Escriba una c s p e c i f i ~ i c i h escpcleto cn Yacc (cs decir.iirihiii :il irht11 .~ 5.) 5.is cn cl cjcrcicio 1.in:ili/ailirr i tlescrii. (Su.icitin de pre-j.

.

por ejemplo.Capítulo 6 Análisis semántico 6. La complejidad de un análisis de esta clase requerido por una definición del lenguaje varía enormemente de lenguaje a lenguaje. Como el análisis que realiza un compilador es estático por definición (tiene lugar antes de la ejecución).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. pero no es tan condescendiente como LISP). El análisis semántico se puede dividir en dos categorías. l.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. del programa que se traduce. o semántica. mientras que en un lenguaje como Ada existen fuertes requerimientos que debe cumplir un programa para ser ejecutable. 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.6.4 Tipos de datos y verificación 6.ipititlo 3 . por lo que no se considera como sintaxis. Otros lenguajes se encuentran entre estos extremos (Pascal. tales como LISP y Smalltalk.3 del c. En un lenguaje típico estáticamente tipificado como C. Este punto fuc cmneniadci con algún detalle en la secci<h 3. En lenguajes orientados en forma dinámica.2 6.3 Atributos y gramáticas con atributos Algoritmos para cálculo de atributos La tabla de símbolos 6. dicho análisis semántico tambien se conoce como análisis semántico estático. no es tan estricto en sus requerimientos estáticos como Ada y C. 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. puede no haber análisis semántico estático en absoluto.' La información calculada tambikn está estrechamente relacionada con el significado final. I 6.

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. la cual asegura que el contenido semántico de un programa se encuentra estrechamente relacionado con su sintaxis. que expr san cómo el cálculo de tales atributos está relacionado con las reglas gramaticales del le guaje. Sin embargo. Conviene advertir que las té nicas estudiadas aqui se aplican a ambas situaciones. Además. sino sólo una clase de exactitud parcial. También que las dos categorías no son mutuamente excluyentes. junto con los cálculos a realizar cada vez que se encuentra un nodo en el recorrido. 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. ya que los requerimientos de exactitud. Investigaremos algunos de estos métodos en el capitulo sobre generación de c digo. existe un problema adicional causado por la temporización del análisis durante el proceso de compilación. o propiedades. Esta clase de análisis por lo regular se incluye en análisis de "optimización".CAP. es semejante al análisis léxico y sintáctico. y en parte porque la cantidad y categoría del análisis semántico estático varía demasiado de un lenguaje a otro. 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. de entidades del lenguaje q deben calcularse y escribir ecuaciones de atributos o reglas semánticas. por ejemplo. En el análisis sintáctico. En este se do. esto implica que el compilador debe ser de paso . 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. En el análisis semántico la situación no es t clara. esto se debe en parte a los mismos problemas que se acaban de mencionar respecto a la especificación del análisis semántico. Aún peor. Tales req rimientos todavía son útiles debido a que proporcionan al programador información p mejorar la seguridad y fortaleza del programa. Las gramáticas con atributos son más útiles para los lenguajes que obedecen el principio de la semántica dirigida por sintaxis. N o obstante. tales como la rificación de tipos estáticos. ya que rara vez la da el diseñador del lenguaje. mientras que en este capitulo nos enfocaremos en los análisis comunes que por exactitud son requeridos para una definición del lenguaje. De nuevo. Un fundamento mucho mejor para la expresi de los cálculos semánticos es la sintaxis abstracta. vale la pena hacer no que los requerimientos de exactitud que aqui se comentan nunca pueden establecer I exactitud completa de un programa. Todos los lenguajes modernos tienen esta propiedad. Un conjunto así de atributos y ecuaciones se denomina gramática con atributos. Los algoritmos para la implementación del análisis semántico tampoco sqn tan claramente expresables como los algoritmos de análisis sintáctico. el diseñador del lenguaje. Incluso. como se representa mediante un ár sintáctico abstracto. 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. 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. Por desgracia. uti zamos gramáticas libres de contexto en la Forma Backus-Naus (BNF. el escritor de compiladores casi siempre debe construir una gramática con atributos a mano a partir del manual del lenguaje. también suele dejar al escritor d compilador la especificacicln de la sintaxis abstracta. o técnicas de mejoramiento de código. 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. también permiten que un compilador genere código más e ciente que para un lenguaje sin estos requerimientos. por sus siglas en inglés) para describir la sintaxis y diversos algoritmos de análisis sintáctico descendente ascendente para implementar la sintaxis.

más conciso y menos proclive a errores para análisis semántico. Los iictnpos ilc fijacifin de :itrihutos iliferentes viiríon. 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. 6.~. Aunque se han construido varias de tales herramientas. además de permitir una comprensión más fácil de ese código.istantc tlií'ucntcs (le un !enguii~c . el número de dígitos significativos eri un número se Por puede establecer (o por lo menos dar un valor mínimo) mediante la definición de un Ienguaje.Atributos y gramátttas con atributor 259 múltiple. ejemplo. el capítulo comienza con un estudio de atributos y gramáticas con atributos.1 ATRIBUTOS Y GRAMATICAS CON ATRIBUTOS U n atributo es cualquier propiedad de una construcción del lenguaje de programación. La última sección describe un analizador semdntico para el lenguaje de programación TlNY presentado en capítulos anteriores. ya que esto redundará en la capacidad de escribir un código más claro. 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. 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. Continúa con técnicas para implementar los cálculos especificados mediante una gramática con atributos. c iircluso c l niis~no ~iirihuio puetlc teticl.jación. su complejicuando dad y en particular el tiempo que les torna realizar el proceso de traducci<ín/ejec~ición pueden ser determinados. Por lo tanto. los atributos s6lo se pueden determiriar durante la ejecuciúti tiel progrini. A pesar de este estado algo desordenado del análisis semántico. Si. o la ubicaci6n de una estructura {le datos dininiicaniente asignada. iieinpos ilc: qnci(íri h. 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. el compilador necesita realizar todas sus operaciones en un solo paso (incluyendo la generación del código). por otra parte. ti11 como el valor de una expresicín (no consiante). El tiempo que cn. Los atributos pueden variar ampliamente en cuanto a la inf«rmación que contienen. ninguna ha logrado el amplio uso y disponibilidad de Lex o Yacc. Dos secciones posteriores se concentran en las áreas principales del análisis semántico: tablas de símbolos y verificación de tipos. es muy útil para estudiar gramáticas con atributos y cuestiones de especificación. 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. incluyendo la inferencia de un orden para los cálculos y los recorridos del árbol que los acompañan.tnd« se presenta la í'ijacitin de un atributo se detoma el proceso de conipilación/ejcc~~ci<íri iiominii tiempo de f¡. Ademis. 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).

en un lenguaje como LISP. Un compilador por lo regular pospondrá los cálculos asociados la asignación de variables hasta la generacibn de código. mientras que en LlSP todas las variable asignan dinámicamente. ser constantes (por ejemplo 3 + 4 * S)..2. En un lenguaje corno C o Pascal. un escritor de compiladores está interesado en aquellos atributos estáticos que s jan durante el tiempo de traducción. . Los atributos que pueden fijarse antes de la ejecución se denominan estáticos. 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. Está implícito en el sentido de que a el escritor de compiladores implementa l9 representaciones para los valores.5 para inforriiaci6n sobre el . Natur mente. los cálculos de atributos son muy variados. 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.inálisis . incluso el analizador léxico puede necesitar conoce número de dígitos signitlcativos permitidos si va a producir constantes de manera correcta.*gj . La asignación de una variable puede ser estática o dinámica. 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. Analizamos el ti po de fijación y la significancia durante la compilación de cada uno de los atributos la lista. y un analizador semántico de elegir evaluarlas durante la compilación (este proceso se conoce como incorp ción de constantes). . Los valores de las expresiones por lo regular son dinámicos. el tipo de datos de una vari o expresión es un importante atributo en tiempo de compilación. Como vimos a partir de estos ejemplos. los tipos de datos son dinimicos. Sin embargo. Considere la lista de muestra de atributos dada con anterioridad.inálisis sintáctico (pero vdase la secciiín 6.. porque tales cálculos depe den del ambiente de ejecución y. de hecho. El generador de código de un compilador está comprometido por completo con el cálculo de este atributo. algunas expresi pueden. tanto el analimdor léxico como el analizador sintáctico pueden necesitar disponer de información de atributo. Sin embargo. 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. Por ejemplo. ocasionalmente. En un lenguaje estáticamente tipificado como C o Pascal. 6 1 ANALISIS SEMANTICO a otro. C y Pascal tienen una mezcla de asignación de variables e tica y dinámica.CAP. y un compilador gener código para calcular sus valores durante la ejecución. Sin embargo. y puede ser necesarici realizat rilgún anhlisis semántico al mismo tiempo que cl anilisis sintáctico. lo que general se considera como parte del ambiente de ejecución que se analiza en e1 pítnlo siguiente. En este capítulo nos enfocamos en cálculos típicos que se presentan antes de la generacibn de c6digo y tlespuCs del . un v rificador de tipo es una parte importante del análisis seinántico. dependiendo del le guaje y las propiedades de la variable misma. < . y un compilador LISP debe nerar código para calcular tipos y realizar la verificación de tipos durante la ejecu del programa. en FORTRAN77 toda variables se asignan de manera estática. tras que los atributos que sólo se pueden fijar durante la ejecución son dinámicos.

y las gramáticas con atributos pueden escribirse de manera separada para cada conjunto. 3.Yl X2 . las gramáticas con atributos se escriben en forma tabular. están relacionados con los valores de los atributos de los otros sínibolos en la regla. de cada símbolo gramatical X.rs iiris . J. . apareciera más de una vez en la regla gramatical. . y u es un atributo asociado con X . de :itrihutri.~delanle. Veremos esto con más detalle en ta siguiente sección. así que los atributos pueden separarse en pequeños conjuntos independientes de atributos interdependientes. .ibez:ido "reglas sernánticas" en estas tablas en ver de "ecnaciones de :~trihtito" tener cn ciientti tina intcrpretnción rnds general de las regla scm. . entonces cada ocurrencia tendría que distinguirse de las otras mediante una subindización adecuada. 6. El análisis de atributos que se puede aplica. 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. La semántica dirigida por sintaxis podría fácilmente ser llamada sintaxis dirigida por scrnáiitira. Si el mismo símbolo X. nk. entonces escribirnos X.a.. Una gramática con atributos para los atributos u l .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). directamente a la generaciiín de código se analizará en el capítulo 8. . En la práctica Ias funciones A. Por otro lado.intic. con cada regla gramatical enumerada con el conjunto de ecuaciones de atributo. Dada una colección de atributos a. taxis implica que para cada regla gratnatical XO . de manera que se puedan diferenciar los valores de atributo de diferentes ocurrencias. mientras que las otras Xi son símbolos arbitrarios). X. (donde Xo es un no terminal. es raro que los atributos dependan de un gran número de otros atributos. los valores de los atributos X. Siempre usarenios el cnc. . En realidad. el lector (~rictle consider:irl:is idéoricas.u para el valor de u asociado con X. Por ahora.Atrtbutos y gramáticas con atributos 26 1 semántica durante el análisis sintáctico). . o reglas semánticas asociadas con esa regla de la manera siguiente:' 2. .. Por lo general. En esta generalidad las gramáticas con atributos pueden parecer muy complejas. . 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. para .. el principio de la semántica dirigida por sin.' Si X es un símbolo gramatical. .uk es la coleccicín de todas esas ecuaciones para todas las reglas gramaticales del lenguaje. 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). por lo regular son bastante simples.1. En nn irahajo posterior veremos las reglas semánticas como algo mis general que ccoacioms . 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.

y su valor es el valor de este dígito. L ecuación de atributo que expresa este hecho es 4 mítnero.CAP. 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 . Advierta que las dos ocurrencias de rtiírnrro en la regla gramatical deben distinguirse. 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. ~jemplo 6. A por ejemplo. Los disiinmiremos titilizmdo subíndices. v a l O. pitesto que el 11N17iero la a derecha tendrá un valor diferente del correspondiente al rirííwro en la izquierda.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. número + dígito entonces el número contiene precisamente un dígito. y asociamos e ecnación con la regla disito -+ O. val = digiro. cada número tiene un valor basado en el díg qne contiene. y la regla gramatical la escribiremos con10 se muestra a -. vid 5 Si un número contiene más de un dígito. c«ritinnacicín: . Adeniás. al cual le asignamos el nombre v Ccrrla dígito tiene un valor que es directamente calculable del dígito real que representa. Si tin número se deriva utilizando la regla. 6 1 ANALISIS IEMANTICO Reglas sernánticas Eciiaciones de atributo asociadas Regla gramatical Regla I Kegla n Ecuaciories de iitrihttto asociadas Continuamos directamente con varios ejemplos.

Considere el uso de la regla gramatical numerol + rnímero2 di@ en el primer paso de esa derivación. 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.Atributos y gramaticas ron atributos 263 Ahora considere un número como 34. el árbol de análisis gramatical para el número 345 se da en la figura 6. del dígito uti1ir.viz1 = O significa que el dígito tiene el valor ituménco 0. mientras que clígito. El no terminal ntírnero2 corresponde a1 dígito 3.vril = . En otras pirlabras. 34).1 como en la figura 6. o contenido setnántico. Los valores de cada tino son 3 y 4: respwtivarnente.mclo diferentes fuentes tipográficas. debemos ntultiplicar el valor de n~irnero~ 10 y agregar cl valor de dígilo: por 34 = 3 * 10 + 4. Tdbla 6.1.1 Regla gramatical Reglas semánticas nrirnerot -+ rnímerot .1. en la regln gramatical &ito -+ O. mientras que díqiro corresponde 211 dígito 4 . como vereinos en la sección s i g ~ i e n t e . Por ejemplo. l pusimos énfasis en la diferencia entre la rcprcsentacih sintictica de un dígito y el valor. ~ Tanto en la tabla 6. desplü~amos 3 un lugar decimal hacia la izquierda y el sumamos 4. 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.1 i: Gramatira con atributos para el ejemplo 6. 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. el dígito O es un token 0 carácter. Por ejemplo. Para obtener el valor de n~írnefo~ (es decir. El significado de las ecuaciones de atributo para una cadena particular pueden visualizarse utilizando el árbol de análisis gramatical para la cadena. : . En esta figura el cálculo correspondiente a la ecuación de atributo apropiada se muestra debajo de cada nodo iiiterior.

+ tem .rp.val = expz .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.1 / \ \ dígifo 4 (w1=3 * 1 0 + 4 = 3 4 ) d(&~ ( v d = 5) 5 niimrro (id // = 3) l (val = 4 ) dtgito (val = 3 ) 3 1 1 1 Ejemplo 6. val factor. Observe. + exp.t e m ex/? -+ term terin.tem. Tabla 6. .xp.UP.1 Árbol stntactico que muestra ~álculosde atr~butopan el ejemplo 6. El atributo principal de una e.va1 = tem.rp (o te factor) es su valor numérico. y la operación de adición o suma aritmética + que se realiza en la ecuación e.vul l . '4 $ e.vd = t r m .VLZI + trrnz.2 Regla gramatical Reglas sern&nticas expl -t expz + tenn exp. .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. la diferencia entre el símbolo sintáctico + (un token) en la regla gramatical :! *.va1 = exp. por ejemplo.. .2.rp. .rpip. -+ expz . el cual escribimos como val. val = número.val r.2 Gramática ton atributos pan el ejemplo 6. .val . Las ecuaciones de atributo el atributo val se proporcionan en la tabla 6.+ termz "factor term -t fuct(~r factor -r ( e .val * factor.~ u = rxp2 .lerm. y ) factor -+ n ú m e r o exp.val factor.vul r e m l .va1 trm.sal 4.val = exp2 .val e.2 Considere la gramática siguiente para expresiones aritméticas enteras simples: esp 4 exp + tenn / exp . 1 6 ANALBIS SEMANTICO ruímero (val= 34 * 10 + 5 = 345) iiiímem Figura 6.va1 = factor.

. S. podemos expresar la semántica de su valor en su irbol de análisis gramatical como en la figura 6. Hacemos esto construyendo una gramática con atributos para un atributo rftype (utilizamos e1 nombre dtype para distinguir el atributo del no terminal typr). como en el ejemplo 6. dada la expresión ( 3 4 . si queremos que este valor esté explícito en la gramática con atributos. 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. mediante el análisis Iéxico).v (vol = 34 3 ( número (val = 42) ) 1 A\' = 31) e. debemos agregar reglas gramaticales y ecuaciones de atributo ri la gramática coi1 atributos (por ejemplo. De manera alternativa. Coino veremos en la sección siguiente.3 ) * 4 2 que muestra cálculor del atributo val para la gramática con atributos del ejemplo 6. las ecuaciones del ejemplo 6.1). esto implica que nÚmero.1. Harenios las observaciones siguientes respecto a las ecuaciones de atributo de esa figura. Considere la siguiente gramática simple para declaraciones de variable en una sintaxis tipo C: decl-.Atr~butosy gramáticas tan atributos 265 Advierta también que no hay ecuación con número.va1 en el lado izquierdo.-La gramática con atributos para dtype se da en la tabla 6. Por ejemplo.3. type var-list type 4 int / f l o a t var-list -+ id.tp (val = 34) 11 term (VUI= 3) I term (vol = 34) I faclor (val = 3 ) I factor (val = 34) I número ( w l = 3) I número (val = 34) Ejemplo 63 .2 term (val = 31 * 4 2 = 1302) term l / (vul= 31) \ /¿actor (vul = 42) 1 factor (val = 3 1 ) e. 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. f~gura6 2 A&ol de anillrts gramatical para ( 3 4 .2.va1 debe calcularse antes de cualquier análisis semjritico que utilice esta gmniática con atributos (por ejemplo.3 i *42.

st (dlype = r m l ) f loat 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.3 hrbol de analisis gramatical para la cadena f loat x .El no terminal type tiene un cltype dado por el token que re de senta. r e d ) que correspon a los tokens i n t y f loat. sólo había un atributo.4 Considere una modificaciRn de la gramática numérica del ejemplo 6. Como antes.' ' \ r?p (~ltypr real) = var-li. Este iltype corresponde al d t ~ p e la vur-list entera. Las gramática atributos pueden involucrar varios atributos interdependientes. una decl no necesita tener un dtype: no es necesario especificar el v de un atributo para todos los símbolos gramaticales.3 Gramática ton atributos para el ejemplo 6. donde los números pueden ser octales o decimales. Se proporciona. Tabla 6.CAP.un ejemplo en la figura 6.1 decl . 6 1 ANALISIS SEMAUTICO En primer lugar. En realidad. El ejemplo siguiente e "situación simple donde existen atributos interdependientes. por la ecuación asociada co regla gramatical para <lecl. los valores de dtype son del conjunto [integer. Advierta que no hay ecuación que involucre el & p e del no ter r l d . por las ecuaci asociadas con vur-list. Ejemplo 6.Cada i d en la lista tiene este mismo tlope.<ftype -+ rype var-lisr figura 6. y que muestra el atributo dtype como re especifica mediante la gramática ton atributos de la tabla 6.3 Regla gramatical decl Reglas sernánticas var-listdtype = gpe. Entonces tenemos la gramática signiente: . las ecuaciones de atributo se pueden exhibir en un árbol de análisis gra tical. Imaginemos que esto se indica mediante un sufijo de un carácter o (para octal) o d (para decimal).1.3.

vul también debe ser error. Tabla 64 Gramática con atributos el ejemplo 6. Además.val = if dígitaval = error nr numz .se num.l~ase dígito.huse dígito.hu. vul = I iiurn-hose -t nu~n riirhnse curbase -+ o <.val dígito.vul * num. 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. es necesario un nuevo valor error p x a tales casos. que se utiliza para calcular el atributo val.junto con 10s valores de atributo calculados de acuerdo con la gramática con atributos de la tabla 6. pero no se le puede asignar ningún valor.vrrl nurni . .~~irl dado por la fórmula n~irn~.Atrtbutos y gramaticas con atributos 267 En este caso rmm y dígito requieren un nuevo atributo buse.re = 1 O riiiin l . riurnz digiro - riiim -t dígito dígifo -t O digiro -+ 1 rlí& +9 if dígif<t. En primer lugar. De este modo.~rrbcrse d -+ nurn.hase + rlígiio.vnl.bu.buse = ririrni . La gramática con atributos para hase y vril se proporciona en la tabla 6.hase = nwnl .w l = if dígitavul = error or riuin2 .val = error then error else num2 . está * Para coticluir con este ejemplo mostramos de nueva cuenta los cálculos de atributo en un irbol de .hme = curbase.4. Por ejemplo.4 ofrece un árbol de análisis gramatical para el ~iúinero 3450.ito.vu1 correspondiente a la regla gramatical numI -t num2 dígito expresa e1 hecho de que si cualquiera de nctin2.vul = if d(qito.buve + dígiro.b~ise+ cli.4 Regla gramatical Reglas sernanticas iiiitn-hure i u1 = nirni 1111 nuni. la cadena 1890 es sintácticamente correcta de acuerdo con la BNF anterior.val = ú(qito.vcil o rli~ito.base = r111m.buse carhnse.vu1 error entonces numi.vnl nurni.Por ejeniplo. 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. y scílo si es no es ése el caso rinrnl. .bu.base = 8 then error eise 9 Deberían observarse dos nuevas características en esta gramática con atributos.se= 8 then error else 8 dígiro. v d = error then error else mimz .vul = O dígiro.buse = 8 c«rbuse. La figura 6.4.inálisis gramatical. la ecuación num . 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.val * numl .

) .4 6. podemos d:ir la siguiente dctlnición de nunriul corito c6digo en C: int numval(char D) { return (int)D . quere convertir las ecuaciones de atributo en código de trabajo en un analizador semántico.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.1 ~ r b o lde analisis gramatical que muestra cálculos de atributo para el ejemplo 6. 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.1. lógicas algunas otras clases de expresiones. 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.Figura 6. 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.íint)'O'. como veremos en breve. En cambio. 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. ya que.de programación real. este libro utilizamos un metalenguaje que está limitado a expresiones aritméticas. junto con una expresión if-then-else. Por ejemplo. y ocasionalmen te una expresión case o switch. Por ejemp en las gramáticas para números hemos estado escribiendo ecuaciones de atributo para ca una de las opciones de rlígito.

Ia expresión isis gramatical y atributos val se mostraron en la figura 6. de 1a.3 ) * 4 2 que muestra los cálculor de atributo de val para la gramitica con atributos de la tabla 6. . de manera que se pueda expresar la semintica definida mediante una gramática con atributos.rp3 .xp.val = .5 / (val = 34) (sal = 3) - Ejemplo 6. pero más simple.2 (página 264). Regla gramatical exppf -+ exp2 + expl e. sin implicar ninguna ambigüedad en los atributos resultantes: Por ejemplo.val = exp.exp 1 exp e a p / ( exp ) / número .3 (compare esta con la tabla 6.~aaf número .val = exp.y la gramática con atributos puede basarse libremente en construcciones ambiguas. pero ambigua: . 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.tapa.val = expz .val .6.5 Arbol sintactico abstracto (val = 31 * 42 = 1302) (val = 34 para ( 3 4 . la gra. En esta gramática con atributos utilizamos dos funciones auxiliares rnkOpNode y mkillumNotle. mática'de expresión aritmdtica del ejemplo 6. Por ejemplo. que se .2 o la tabla 6.5 Dada la gramática para expresiones aritméticas enteras simples del ejemplo 6. 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. .val exp.val * exp3 . . Cuando se utiliza esta gramática el atributo vdl se puede definir mediante la tabla. -+ expz * exp3 ex& -+ ( e % exp -+ aúmaro ~ e ~ semánticas f& expt . exp -+ exp + e x p / exp .gramáticaariginal.ip2 exp3 exp.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.val exp. toda ambigüedad se habrá abordado en esta e. Un árbol sintáctico abstracto debe tener siempre la estructura suficiente.Auibuter y gramáticar con atributos 269 Una shptificación adicional que puede ser útil al especificar gramática con atributos es utilizar una. e x p ~val t exp3 .e.2).val exppc . y La función tnkiVurnNocfe toma u n parámetro (un valor numérico) y construye u n nodo hoja .va¡.fomaambigua. . puesto que se supone queel analizador sintáctico ya fue construido. cuyos hijos son el segundo y tercer parámctros.2. Figura 6. podemos definir un árbol sintáctico abstracto para expresiones mediante la gramática con atributos dada en la tabla 6.2 tiene la siguiente forma más sencilla. -9 e. De hecho. muestra en la tabla 6. exp.

6 escribimos el valor numérico c número.6 con la construcciún descen te recursiva del irbol sintáctico de TlNY en el apéndice B. 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.6 Gramática con atributos para árboles sintácticos abstractos o expresiones aritméticas enteras simples Regla gramatical Reglas semanticas expi -r expz erp .&e = mkOpNoiie(*. (Compare las ecuaciones de la tabla 6.tree = mkNumNodefnúmero.trrm etp -+ term ler1nl -+ f e m 2 *fili..ai.tree) exptree = termmre ferm.tree.pr)ndicnte . 6 1 ANALISIS SEHANTICO representando un número con ese valor. que define de manera única los a butos dados? La respuesta simple es que hasta ahora no podemos. term2 .CAP.. De hecho.cp2 - + term . term. Fundamentalmente. rerm.itributos.rrre) a p i . Ésta es una cuestió similar a la de determinar si una gramática es ambigua.ror terrn +fuctor factor -+ ( exp ) .tree = exp. 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. De este modo.tree. o su representación de cadena.tree factor. 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. expz .) Tabla 6. es decir. En la tabla 6. El problema de iniplementar un :ilgoritnio corre.f'ocror -+número erp E . De este ni do.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.tree. 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. erp2 . los valores de todos los atributos que aparecen en el lado derecho ya deben existir. donde las ecuaciones se pueden escribir en orden arbitrario sin afectar su validez. firctor.tree factor. Este requerimiento se puede pasar por alto en la especificaciitn de las gramáticas con .rree = fuctor. dependiendo la implementación.tree = mkOph%>rle(+.le-xvd para indicar que es construido por el analizador léxico.. En la práctica.Irre = mkOpN<ide(-. esto equivale a convertir las ecuaciones de atributo en reglas de cálculo. Para que esto tenga éxito.e. éste dria ser el valor numérico real del número. tree) term. .

vc11es .ci. ya que la representación gráfica distingue claramente las diferentes ocurrencias como nodos distintos. Ejemplo 6. Estas gráficas dirigidas se conocen como gráficas de deperidcncia.. Esta gráfica tiene un nodo etiquetado por cada atributo Xl.de atributo titiliz. en el lado derecho para el nodo Xi. existe sólo un na en la tabla 6. Hay sólo ~ i n . La regla gramatical número. y L primera tarea es hacer explícitas las liinit.vcil= clígito. de cada símbolo en la regla gramatical. -+ rihnero2 digito tiene la ecuació~i atributo asociada simple de izlhzero. de modo que .). con la gramática con atributos como se proporcioatrihuto.1.q respecto a X. para cada sí~nbolo nodo en cada gráfica de dependencia.2..intes a de orden utilizando gráficas tlirigitlas para representarlas.vn1 (En futuras gráficas de dependencia omitiremos los subíndices para símbolos repetidos.6 Considere la gramática del ejemplo 6.) De manera semejante.id«s en cada cálciilo estén disponibles cuando éste se realice.red = número2 .1 Gráficas de dependencia y orden de evaluación Dada una gramática con atributos. l . y para cada ecuación de atributo asociada con la regla graniatical hay un borde desde cada nodo X. Las mismas ecuaciones de atrihuto indican las limitantes de orden en el cálculo de los atributos. Dada una cadena legal en el lenguaje generado por la gramática libre de contexto. la gráfica de dependencia para la regla gratnatical rtcímero -+ dígito con la ecuacih de atributo r~rítnero. 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. los nodos asociados con cada síinbolo X son trzados en u11 grupo.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.czj (q~ie expresa la dependencia de Xi. 6.. Al dibujar la gráfica de dependencia para cada regla gramatical o cadena. w l .ci. .val * 10 La gráfica de dependencia para esta regla gramatical es + clígito.. de manera que las dependencias se puedan visualizar como estructuras alrededor de un árbol de análisis gramatical. cada regla gramatical tiene una gráfica de dependencia asociada.u. correspondiente a su atributo val.

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). En este ejemplo la regla gramatical vur-lisr.1): Ejemplo 6. como &el no está involncrada de manera directa en la gráfica de dependencia. la cadena 345 tiene la siguiente gráfica de dependencia correspondien su árbol de análisis gramatical (véase la figura 6. la regla decl i type wr-list con la ecuación asociada vnr-1ist.dtype 1 i d . no está conipletamente claro cuál regla gramatical tiene su gráfica asociada a ella. a menudo dibujarnos la griifica de dependencia sribrepucsta sobre un segrncnto de &bol de análisis gramatical correspondiente a la regla jrarnatical. vur-11 tiene las dos ecuaciones de atributo asociadas y la gráfica de dependencia De manera similar. Por esta razón (y algunas otras razones que comentaremos posteriormente).&pe Las dos reglas cype -+ i n t y type -+ íloat tienen gráficas de dependencia trivides.3 con la gramática con atributos para el atrib tltype que se da en la tabla 6. Finalmente. De este modo.vu1 se calcula directamente del lado derec la regla.7 Considere la gramática del ejemplo 6. ya que dígito.ilt)ye tiene la gráfica de dependencia En este caso.WP. la regla gramatical vcir-list + i d tiene la gráfica de dependencia var-1ist.3. la gráfica (fe &pendencia anterior se p c d e dibujar como .dtype = tvpe. Finalmente. + i d .

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. cuyo árbol de aiiálisis gramatical se muestra en la figura 6. Así.s~: . y es float dype id (X) r ~Irype vnr-li~t úfype 1 id (Y) 1 1 Considere la gramática de números de base del ejerriplo 6.4. Comenzamos con la gráfica de dependencia para la regla gramaticül rrutrr-btrse -+ rrirm LY~~/xJ. Advierta .4 con la gramática con atributos para los atributos buse y vol como se dan en la tabla 6. 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.Algoritmos para dltulo de atributos y esto hace más clara la regla gramatical a la que está asociada la denendencia. la primera gráfica de dependencia en esle ejemplo tamhih se puede escribir como a Finalmente.4. la grifica de dependencia para L cüdenü f loat x.

S610 resta dibujar la gráfica de dependencia para la cadena 3450. I . Esto se h ce en la figura 6.. a saber.ial depende de dígito. y iiutri.CAP.hase= numl .buse (es parte de la prueba en expresión if).9: i Esta gráfica expresa la dependencia creada por la ecuación digito. base 1 dígito 1 1 1 val O hose ririm l t i.hase = carbuse.iil - hase díi'ito l 1 1 r. . E~ta gráfica erpre\a las dependencias de las tres ecuaciones de atributo nurn I .base dígito.d 5 4 .8) /S:C nrirn-base val .val * nrtml .base. A continuaci6n dibu&imos la gráfica de dependencia correspondiente a la regla gram tical trwn .qito. que dígito.num dígito: . 6 1 ANÁLISIS IEMANTICO Esta gráfica expresa las dependencias de las dos ecuaciones asociadas rturn-huse. vul n m z . v d = error then error else num2 .buse + La gráfica de dependencia para la regla gramatical num -+ dígito es similar: huse num vul Finalmente.buse ~lí.base = then error else 9..vctl = if dígito. Figura 6 6 Gráfica de dependencia para la cadena 3450 (e'jemplo 6.vul = if ciígito. c~rbuse2se . .6. .vu1 = error or mm2 . dibujarnos la gráfica de dependencia para la regla gramatical tlígito .vul = num.base = nuin. .

'Tales cálculos se realizan con frecuencia mediante los :inrilizadores léxico 0 sintáctico.7. 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. cualquier algoritmo debe calcular el atributo en cada nodo de la gráfica de dependencia cirires tie intentar catcular cualquier atrihuto sucesor. Tales gráficas se conocen como gráficas acíclieas dirigidas.Algoritmos para cálculo de atributos 275 Supongamos &ora que deseamos determinar un algoritn~o calcula los citrihutos de que una gramática con atributos utilizando las ecuaciones de atributo como reglas para el cálculo.~ gura 6. los nodos 1. En la figura 6. En la figura 6.7. Todos esos valores raíz necesitan ser calciilahlei antes del cálculo de conlquier otro valor de atributo.6. por sus siglas en inglés. 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. Un orden del recorrido de la gráfica de dependencia que obedece esta restricción se denomina cIasificaciFn topológica. entonces se deben calcular utilizrindo la información que está directamente disponible. por ejemplo. En realidad. Esta información se encuentra a rnenudo en forma de tokens que son hijos de los correspondientes nodos de %bol de artálisis gramatical.6). En la fi.7 numeramos los nodos de la gráfica (y eliminamos el árbol de análisis gramatical subyacente para facilitrir la visualización). La gráfica de dependencia de la figura 6. . 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). 9 y 12 son todos raíces de la g r á f i ~ aLos valores de atributo en estos nodos no dependen de ningún otro atributo. Una clasificación topológica está dada por el orden de nodo en el que los nodos están numerados.6 es una DAG. e1 valor de atributo en el nodo 6 es 3.. o DAG. De este modo. 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. Dada una cadena particular de tokens para traducirse.

7). la gráfica de dependenci para digito -+ 9 es Así.8 y las clasificaciones pológicas de la gráfica de dependencia analizada en el ejemplo 6. Ejemplo 6. para evitarlo debería pro por adelantado una gramática con ahbutos. en la figura 6. evaluara prinie- . itigatnos. La alternativa al método anterior para evaluación de atributos. 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. este método se conoce en ocasiones c método de &bol de análisis yametical. el método se conoce como método basado en reglas. pero es un algoritmo de tiempo exponencial. A cuando los nodos 6. por consiguiente. 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. y el nodo 12 puede haber dependido del nodo I l . aunque este método puede determinar si una gráfica de dependenc acíclica en tiempo de compilación. tenemos la complejidad gada requerida por L constmcción en tiempo de compilación de la gráfica de depende a En segundo lugar. Por ejemplo.7 son raíces de la DAG. La complejidad de este método es un argumento convincente. Existen varios problema con este método.En rralidad.9 (vkase la figura 6. ya que depende de un a lisis de las ecuaciones de atributo. A menudo se les denomina gramáticas con atributos completamente circulares.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. el nodo 9 puede haber dependido del nodo 8. Existe un algoritmo para hacer esto (véa sección de notas y referencias). ya que es casi seguro que este represen un error en la gramática con atributos original. Por lo tanto. en un método basado en r esto no es posible. es que el escritor de compiladores analice la gramática con atri tos y fije un orden para L evaluación de atributos en tiempo de constmcción de compil a Aunque este método todavía utiliza el &bol de análisis gramatical como guía para evalu de atributos. t podría ocurrir al principio de una clasificaci<ín topológica. Natural este algoritmo sólo necesita ejecutarse una vez. por lo general es inadecuado esperar hasta el mom de compilaci6n para descuhrir un círculo vicioso. Dicho método es capaz de evaluar los atri en cualquier gramttica con atributos que sea no circular. 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.10 Considere nuevamente las gráficas de dependencia del ejemplo 6. es decir.7 el nodo 6 puede haber dependido del nodo S. En primer lugar. Continuaremos con una exposición de los algoritmos basados en reglas p esta clase de gramáticas con atributos después del ejemplo siguiente. o reglas semánticas. por la que opta prác mente todo compilador. y. si e1 token correspondiente es 8 o 9. una gramática con butos para la cual toda gráfica de dependencia posible es acíciica. 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 razón es que cualquier val puede depender de la base de su nod gito asociado. kin orden de evaluaci6n que.9 y 12 de la figura 6. pero en la práctica todas las gramáticas con atributos razonables tien esta propiedad.

calcule todos los atributos sintetizados de T.írbolcs de análisis grinnatical.stEvul( C ). como el atributo vtil de las expresiones aritméticas enteras simples en el ejemplo 6. que se define a continuación. 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.7.2 para expresiones aritméticas sirnpies. página 271). los valores de atributo de una gramática con atributos S se pueden calcular median. página 264. .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. (S 6. Esto se puede expresar mediante el siguiente pseudocódigo para un evalnador de atributo postorden recursivo: procedure PastEva~ T: treeiiocle ).' gramatical.. Un atributo es sintetizado si todas sus dependencias apuntan de hijo a padre en el árbol de ana ' t s ~ s '1 . Diferentes clases de recorridos varian en potencia en términos de las clases de dependencias de atributo que se pueden manejar.2. El atributo val de números en el ejemplo 6. X.. Dada la estructura siguiente para un árbol siniiclico 4 tal corno cl de la i'igt~ra 5. * . puesto que violaría el orden pira otros . t. La clase más simple de dependencia a manejar es la de los atributos sintetizados. página 261)) 6 . . del árbol.1 1 <:onsiileremos la gratnática con atributos del ejcriiplo 6. . Para estudiar las diferencias primero debemos clasificar los atributos mediante las clases de dependencias que exhiben. dada una regla gramatical A + XIX2 .1 está sintetizado (véanse las gráficas de dependencia en el ejemplo 6. con el atributo sintetizado vid. un recorrido ascendente. g: Definición 9.Algoñtmos para calculo de atubutos 277 ro el nodo 12 (véase el ejemplo 6. no es un orden vilido para un algoritmo basado en reglas.91. end. aunque es correcto para el árbol particular de la ligura 6. De manerd equivalente. . un atributo u es sintetizado si. Dado que un árbol de análisis gramatical o árbol sintactico se ha construido mediante un analizador sintictico. Ya vimos varios ejemplos de atributos sintetizados y gramáticas con atributos S. o postorden. Ejemplo 6.. ( hegin for cada hijo C de T do Po.2.6.

typedef STreeNode *SyntaxTree. struct streenode *lchild. a . . case Minus: t->val = t->lchild->val * t->rchild->val. > STreeNode. esto es necesario \ i los bordes del irbol siiirácticcr d o apuntan ile padre a hijo (de este tnoclo un hijo no puede . Definición Un atributo no sintetilado \e denomina heredado. case Minus: t->val = t->lchild->val t->rchild->val. break. de dependencia se preserttrtn en el ejemplo 6.7 para el atributo cbpe L r a h cle que 'ises . postEval(t-zrchild). if (t->kind = OpKind) ( postEval(t-7lchild). break. 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. el pseudocódigo PostEval se traduciría en el ccídigo C de la figura 6.8 Código C para el evaluador de atributo postorden para el ejemplo 6. En efec&.. 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. struct streenode ExpKind kind.1 1 void postEval(SyntaxTree t) { int tenp.*rchild.9(a) y ib) ilustran las dos categorías hásicas de dependencia de atributos heredados. OgKind og. switch (t->op) { case Plus: t->val = t->lchild->val + t->rchild->val. enum {OpKind.Minus.typedef typedef typedef f enum (Pius. Las figuras 6. Cada una de estas c ~ . 1 / * end switch * / 1 / * end if * / / * end postEva1 * / - Por supuesto.Times) OpKind.unhas se clasifiquen corno herellatlas es que en algorilmos para calcular atribut«s heredados.3 (página 265) y cl atributo base del ejemplo 6.4 (página 266). break.8 para un recomd izquierda a derecha.ConstKind} ExpKind. Figura 6. no todos los atributos son sintetizados. Ejemplos de atributos heredados que ya hemos visto incluyen al atributo dtwe del ejernplo 6. int val.

esto se puede representar mediante el siguiente pseudocódigo: procedure PreEv<d( T: tremode ). A diferencia de los atributos sintetizados.corno se representa en la figura h.-li. ~<ri.Algoritmos para calculo de atributos 279 tener acceso a su padre 0 hermanos directamente). Sup«ng.12 Considere la gramática del ejemplo 6. Por otra p. El orden en el que se visita cada hijo C de Ten e l pseudocódigo precedente debe.%c).tvpe tur-li. el orden en e l que se calculan los atrihutos heredados de los hijos es importante.-Iist 4 id. la que repetimos aquí para poder hacer referencia a ella con facilidxl: tlccl .irnos primero que un árbol de anilisis gramatical se ha construi<lo explicit:imente a partir de la gramática. (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.st . pigina 266). y Ejemplo 6. adherirse a cualquier requerimiento de estas dependencias. entonces la herencia entre hermanos puede coniinuar directarnerite a lo largo de una cadena de herr~iario. Los atributos here&ados se pueden calcular nlediante un recorrido preorden. porque los atributos heredados pueden tener dependencias entre los atributos de los hijos. s i algunas estructuras en un irbol sintáctico se implementan a través de apuntadores hermanos.3. int / f loat i:cii. begin for cudu hijo C de T do cczlcule todos los cztrihutos heredados ú e C.3 que iiene e l atributo heredado cliy)e y cuyas gráficas de dependencia se proporcionan en e l ejemplo 6. En los dos ejemplos siguientes demostrareinos esto utilizando los atrihutos heredados clt)~pe base de 10s ejemplos anteriores. o combinado preordenlenorden del árbol de análisis gramatical o del árbol sintáctica. por lo tanto.rrte.st id 1 ypr - . PrcEvrzl( C ). end. página 272 (véase la gramática con atributos en la tabla 6.7. De manera esquemática.

-. y numeramos los nodos e orden en e1 cual se calcula drype de acuerdo con el pseudocódigo anterior.10 Arbol de anilisis gramatical que muestra orden de recorrido pan el ejemplo 6. / / . EvnlType ( var-list hija de T ). -. m'<: Figura 6. 1 &pe uar-list @ dope 1 i id @ (Yf Para proporcionar una forma completamente concreta a este ejemplo. drvpe I I I I . . '--. Entonces una cadese na de declaración tal como f loat x. éste es un p ceso preorden. 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.10) . En la figura 6. dependiendo de la cl diferente de nodo que se está procesando. -. un no wrr-list asigna drype a sus hijos antes de hacer cualquier llamada recursiva. . EvalType ( tercer hijo de T ).dtype := real.diype at-primer hijo de T.10 mostramos el árbol de analisis gramatical para la cadena f loat y junto con las gráficas de dependencia para el atributo drype. end EvalType.12 .' y . convertimos el pseudocódigo anterior a código C real.drype := intcger else T. Por otra parte. Asignar dvpe de tipo hijo de T a vur-list hija de T. en vez de utilizar un 6rbol de anrilisis gramatical explícito. en el que ~ur-list representa mediante una lista de hermanos de nodos i d . if hijo (le T = int then T. y tendría el árbol sintáctico (compare esto con la figura 6. d e uar-list@ 8 ' _ decl -. éste es un proceso en orden.. begin case clasenodo de T of decl: EvalType ( tipo hijo de T ). suponemos que se ha construido un árbol sintáctico. var-list: asignar T. 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 ). end case. @ float &pe id ix) . Advierta cómo operaciones preorden y en orden están mezcladas. También.CAP. C . Por ejemplo. if tercer hijo de T no es ni1 then asignar Tdtype u1 tercer hijo.

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. typedef struct treeNode ( nodekind kind. break. evalType(t->rchild). Observe que en este árbol ya incluimos el dtype del nodo type.id) nodekind. case id: if (t->sibling l = NULL) { t->aibling->dtype= t->dtype. que funciona enteramente al nivel del nodo raíz (decl): . struct treeNode lchild.realf typekind. / * 8610 para nodos id */ * SyntaxTree. * rchild. typedef enum (integer. 1 break. ) / * end switch * / / * end evalType * / > Este ccítiigo se puede simplificar al siguiente procedimiento no recursivo.type. * aibling. evalType(t->sibling). La estructura del jrbol sintáctico la proporcionan las siguientes declaraciones en C: typedef enum {decl. / * para nodos id y type * / char * name. luego el nodo x y finalmente el nodo y ) . suponemos que esto se ha calculado previamente durante el análisis sintáctico. typekind dtype.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.

carbase 4 o d tium 1 num digito cligito .282 CAP. P . Así.8. 1 1 / * end if * / ). El pseudocódigo se presenta a continuación (vd la gramática con atributos en la tabla 6. / * end evalType * / Ejemplo 6.4 (página ?66). u atributo sintetizado val y cl atributo heredado base.vd := T. pígina 273). if hijo derecho de T no es ni1 then nsignur 7 b m e a 1 1 huse del hijo derecho (le T. if wlores de hijos izquierdo y derecho # error then T. while (0->sibling ! = MILL) ( p->sibling->dtype = p->dtype. / / dígito-. página 267). En primer lugar. del cual depende val.> d t m e = t->lchild->dtype. que tiene el atributo heredado base gráficas de dependencia están dadas en el ejemplo 6. asignur base del hijo derecho de T a la base del hijo izquierdo.13 Considere la gramática del ejemplo 6. En segundo L el atributo base es heredado del hijo derecho al hijo izquierdo de un num-base (es de desde carbase hasta num). procedure EvalWithBuse ( T : treenode ). Repetiremos la gr tica de ese ejemplo aquí: nirm-base 1 num curbuse . p = p->sibling. 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). tz11m: asignar T. 6 1 ANALISIS SEMANTICO void evalTne ( SyntaxTree t ) ( if (t->kind -= decl) f SyntaxTree p = t->rchild. EvulWi?hBase ( hijo izquierdo de T j. Procederemos a dar el pseudocód para un procedimiento EvulWithBase que calcula tanto base como val.base a la buse del hijo izquieráo de T.0/1/2/3~4/5/6/7/8/9 Esta gramfitica tiene dos nuevas características.vul. EvairlCVithBase ( hijo derecho de T ) . asignar valor de hijo izquierdo de T a T.bnse*(vulor de hijo izquier(1o) + w l o r de hijo rlrrt. en este caso debemos evaluar los hijos de un nztm-base derecha a izquierda en lugar de izquierda a derecha. EvulWibhBase ( hijo derecho de T ). existen dos atributos.4.clzo else % 1x11 : error. - . hegin case clusenodo de T of num-base: EvalWithBase ( hijo derecho de T ).

que se indican por medio del token num.hase = S and &jo de T = 8 or 9 then T. Si no. Tarribién tiene (los ver.trrhose: if hijo de T := o then l h a s e :-. Comhiizc4Eial ( C ). las c~mies se indican iiietiiürite el token num y números de punto flotante. de división.num.sii~iics ií~i. 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.:d.h<ise:= 10. la división con frecuencia se llama opci-acih div. De. rnlccdr roilor los ntnbutos rrnrt. como lo rnuestra el siguiente ejemplo.tr:~rdoc de T. 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.icii'w requiere eupri.~cI.iortes de núrneros: números enteros cvmpvestos de secuencias de dígitos. tlQit0: if T..Algontmor para cálculo de atributos c. Siipong. cntonccs 5 1 4 time u n b:lIor tic l. y cl valor i!e 5 / 4 cs 5 tfiv 4 = 1 . La idea de esla gramática ei que las operaciones se pueden iorcrpretar de manera diferente.wd := error else %val := rirtntvrd ( hijo de 7' ). end.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~~ulWithRnsecódigo en C. Ejemplo 6.se. en pxticulx. clepcrldiciido (le j i se permiten las í'rxxiones. 8 else T. a § En las gramjticas con atributos con combinaciones de atnbtítos sintetizados y heredados. 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.14 Considere la siguiente versión simple de una gramática de exprcsión: Esta gramática tiene una sola operación. si los atributos sintetizados dependen de los atributos heredados (además de otros ütrihtitos sintetimios).is .?. La división. end Evc~lWithB<i. S i se !efierc a I U divisifin de punto Slotaiite. end case.imvs :hora qiic nrl Ienpijc de progrnii. dependiendo de si son operaciones de pu~ito flotante o esirict:imcnte entenis. pero los atributos heredados no dependen de ningún atributo sintetiza&. indica& por el toke~i/. es bastante difcrentc.

Hacemos esto aumentando la gramática con un sím inicial: . que depende del efype heredado. Tabla 6 1 Gramatica con atributos para el ejemplo 6. con dos valores int y que da el tipo de cada subexpresión y que depende de isFlour.rsFloat rxp2 . Esta regla no es la niisnia regla que la empleada en C. que indica si alguna parte de una exp tiene un valor de punto tlotante. Dejamos la descripción de estos pasos y los correspondientes cálculos atributo para la expresión 5/2/2. 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. S 4 exp Las ecuaciones de atributo se dan en la tabla 6.val = exp val exp. cl valor de 5 / 2 / 2 . el val c lado de cada subexpresión. y finalmente.exp i .3 % buto sintetizado isFloat mediante un recorrido postorden.isFlout = exp2 . Los atributos isFloat. Por ejemplo.d atributo heredado etype como el atributo sintetizado val en un recorrido combinado pre den/postorden. El segundo paso calcula tanto cly. .isFlout or exp.val /exp3 .0 para los ejercicios. En las ecuaciones para la regla gra e. También empleamos la 1 para la divisi6n to flotante y div para la división entera.val else Floarfnum. eiype y val en este ejemplo se pueden calcular mediante dos sos sobre el árbol de análisis gramatical o el árbol sintáctico.xp -+ un utilizamos Float (num.14 Regla gramatical Reglas sernánticas S .7.0 en C.val 7.isFloat = false exp. exp.CAP.val = i f eqetype = tnt then num .e@pe = rnt then exp2 . .e@pe = expl espe erp.val = num.:.num erp. rnientr el significado de 5/2/2 es Para describir estas semánticas se requieren tres a tos: un atributo booleano sintetizado isFloat.vul) para indicar una función que convierte el val tero num.val ) exp.val = ii'exp.25. num .val div exp. 31 I 1 9 .vul en un valor de punto flotante.rxpz / erp3 exp -+ num exp -t num. un atributo heredado efype.val e.isFloat = true exp. El primer paso calcula el a .e@pe = if exp.xp. 6 1 AHALISISSEHANTICO 5 / 2/ 2 O (suponiendo la asociatividad por la izquierda de la división) es 1. .25.val else expz . .isFloat thenfloat else int S. rtype = expl etype exp. . O es 1. 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). no 1.. .

temp ). digiro: if huse = 8 and hijo de T = 8 or 9 then return error else return riurnvcrl ( hijo de T ). num: ternp := EvcrlWithBase ( hijo irqctierdu de T. var ternI). end ease. .se*teinp + temp2 else return error. 'Tiene sentido convertir a base en un parámetro (como un atributo heredado) y a r'ul en un valor devuelto. error then return ba. begin case clusenodo de T of num-buse: rerrzp := EvulWithBase ( hijo derecho de T ) . if temp r f error and te1np2 J.3 Atributos como parametros y valores devueltos Con frecuencia al calcular atributos tiene sentido utilizar paránictros y valores de iiinción devueltos p r comunicar los valores de atributo en vez de alinacenarlos corno cnnipos en una aa estructura de registro de {íbol sintáctico. de hecho. no existe todavía estructura de datos en la cual pueda registrarse a sí mismo como un atributo. 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. En capítulos anteriores ya se presrntiuon varios ejemplos de esto. temp2: integer.13. buse: integer ): integer. end f~vtrlWithHtrse. else return temp. Ejemplo 6. if hijo derecho de T no es ni1 then tentp2 := Evul WithBuse ( hijo derecho de T. puede ser necesario emplear una estrnctura de registro o unión como un valor devuelto. En situaciones más complejas. 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. Ilustramos esto con un ejemplo. huse ).se del ejernplo 6.Algoritmos para calculo de atr~butos 285 6. El procedimiento modificado EvulWitftBuse queda conlo se presenta a continuación: function EvdWithBuse ( T: treenode. el árbol sintáctico como un atributo sintetizado debe él mismo ser calculado mediante un valor devuelto durante el análisis sintáctico. 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. En particular. como cuando debe ser devnelto más d e u n atributo sintetizado. o puede dividirse el procedimiento recursivo en varios procedimientos para cubrir los diferentes casos. De manera similar. 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. return EvalCl/itlzBuse ( hijo izquierdo de T. puesto que hasta que se haya c«nstrnid». [Jn procedimiento de recorrido recursivo simple que calcule atributos heredados en preorden y atributos sintetizados en postorden puede. Esto es p~uticulmente cierto si niuchos de los valores de atributo son los misinos o se utilizan scílo de manera temporal para calcular otros valores de atributo. curlmse: if hijo de T = o then return 8 else return 10. En este caso no tiene niucho sentido eniplear el espacio en el árbol sintáctico para almacenar valores de atributo en cada nodo. huse ). De manera semejante.15 Considere el procedimiento recursivo EvulWithBu.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. EvalBuse(hijo derecho de 7') ). Por lo tanto. (* sólo llanziido en nodo cahase *) hegin if hijo de T = o then return 8 else return 10. if temp i error and femp2 J: error then : return base*temp + remp2 else return error. rfígito: if base = 8 and hijo de T = 8 or 9 then retnrn error else return nnrnval( hijo de T ). (* sólo llanzuilo en nodo rciiz *) begin return EvalNum ( hijo izquierdo de T. y lo cual es ignorado posteriorme Por ejemplo. 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. end EvalBase.EnP. 6 1 anAua nnhwrico Naturalmente.senodo de T of nunz: ternp := EvalCVithBuse ( hijo izquierdo de T. esto funciona sólo porque el aiributo base y el atributo vul tienen el mi tipo de datos integer o "entero". end EvdNum. remp2: integer. con un valor de base de prueba O. base ). El pseudoc6digo aparecería eotonces como se muestr enseguida: function EvalBusetlNutn ( R treenode ): integer. else return renzp. base: integer ): integer. function EvalNum ( T: rreenode. base ). function EvalBase ( R treenode ): integer. para iniciar el cálculo se tendría que hacer una llamada como EvulWithBase(nodorraiz.itivay pticde ser necesaria e11p~iiitos . 6.ilores (le itrhiit-ariosiiur:~i~te airihuto tienen un. aun cuando todavía no exista. end ease. así como escribir tres procedimie separados para estos tres casos. var tetnp. 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.O). sena más racional distinguir tres casos: e so num-base.r eslrnctiira <ignilic. if hijo derecho (le T no es ni1 (nulo)then temp2 := EvcilI. hegin case clu. end EvalBasedNuin. el caso carbuse y el caso num y dígito.VithBuse ('hijoderecho de T.2.

sin pasarlo como u n p.irirnetr«..)caI h!i. end S e t R t ~ ~ r .se( T: treenode 1: iizteger. and terrzpZ f error then error return bn.ilonile uti1i~.El resto del c6digo de I<vcilWiiltBtr.I lo \igi~icntc. el cual sólo es llamado en u n nudo cvri>osc.s ni) (rudo) then temp2 := EvdWithBase ( hijo derecho de T ). . vez que se establece.totlavía puede no ser razonable almacenar valores de atributo en los nodos dcl árbol sintáctico. Corno el atributo h~r. Las reglas semánticas resultantes ya no representan entonces una gramática con :itributus.I wrii~blen o Ii1c:il Í v w .se*tetttp temp2 else return error. var tenzp.SetBrise ( hijo derecho de T ). podemos utilizar una variable no local para alniacenar su valor. 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.tso las reg1:rs tcntlríxi un aspecto :ilgo parecido .W .iinos .issernrinticni pitrn reflejar el uso . cn ver.Algoritmos para calculo de atributos 287 la txailucci<ín). - tlquí sepafiimos el proceso de . 131 esre c.le I.) De este modo. else return temp.isignaci<ína la variable hri. begin case clasenoiio de T uf num-bme: . (Si htrse no estuviera fijo.ir de iiintlera explícita el cst.ihlccirriic~iio la v:rri. + 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 ) . gráficas y otras estructuras pueden ser útiles para obtener el componamieiito correcto y accesibilidad de los valores de atributo.isignxiríri p:tra iriiiic. í begin if hqo (le T = o ihei1 hir te := 8 else hare : 10. Considere el ejemplo anterior con el proceditnicnto Ev<iIWlhRttse empleantio parámetros y valores devueltos. de 11 llm: r m p := E d WithBnse ( htjo izquierdo de T ). tetnp2: Nfteger. end EvtdWitizBuse.~i. rnieiitras que la operación de los procedimieiitos se ve con claridad. ci1toiiccs se refiere simple~ric~itehase de manera directa. podemos alterar el pseud«cí>digo para EvcilWilhBose de La niünera siguiente: function EiwlWithB<i. if ternp i .re no local e11 cl procedimiento SvrBcm. en u n proceso recursivo así esto sería riesgoso o incluso incorrecto. se mantiene fijo el tiernuna po que dure el cálculo del valor. if hijo clerecho de Tizo e. de pasarlo como un parámetro en cada ocasión.lhlc la de ¡ l o 1. end case.t 'TainhiCn podenios cümhi:~rlas I-cgl.se. return EvdWithBme ( hijo iryi~ierilo T 1. procedure SetB~ise T: rrtwoik ). En tales casos las estructuras de datos tales como las tablas de búsqueda. pero todavía son útiles en la descripción de la seniáiitica de los atributos.

dtype: typekind).val = iP dfgitu. pdemos utilizar una variable no local par macenar el dtype constante de cada declaración durante el procesamiento. y insertarán pares de nombre-tipo de datos en la tabla de símbolos mediante un proced~ to de inserción denominado insert. En la sig sección analizaremos cuestiones en torno a la tabla de símbolos en lenguajes de progr ci6n típicos. que almacena atribntos asociados con procedimientos.-. Uno de los ejemplos principales de una estructura de datos externas al árbol sintá es la tabla de símbolos. 6 1 ANALISIS Regla gramatical SENANTICO Reglas semCInticas iwm-buse -+ mtm curhuse curhase i o curbase -r d nurn~. lypa . vttr-lisb vur-list-1 i d - Reglas sernánticas dlype = Niteger útype = reul insert(id . reglas semánticas ya no fonnan una gramática con atributos.12 (página 279). 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. numi dígiro nurn-basc. consiguiente.i n t t y ) e + f loat wir-lirll * i d . d@[)ef inserffid.vid etc.riumr. También. Ahora base ya no es un atributo en e1 sentido en que lo henios utilizado hasta ahora. 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. Ejemplo 6. Por lo general. variab constantes declaradas en un programa.val = errur or numz.3 (pá 266) y el procedimiento de evaluación de atributo para esta gramática con atributos dad el ejemplo 6. rltype) .EAP.v d * buse + digiro.vu1 = huse := 8 base := 10 num. si se sab base es una variable con Las propiedades adecuadas.i. búsqueda y eliminación. . entonces estas reglas todavía de de manera adecuada la semántica de un num-base para el escritor del compilador. De este modo.val = error then error else ntrmz. etc. puesto cada declaración tiene sólo un tipo asociado. que se declara como se muestra a continuación: procedure insert ( name: string. Nos contentaremos en esta sección con el siguiente ejemplo simple. No obstante. tJna tabla de símbolos es una estructura de dat diccionario con operaciones tales como inserción.rrunre. Las reglas semá ticas resultantes son las siguientes: Regla gramatical (lec1 jype vur-lis1 .17 Considere la gramática con atributos de las declaraciones simples de la tabla 6.111 riirm. 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.

vale la pena ofrecer una perspectiva básica de sus ideas y requerimientos. var-list: insert(nombre del primer hljo de T. sin esperar a realizar pasos adicionales sobre el código fuente por medio de recorridos recursivos del árbol sintáctico. begin case clusenodo de T of decl: EvalType ( tipo hijo de T ). Esto es equivalente al requerimiento de que los atributos puedan evaluarse mediante un recorrido del árbol de análisis gramatical de izquierda a derecha. En la actualidad esto es menos importante. si se va a emplear para análisis semántica adicional. lo que está establecido en las reglas de type. 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. la posibilidad de calcular todos los atributos durante el análisis sintáctico era incluso de mayor interés. Estas reglas semánticas son m u y diferentes de la gramática con atributos correspondiente. 6. type: if hijo de T = i n t then dtype := uiteger else dtype := real. 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. 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). en particular de izquierda a derecha. 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.3 (pigina 266) viola . Históricamente. la gramática con atributos del ejemplo 6. Esto es particularmente importante respecto al árbol sintáctico mismo.Algoritmos para dlculo de atributos 289 En las llamadas a irrsert hemos utilizado i d . end EvalTvpe. Las dependencias no están expresadas tan claramente. de hecho. 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). dtype) if tercer hiJo de T no es nrl then EvalType ( tercer hijo de T ). la [-egla gramatical para una úecl no tiene. Para los atributos sintetizados esto no es una restricción. que suponemos es calculada por el analizador léxico o analizador sintáctico. riurne para hacer referencia a la cadena de identificador. Sin embargo.5 E cálculo de atributos durante el análisis sintáctico l 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. para los atributos heredados. ya que se había puesto mucho énfasis en la capacidad de un compilador para realizar la traducción en un paso. end case. ninguna regla semántica. el cual es un atributo sintetizado que debe ser construido durante el análisis sintáctico. asi que no proporcionaremos un análisis exhaustivo de todas las técnicas especiales que se han desarrollado. porque los hijos de un nodo pueden procesarse en orden arbitrario.2. Por cjemplo. EvalTjpe ( var-list hijo de T ). Pero.

.5 (página 269)). . en la mayor potencia de los analizadores L respecto a los analizadores LL. . . 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. Por desgracia. . sólo puede depender de los atrihutos de los símbolos X. si.R .al. . Eri pxiicular. X. . Xl. .-+ x . ' CBfcuIo de atributos rintetizados durante el análisis sintáctico LR Éste es el caso más sencillo para tin analizador LR. Como un caso especial. no tle 1. atributo vcd no se puede calcular hasta que el sufijo al final de la cadena se ve y se proc Las gra~náticas atributos que satisfacen esta propiedad se llaman de atrihuto L (por 1 con del término izquierda a derecha en inglés) y se detine de la manera siguiente. Expondremos brevemente el uso de la pila de an lisis sintáctico para calcular los atributos en los casos más comunes. son adecuados para controlar principalmente atribut sintetizados. .X. La pila de valores será manipulada en paralelo con la pila de análisis sintáctico.. y para cada regla gramatical x. . para ca atributo heredado u. 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)..imvs algun<~s l o s tietalles de u11 algoritmo de :rn:ílisis si~itictico en la i:ibla. los analizadores LR. x . Esto hace difícil que los atrib tos iteredados estén disponibles. . . 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 de anilisis sintáctico.. La razón reside. .fliiXO. Los atributos sóIo se pueden calcular cuando la regla gr rnatical a utilizarse en una derivación se vuelve conocida.. 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. Las fuentes para técnicas más complejas se mencionan en la sección de notas y re rencias. x. . irónicamente.. . . Ilustrarnos esto en la tabla 6.uk. . que se presentan a la izquierda de Xi en la regla gramatical. X . Xi.. Dada una gramática con atributos L en la que los atrihutos heredados no dependa de los sintetizados. L I ~ ) Es decir. el valor de (9 para X. . uk es de atrihuto 1.. . . . con aplicaciones p Yacc.I .8 para la gramática con :itrib~~tos la tabla 6. porque la base de un nurn-base tiene su base dada por el sufijo o o d. Por simplicidad utilizamos notaci6n abreviada para la graitiitica e ignor. a menos que sus propiedades permanezcan fijas para toda las posibles selecciones del lado derecho. 1as txuaciones asociadas para a.al. son todas de la forma _ .. Xt>ak.oj =. . Sin embargo. . ..que es la versión ambigua de la grainatica de eupresicín aritrriética 4inpls. 6 1 ANALISIS SEM~TICO esta propiedad. ya advertimos que tina gramática con atributos S es de at buto L.CAP. Una gramática con atrihutos para los atributos a . .u. . ctnno un analizador sint rico LALR(1) generado por Yacc. porque sólo entonces las ecu ciones son determinadas por el cálculo de atributo. Xi-..

) Conio ejernplo de una accidiiseinántica cotisidere el paso 10 de la tabla 6. inientrtis cpe $1 hallará dos posiciones por dehijo del tope.<II r i i -= n .a .-..idde l a pila de valores ) descarta e l toketi ] obtiene E . ' 17 + n ..~ . se encontrar5 se en la parte superior o tope de la pila.d *E i .n representada por la recluccidn e n e l paso 10 se escrihiríü como la regla Aquí las pseudov. SI?+.l. 7F .~:d 5 6 7 S '1 10 Il $E <SE* SE*n %E*/< 'S 3 -- si* $3 *n $3*4 S 17 ' IZ+ .l ' . suma ] ( inserta el resultado de iiuevo eii la pila de valores ( ( ( ( + / Eli Yacc la situacií.1. y lii accicín seináiitica cimeqx)ndiente de I. c d + 5. 5 17 ' 1:' .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 Acción de analisis sintactico dcylaz:i~nienro rectuccih de E --r n dzsplazntnie~ito desplar~rniento reducción <le E n redtrcci6n de E . v d . De este rriodo. ([. 1.d RE $E+ SE+n CI+li +5 S ' 5% S 3 ' i I < . I .5 es calcular de acuerdo con l a ecuaci6n El .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. iio se ri~iicstra l sínibolti iiiicinl aiirnentado y no se exe presan las reglas de elirrriiiiiciii~i airihigüedrtd iiiiplícitas.i cuhla 6.icci6ii del análisis sintáctico es reducir rnediante E . i ~ .I.vol = n . d C .~ f I = E2 . separados por e l token t. aunque esto puede diferir en a r i a l i ~ ~ d o r e s sintácticos individuales. --+ f'.'<l . 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.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.IJ * E dcs~iln~aniieiito ~le.8.Algorrtn~ospara cálculo de atributos 29 1 se indican los núnieros de csiado. L a t. .vid de la pila de valores ) . con el 5 en la pasie superior de la pila. d + l.<i/ & . Tdbla 6.-: i/ n riil 1.spl~~:it~iienii~ rzdi~cciiín E -a n Jc r.. que corresporide al E más a Iri derecha.cclirccii>nde Pila de valores $ $n Entrada 3*4+5 'S *4+5 $ *4t5 $ 4t5 $ +5 $ t5 $ Acción sernantica S 3 4 $n E .~ccionescorrespoiidientes en la pila de valores que realiza cl = analizador sin(6ctico son las que se muestran a coriiinuacidn (en psetidocódigo): pop 13 [~op pop 12 t l = rZ -t 13 push rl obtieite E .--+ E t E. Las . $3.~.ihla coniiciie dos nuevas code I~irnrias adeiriás de las acciones habituales de análisis sir~táctico:la pila tic valores y acciones scniánticas. i i l .' + i<. L a pila de valores contiene los valores enteros 12 y S.

mientras que cuando . Entonces.i = f (5.&pe type.) Tales acciones incrustadas en Yacc se estudiaron en la secci 5. sino que se puede acceder a él dire tamente en la pila de valores. se puede calcular en la pila de valores justo antes de que se reconozca la primera vw-list. D-rc: C-3. Para ilustrar esto brevemente considere la opción de producción A..drype = real insert(Ld . se reduce iar-lixt.5. por ejemplo. > C .s ) saved-i = f (vulstack[top]) [ ahora está disponible . Podemos implementiw esto en un rinalizador siiitictico LR mediante la eliminación de las dos .s)..dr/pe) En este caso el atributo clrype.ipe.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. En Yacc este proceso se vuelve aún más fácil. -3 v(ir-li.&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.dtype = integer type.~t~ dvpe está tres posiciones debajo del tope de la pila. 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. El valor de C.yaved-i ) .i d iw-lisr + i d Reglas semhnticas var-list.narne. que es un atributo sintetizado para el no terminal rype. Regias semánticas calcule B. dt-ye está justo debajo de la parte superior de la pila.U?. y suponga que C tiene un atributo heredado i que depende de alguna manera del a buto sintetizado s de B. .var-lista .: C. ya que la producción E no necesita ser in ducida explícitamente. var-listl. cuando se reduce cada regla para vnr-lis?.dtype = . (Aqui la pseuciovariable $1se refiere al valor de B. En es caso.&ype) var-list2. var-list. puesto que estos valores ya se insert en la pila de valores. que está en la parte superior de la p cuando se ejecuta la acción. el valor no necesita ser copiado en una variable.dtype= var-list&pe insert(id . Considere.name. 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). B 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 A-BDC B+.id.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. la siguiente gramática con atributo L con un atributo dtype heredado: Regla gramatical decl -t type vur-list type -+ int type -3 float + var-listl . En su lugar.

requiere que el programador tenga acceso de manera directa a la pila de valores durante un análisis sintáctico.2. Qpe f loat iur-iist. 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. De este modo. Con mucho.6). el analizador sintáctico debe insertar un valor de prueba en la pila para mantener la ubicación apropiada en ésta.&pe = integer tvpt.15 y la sección de notas y referencias). <*m *: - . la mejor técnica para tratar con atributos heredados en el análisis sintáctico LR es utilizar estructuras de datos externas. se tendría que escribir código especial para hacerlo.) Existen varios problemas con este métdo. 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. sin cambiar el lenguaje de la misma.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. En realidad. 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. En primer.17). 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. tal como una tabla de símbolos o variables no locales. todos los atributos heredados se pueden cambiar en atributos sintetiz. no obstante. En situaciones prácticas.s~ npe . + var-list2 .id«s mediante la modificación adecuada de ILI pramátic.dtos en ios momentos apropiados. y no se conocería la posición de d y p e en la pila. vale la pena advertir que las propiedades de los atributos dependen en gran parte de la estructura de la gramática. como se requeriría con el método anterior. como vur-lisr no tiene atributo sintetizado dtvpe. esto rara vez sucede.5. r~ul. para implementar un esquema de esta clase en Yacc. Por ejemplo.nome. tenemos el siguiente: Teorema (De Knuth [l9681).i ~ ) insert(id .6 l a dependencia del cálculo de atributos respecto a la sintaxis Como el tema final en esta sección. 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. entonces un número arbitrario de i d podría estar en la pila. Por ejemplo.~C i d + ) type. Sin embargo. Yacc no tiene una convención de pseudovariable para tener acceso a la pila de valores bajo la regla actual que se está reconociendo. y de agregar producciones E (o acciones incrustadss como en Yacc) para permitir que se presenten cambios a estas estructuras de d.ilIype = rtul insert(id . 6.i n t .lugar.i. Dada una grüniáticü con atributos.i d w~r-li. y esto puede ser peligroso en analizadores sintácticos generados de manera automática. ~ ~ ~ i ~ r l l l c k [ l o p . 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. con el fin de mantener los valores de atributos heredados.narnr.rtack~op-) 11 (Advierta que..

.dtype = vur-listz . . dependencias no se ven como herencias. 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.stt . 1 type entonces se aceptan las mismas cadenas... .3 es heredado. De este modo. d ~ p e typedope = integer f)pe.st...S!. .UP..junto con las dependencias y valores de atributo. . . .11. y.. -. Las dependencias de los valores i d . . vnr-list / id El atributo clvpe de la gramática con atributos de la tabla 6. .. . que exhibe el &bol de análisis gramatical para la c f loat x. 4 ! / f loat . Sin embarg volvenios a escribir la gramática de la manera siguiente.dtype = vur-li.dtype vur-¡¡. . iiecl -+vur-list id var-list -+ vqr-list i d ype -+ i n t / f loat . sur-lis1 .18 Vuelva a considerar L gramática de declaraciones simples de los ejemplos anteriores a decl-+ type iur-list type -+ i n t 1 float vur-list -+ id .dtype vur-li. .d@pe respecto a los valores del padre o hermano se trazan como líneas d' tinuas en la figura. 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 dtype = red (Y) .rype type -+ int tjpe -+ f l o a t id ._ _ _ _ . Aunque estas dependencias parecen ser violaciones a la afirmaci que no hay ztributos heredados en esta gramática con atributos. . .. . . . . . Ejemplo 6.dtype = real 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.18 /a- r . F~gura6 11 _--a decl / - Arbol de análiws gramatical para la cadena fioat x. dtype = rrnl . no recursivo) y den conseguirse mediante operaciones en los nodos padre apropiados. .iltyp = 1 p e .i r . de hecho estas depe cias son siempre hacia hojas en el árbol de análisis sintáctico (es decir.&pe = iw-listdtype id . y que muestra el atributo dtype como se eípecifico med~antela gramát~cacon atr~butor del ejemplo 6.

Por otra parte. es más prohabk que una operación de elimin<rciún(deirte) la retire de la vista. Antes que destruir esta iiiforrnación. Por lo tanto. organizaciones diferentes: así como la investigación de buenas estrategias de orgmización. puede ser porque Ia gramática está definida de ada para su c&ulo. d teorema establecido es menos útil de lo que puede parecer.De hecho. si un cálculo de atributo parece anormalmente difícil. y un cambio en la gramática puede valer la pena. Por lo tanto. no es una manera recomendable pata tratar con los problemas de cálculo de atributos heredados. . 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. ya sea alinacenándolü en otra p:irte: o marcándola ct>inoinactiva. en este texto 8. es uno de los temas principales de un curso de estructura de datos.

denominadas cubetas. Un ril6totlo común es insertür siempre al principio de la lista. Examinaremos funciones de dispersión en breve. Una cuesti6n importante es cómo una tabla de dispersión se ocupa de las colisiones (e se conoce como resoluci6n de cofisión). Por lo t a r comentaremos e1 caso de la tabla de dispersión con algo más de detalle.13 muestra un ejemplo simple de este esquema. a1 menos para la construcción del compilacior. Una tabla de dispersión es un arreglo de entradas. de marizra que ~isando mitocste <losize se Iiahría insci-tadodespués de j. Las listas lineales son una bu tructura de datos básica que puede proporcionar implententaciones directas y fáciles de 1 operaciones hiisicas. porque las colisiones de dispersión (donde se mapean dos claves hacia el mismo I dice mediante la función de dispersión) provocan una degradación del rendimiento en I operaciones de bií. En este método cada cub ta es en realidad una lista lineal. La función de dispersión también necesita funcio nar en un tiempo constante. La figura 6. 1). sino que remitiremos al lector que desee inhrmación a las fuentes que se mencionan en la sección de notas y referencias al fin este capítulo.CAP. denorninacla encadenamiento por separado. Esto puede ser suficientemente bueno para una itnple ración de compilador en la que el interés principal no sea la velocidad de compilacicín. ta mo un compilxior prototipo o experimental. La tabla de dispersión proporciona a menudo la selección pan implementar la tabla de símbolos. el ordcn de loi elemcritos en la lista depende del orden de insercih y de cómo se mantenga la lista. Se debe tener cuidado p:ira garantizar que la función de dispersic distribuya los índices clave tan uniformemente como sea posible sobre todo el intervalo índices. Las implementaciones típicas de estructuras de diccion'uio incluyen listas lineales. 6 / ANALISIS SEMANTICO no trataremos este terna de manera detallada. a medida que el arreglo se va llenando. . indiz mediante un-intervalo entero. size y temp)y que size ("tamaño") j tienen el rnisrno valor de dispersión a saber. sas estructuras de &bol de búsqueda (árboles de búsqueda binarios. Quizás el mcjor esquema para la construcciitn de compiladores es la alternativa al dire cioitaniiento abierto.j. Las estructuras de árbol de búsqueda son un poco menos útiles para la tabla de sínibola parte porque no proporcionan la mejor eficiencia del caso. con una tabla de dispersión de tamaño 5 (irrealmente pequeño. las colisiones se vuelv más y mis frecuentes. proporcionaremos aquí una perspectiva general de las estn ras de datos más útiles para estas tablas en la construcción de compiladores. y estas eliminaciones no mejoran el r dimierito oosterior de la tabla. Una función de dispersión convierte la clave de búsqueda (en este caso. y el elcntento correspondiente a la clave de búsqueda se almacena en cubeta en este índice. el nombre identificadoi. En esa tabla partimos del supuesto de que se han insertado cuatro identificadores (i. generalmente desde el O hasta el tamaño de la tabla m uno. ya que las tres operaciones se pueden z en un tiempo casi constante. Un' pro ma adicional con este método. árboles AVL y irbo así como tablas de dispersión (bUsqueda de dirección). Sin embargo. Un método asigna sólo el espacio suficiente p un solo elemento en cada cubeta y resuelve las colisiones mediante la inserción de nuev elementos en cubetas sucesivas (esto en ocasiones se denomina direccionarniento abiert En este caso el contenido de la tabla de dispersión está limitado por el taniaño del arre utilizado para la tabla y. para propósitos de dernostración). En la y ! ilustración mostranios size antes que j en el número 1 de la lista de la cubeta. pero también debido jidad de la operación de elirninlrci<ín.squedu y eliminación. o un intérprete para progranlas muy peclu. y las colisiones se resuelven insertando el nuevo elemento en la lista de la cubeta. con una t)perdción de inserri(jn en tiempo constante (insertando sie y al frente o la parte posterior) y operaciones de b~ísrprrla eliminc~cicínque son de tie lineal en las dimensiones de la lista. es que es fícil iinplementar una operación de eliminacitín. compuesto de una cadena de caracteres) en un valor entero de dispersión e intervalo del índice. y se utiliza niuy frecuentemente en la práctica. o por lo menos en un tiempo que sea lineal en Las dimension de la clave (esto equivale a tiempo constante si existe una cota superior en las dimensiones la clave). esto causa una degradación importante del rendimiento.

si se desea iin arreglo de cubetas de tamaño 200. un conjunto de enteros distribuido al azar puede no tener sus valores escalados distribuidos aieatoriarnente en el intervalo O. Volvamos ahora a una descripción de las funciones de dispersión comunes. Por lo común. Por lo regular. y así sucesivamente. Existen métodos para incremenrar el t. Esa función se llama mod en Pascal y % en C. se debería seleccionar 21 1 como el tamaño desde el 200 mismo (21 1 es el número primo más pequeño mayor que 200).size . ... tal como templ. En cualquier caso el tan~año del arreglo de cubetas debería elegirse como un número primo. así que este niétodo ocasionará frecuentes 9. la cual devuelve el residuo dividiendo size entre el número. Esto es inadecuado para un cornpilador.1. este tamaño se fijará durante el tiempo de construcción del compilador. ya que esto hace que las funciones de dispersión típicas se comporten mejor.. En primer lugar. Por ejemplo.iño "siie" del x r c g l o (y tnodificar la iuncitin dt: disp r s i ó i i ) :ilviielo s i la tabla dc ilkpcrii6n crcce iIciiiasi. Si se utiliza la asignación dinámica para las entradas reales.. m2tmg. Escalar un entero no negativo para que caiga en el intervalo O. el entero restiltante se escala al intervalo O. con el costo de un aumento real en el tiempo de compilación.. el medio y el último. Un método simple es ignorar muchos de los caracteres y sumar sólo los valores de unos cuantos caracteres prímeros. la función ord de Pascal convierte un carácter en un entero.l.1. convierte una cadena de caracteres (el nombre del identifieador) en un entero en el intervalo O. por lo general su valor ASCII. Luego.1 también se hace fácilmente utilizando la función módulo de matemáticas.i i. porque los programadores tienden a asignar nombres de variable en grupos. De manera similar. incluso arreglos pequeños permitirán la compilación de programas muy grandes. Una cuestión que debe contestar el escritor de compiladores es qué tan grande hacer el arreglo inicial de cubetas. el lenguaje C convertirá automáticamente un carácter a un entero si se utiliza en una expresión aritmética o se asigna a una variable entera. estos enteros se combinan de alguna manera para formar un entero simple. temp2 0 mltmp.~ize.ini.size . Una funcirín de dispersión.La tabla de rimbolos hdices Cubetas O I muestra la resolución 3 d e o ---.ei i e i i t i l i ~ 3 1 i .-. para su uso en una implementdción de tabla de sirnbolos. Resta al implementador de la tabla de dispersión elegir un método para combinar los diferentes valores enteros de los caracteres en un solo entero no negativo. -1 --+aize /1 o+ m temg / O Listas de elementos La Figura 6. esto se hace mediante un proceso de tres pasos. Éstos se pueden asignar utilizando la asignacicín dinámica de apuntadores del lenguaje de implementación del compiiador. pero s11n coinplejos y mr. La conversión de cada carácter a un entero no negativo generalmente se hace utilizando el mecanismo de conversión integrado del lenguaje de implementación del compilador. De otro modo. cada carácter en la cadena de caracteres se convierte a un entero no negativo.size . Cuando se utiliza este método.12 también muestra las listas en cada cubeta implementadas conio listas ligadas (se utilizan puntos sólidos para indicar apuntadores nulos). es importante que size sea un número primo. Al final.9 Los tamaños típicos abarcan desde algunos cientos hasta más de un millar.. o el primero. o se pueden asignar a mano a partir de un arreglo de espacio dentro del compilador niismo. Por ejemplo.ido.

se calculan de acuerdo con Las fórmulas recursivas y h. Una buena solución a estos problemas es emplear repetidamente un número cofist como factor multiplicativo cnando se sume al valor del siguiente carácter.vize. Si se usa este método. cuando se necesita . todas las pennutaciones mismos caracteres. De modo c. de ri es el número de caracteres en el nombre donde se está aplicando la dispersión. Otras posibilidades que se mencionan en la literatura son va números primos (véase la sección de notas y referencias).CAP. y tii es el valor de dispersión parcial cal en el i-ésinio paso. suponiendo que todos los valores de carácter ci sean menores que 128 (verda para los caracteres ASCII). E equivale a la fórmula La selección de a en esa fórmula tiene. En ocasiones el desbordantiento puede ser un problema en la fórmula para h. el desbordamiento puede dar como resultado valores ne (en representación de complemento a dos). puesto que 16 = 24). tal como 16 O 128. Una selección razonable para a es una potencia de dos. con el valor de dispersión Final h calculado como h = h. Si se utilizan val res enteros para los cálculos. Por ejemplo. Por consiguiente. la figura 6. un efecto importante sobre 1 lida. En ese caso posible obtener el mismo efecto realizando la operación mod en el bucle de sumatoria. Efectivamente. la manera en que las operaciones de ir?.13 se proporciona una muestra de código C para una función de dispersión h qu efectúa esto utilizando la fórmula anterior y un valor de 16 para u (un desplazamiento de de 3. por supuesto. Otro método popular pero inadecuado es sumar simpl te los valores de todos los caracteres.provocwín colisiones. de man que la multiplicación se pueda realizar como un desplazamiento.wrcitjny elinrintrciún nctitan sobre la tabla de símbolos. espe mente para valores grandes de a en máquinas con enteros de dos bytes. lo que causará errores de ejecución. 6 1 ANÁLISIS SEKÁNTICO colisiones entre tales nombres. Figura 6. es el valor numérico del i-ésimo carkter. entonces las h.13 funtlón de dispersión para una tabla de simbolos 63. el método elegido debería involucrar los caracteres en cada nombre.2 Declaraciones El comportamiento de una tabla de símbolos depende inuchb de las propiedades de las decl:iraciones del lenguaje que se está traducietido. selec nando u = 128 se tiene el efecto de visualizar la cadena de caracteres como un número base 128. tal como temgx y xtemg.¡ = alzi + ci. mod .

en las cu. las cuales definen un tipo de estructura con nombre Entry. ya que se utiliza tina construcc i h del lenguaje especial para declaraciones. struct Entry }. También es posible tener declaraciones implícitas. tleclaraciones de tipo. F1)RTRAN. Estas dec1ar. y declaraciorie~ C s t r u c t y union tales como de Struct Entry ( char * name. En esta seccii.n indicarenios alg u ~ s ú las cuestiones de lenguaje involucradrts en tieclaraciones que afectan el coinportae iniento y la implemcnt:~cii>ii de la tabla de símbolos.) 12 declaraciones de tipo incluyen las declaraciones de tipo de Pascal.b(lOO) y declaraciones de C corno int a.. ~ r x i hc~p1ícit:i. pero k t e .13. Las declaraciones de variables son la h r m a más coinún de d e c l a r a c i h e inclrtyen declaraciones en FORTRAN tales como integer a. (C también tiene u11 mecanismo # d e f i n e para la creaci6n de constantes. varía :iiripliatnente de lenguaje a lenguaje. perrriiten que las v:iriahles se utilicen sin i l e c l n r ~ i t i nexplícita. l a s declaraciones de constantes incluyen las declaraciones c o n s t de C. Éstas en realidad son s6lo una declaraci6n constante de tipo promdimie~iio/f~incií. * next. tieiic la conveiicicí~ide tipo de que las vsriahlcs que cornicticcii con el iritervalit tic Ietr. por l o regtilar se destaciin en definiciones del lenguaje debido pero a sti iiaturaleza especial. tales coiiio la t'uncióti de C definida en la figtira 6. es iiianqjado meniante un preprocesador. $irr una d c c l . FORTRAN y BRSIC. tales como const int SIZE = 199.cuando sc puede construir la rab\:i de síniholos.b[1001. tales coiiio . por ejemplo. Finaltnente. C también tiene un niecrinisrrio typedef pdra declamci6n de norrihres que son alias para tipos typede£ struct Entry * EntryPtr.l a tabla de rimbolos 299 llarniir a esas operaciones. lncluso el tiempo que toma el proceso de tratlucci(>nie.iciones son explícitas.rs <Irle va de la l 3 la N sean :iutorniticarneritc enteros s i sc utili~:in i r. y la extcnsilín que necesita tener la tahla sírnholos para existir. por ejemplo. tnás que por et propio coinpilador. Existen cuatro cl:ises básicas de declaraciones que se presentan con ('rccucncia en los lcngrmjes de programacih: declaraciones de constantes.s tYPe Table = array [l.jecuci<ín. existen declaraciones de proeedimientolfunción. y cuáles atributos se insertan en la tabla. pueden diferir hastante de un Ieiiguaje a otro. declwaciories de variable y declaraciones de proceditiiiento/funcicín.SIZEI o£ Entry.cd . En tales declaraciones implícitas se u t i l i ~ a r corivenciones para i proporcionar la inl'orniíiciúii que de otro rnodo sería diida por una dcclarltcicín esplícito.n.ilcs las declaraciones se unen a instrucciones ejecutables sin rrienci6ii explícita. iriieritras que tocI:th las otras son ~ii11o1ri5iiciit11~rlte c ~1-:E . int count.

pero también puede ser afectado notaciones sintácticas explícitas e interacciones con otras declaraciones. Dedicamos una sección tenor en este capítulo a la verificación de tipos. los tipos de dato constantes se determinan implícitamente de sus valores (estáticos). tales como C y Ada. permiten que las constantes sean dinámicas. tales constantes son de asignación única: una vez que sus valores se han minado no pueden cambiar. El compilador puede entonces utilizar I de símbolos para reemplazar los nombres de constante con sus valores durante la compil Otros lenguajes. Tales constantes se deben tratar más bien como vari en el sentido que se debe generar el código para calcular sus valores durante la ejecució embargo. El ámbito suele indicarse me te la posición de la declaración dentro del programa. De cuando en cuando es más m plear una tabla de símbolos diferente p cada clase de declaración. por lo tanto. Pascal y Modula-2 requieren que los valores en una declaración constante sean y. mientras que en C los de datos se proporcionan de manera explícita. 6 1 ANALISIS SEHANTICO declaraciones implícitas también se pueden denominar declaraciones por el uso. además. P plo. Las declaraciones de constante asocian valores a nombres. así que por el momento no hablaremos de las declaraciones de tipo. todas las declaraciones de tipo estén contenidas en una tabla de símbolos. las variables definidas por la declaración son accesibles). Los valores que pueden ser fijados determinan cómo los trata el compilador. todas las variables cuyas declaraciones son externas a funciones se asignan estáticamente (es decir. por esta raz ocasiones las declaraciones de constante se conocen como fuaeiones o especifkaci valor. tipo y procedimiento. Las declaraciones de variable vinculan más frecuentemente nombres a tipos de d como en el caso de C Table aynrtab. como en las declaraciones de variable. en C. Las declaraciones de variable también pueden fijar otros atributos impli Uno de tales atributos que tiene un efecto importante sobre la tabla de símbolos es de una declaración o la región del programa donde la declaración se aplica (es decir. que vincula el tipo de datos representado por el nombre Table a la variable con el no symtab. tales como C. Abordaremos las reglas del ámbito con más detalle dentro de poco. por ejemplo. de manera ejemplo. Las declaraciones de tipo vinculan nombres a tipos recién construidos y también den crear alias para tipos nombrados que ya existen.CAP. En Pascal. podemos desear aso blas de símbolos separadas con diferentes regiones de un programa (tales como los dimientos) y vincularlas de acuerdo con las reglas semánticas del lenguaje (coment esto con más detalle en breve). Con ciertos lengu particular los derivados de Algol. así como el tiempo que dura la ejecución de la asignación (que a veces se denomina tiempo de vida o extensión de la declaración). Los nombres de tipo por lo regul utilizan en conjunto con un algoritmo de equivalencia de tipo para realizar la verifica de tipos de un programa de acuerdo con las reglas del lenguaje. s una propiedad de las declaraciones de constante. po primer uso de una variable que no esté declarada de manera explícita se puede const como si contuviera implícitamente su declaración. que todas las declaraciones de variable estén contenidas en otra. es dec lo calculables durante la ejecución. Un atributo de las variables relacionado con el ámbito que también está ligado de manera implícita o explícita por una declaración es la asignación de memoria para la variable declarada. Los atributos ligados a un nombre por medio de una declaración varían con la c declaración. Puede. particularmente cuando el lenguaje proh el mismo nombre en distintos tipos de declaraciones. Las declaraciones de constante también pueden ligar de implícita o explícita tipos de datos con nombres. Con frecuencia es más fácil utilizar una tabla de símbolos para mantener los no todas las diferentes clases de declaraciones. Pascal y Ada. calculables por el compilador. antes del principio . Por ejemplo.

la declaración antes del uso y la regla de anidación más próxima para estmctura de bloques. no se emplea cualquier declaración de var~able comience con la palabra reservada extern para efectuar asignaque ción. Las estrategias de asignación de memoria. es que ha ocurrido una violación de la declaración antes del uso. mientras que reserva la palabra "declaración" para las declaraciones que no asignan memoria. C también tiene en cuenta el cambio de la extensión de una declaración dentro de una función.que tienen una extensión igual a todo el tiempo que tome la ejecución del programa. El lenguaje C se refiere a las declaraciones que asignan memoria como definiciones. De este modo. de manera que count devuelve como su valor el número actual de veces que ha sido llamada. mientras las variables que se declaran dentro de las funciones se asignan sólo mediante la duración de cada llamada de función (lo que se denomina asignación automática). asf. a ser cambiada de automática a estática mediante el uso de la palabra reservada static en la declaración. k la variable counter se tendda que asignar (e inicializar) en otro sitio del programa. forman una importante y compleja área en el diseño de compiladores que es parte de la estructura del ambiente d e ejecución. 6. como en int count (void) ( static int counter return ++counter.33 Reglas de imbito y estructura de bloques Las reglas de ámbito en los lenguajes de programación varían mucho. puede haber muchas declaraciones de la misma variable. una declaración que comience con la palabra reservada extern no es una definición. De manera que. C también clistingue entre declaraciones que se utilizan para controlar la asignación de memoria y aquellas que se emplean para verificar tipos. La declaración antes del uso es una regla común. es una definición. aquí no hablaremos más de la asignación. tal como int x. De este modo. return ++counter. pero sólo una definición. La función count tiene una variable local estática counter que retiene su valor de Ilamada en Ilamada. pero una declaración estiuidar de una variable. En C. Corno dedicaremos todo el capítulo siguiente al estudio de los ambientes. como la verificación de tipo. y el compilador emitirá un mensaje de error apropiado. utilizada en C y Pascal. En esta sección analizaremos todas estas reglas. La declaración antes del uso permite construir la tabla de símbolos a medida que el análisis sintáctico continúa y que las búsquedas se realicen tan pronto como se encuentra una referencia de nombre en el código. si la función anterior se escribiera como int count (void) f extern int counter. 1 a 0.la tabla de simbolos 30 1 de la ejecución). Algunos lenguajes . En C. En vez de eso regresaremos a un análisis de ámbito y estrategias para el mantenimiento del ámbito en una tabla de símbolos. que requiere que se declare un nombre en el texto de un programa antes de cualquier referencia al nombre. pero existen varias reglas que son comunes a muchos lenguajes. la tieclaración antes del uso fomenta la compilación de un paso. si la búsqueda falla.

(Se dice que la i n t i no local tiene un h de ámbito dentro de f .14. las declaraciones de procedimientoifunción y las sent compuestas (secuencias de sentencias encerradas entre llaves ( 1). y si el ámbito de d raciones en un bloque está limitado a ése y otros bloques contenidos en el mismo. consideremos el fragmento de código en C de la figura 6. Dentro de la función f tene declaraciones simples de variables size y temp en la tabla de símbolos. que contiene las declaracio las variables carácter i y temp. char { * j. Por ejemplo.(La declaración de función y su cuerpo asociado se p visualizar alternativamente como la representación de un bloque simple.. las declaraciones de clase de los le jes de programación orientados a objetos son bloques. los bloques son el programa principal y las raciones de proccdimiento/función.. Las estnict uniones en C (registros en Pascal). la compilaci un paso no es posible. : . De manera similar.. En segundo existe la declaración de f misma. en Pascal. los archivos de código). Figura 6. Para ver cómo la estructura de bloques y la regla de anidación más próxima afe tabla de símbolos. también se pueden visualizar como bloques. En primer lugar. La estructura de bloques es una propiedad común de los lenguajes moderno bloque en un lenguaje de programación es cualquier construcción que pueda co declaraciones.14 fragmento de código en C que ilustra los ámbitos anidados int i. 6 1 A N ~ L ~ SSEM~NTICO ~S . . la declaración que se aplica a una referencia es la única en el bloque anidad próximo a la referencia. . y todos los uso estos nombres se refieren a estas declaraciones. . ) { double j.CAP. las declaraciones de j en las dos sente compuestas postenores dentro de f reemplazan la declaración no local de j como un dentro de sus bloques respectivos. y por la regla de dación más próxima esta declaración reemplaza la declaración no local de i como un en el bloque de archivo de código circundante. exist sentencia compuesta que contiene la declaración char * j .. hay una decl ción local de i como un char dentro de la sentencia compuesta de f . . En C.. ya qu tienen declaraciones de campo. . int f(int size) { char i.) En cuarto existe la sentencia compuesta que contiene la declaración double j. no requieren de la declaración antes del uso (Modula-2 es un ejemplo). existe la sentencia completa del cuerpo de f . Finalmente. temp. En el caso del nombre i. y en ellos se r re de un paso por separado para la construcción de la tabla de símbolos. s la regla de anidación más próxima: dadas varias declaraciones diferentes para el nombre.. los bloques son las unidades de compila decir. código existen cinco bloques. existe el bloque del código complet contiene las declaraciones de las variables enteras i y j y la función f . Un lenguaje está estructura bloques si permite la anidación de bloques dentro de otros bloques. que contiene la declaración del parámetro siz tercer lugar..) De manera similar. En cada caso las declaraciones originales de i y j se cuperan cuando se sale de los bloques de las declaraciones locales.j.

var j: "char. grocedure h. Si tiacemos suposiciones de siinplificación seniejantes a las de la figura 6.. begin end. Por ejemplo.15 fragmento de código en parcal que ilustra los ambitos anidados program E x . de manera que la operación de búsqueda sólo encuentre la declaración para un nombre que se haya insertado más recientemente. var i . procedure g. el código en Pascal de la figura 6. por supuesto. begin ( * programa principal * ) Para implementar hmbitos anidados y la regla de anidación más próxima. sino que las debe ocultar temporalmente. De manera similar. end. revelando cualquier declaración previa. la operación de eliminación no debe eliminar todas las declaraciones correspondientes a un nombre.temp: char. Esto presenta un factor de coinplicación en el ambiente de tiempo de ejecución para tales lenguajes (el cual se estudiará en el capítulo siguiente)..15 contiene los procedimientos anidados g y h. Entonces la construcción de la tabla de símbolos puede continuar realizando operaciones de inserción para todos los nombres declarados en la entrada dentro de cada bloque y efectuando operaciones de eliminación corre?pondientes de los mismos nombres a la salida del bloque... Yara ver cómo p k d e mantenerse esa estmctura de una manera práctica.14 (excepto. los procedimientos y funciones también pueden estar anidados. j : integer. En otras palabras. tales como Pascal y Ada (pero no C). end. sino sólo la más reciente. function f ( s i z e : i n t e g e r ) : integer. var i. entonces tlespués [le que son procesadas las declaraciones del cucrpo del procedimiento . para los nombres adicionales g y h).La tabla de sirnbolor 303 En muchos lengua. pero tiene esencialmente la misma estructura de iabla de símbolos que la del código en C de la figura 6.jes.12 (plígina 297). la operación de inserción de la tabla de símbolos no debe sobrescribir declaraciones anteriores. la tabla de símbolos se comporta de una manera parecida a una pila ilurante el procesamiento de los ámbitos anidados. var j : r e a l . begin . pero no presenta complicaciones particulares para ámbitos anidados. . Figura 6. considere la iniplcmentación de la tabla de dispersión de una tabla de símbolos que se escribió con antcrioridad. begin ( * f * ) ...

la tabla de símbolos se vería como la de la figura 6. la tabla de símbolos podría parecerse a la de la figura 6. Para abandonar un ámbito sólo se requiere volver a establecer el apuntador de acceso (indicado a la izquierda) al siguiente hmbito niás externo.17. Abandonar un imbito requiere entonces menos esfuerzo. para cada nombre.CAP. ligadas desde la más interna hasta la más externa.16(a). 6 1 AN~LISIS SEMANTICO f en la figura 6. si falla al encontrar un nombre en la t bla actual. las listas ligadas en cada cu comportan como una pila para las diferentes declaraciones de ese nombre. D procesamiento de la segunda sentencia compuesta dentro del cuerpo de f (la que la declaración char * j). En vez de eso. Advierta como.l6(c). de manera que la operación de búsquedu contin buscando automáticamente con una tabla encerrada. la tabla de símbolos podría verse en la figura 6. 9 3 4 (a) Después del procesamiento de las declaraciones del cuerpo de f hdices Cubetas Lista de elementos (b) Después del procesamiento de la declaración de la segunda sentencia compuesta anidada dentro del cuerpo de f índices Cubetas Lista de elementos (c) Después de salir del cuerpo de f (y de eliminar sus de~laraciones) Existen varias alternativas posibles para esta implementación de ámbitos anidados. despues de salir del bloque de la función f. Un ejemplo de esta estructura correspondiente a la figura 6.14 o 1 --+i m Lista de elementos ( c h a r ) H i (int)jo] (ni. En esa figura se tienen tres tablas. it1l 4 s i z e (inti-j ' . una para cada ámbito. .16(b mente.16(b) se proporciona en la figura 6. solución es construir una nueva tabla de símbolos para cada ámbito y vincular las tablas de de ámbitos internos a ámbitos externos. F ¡ @ ~ f a6. toda la tabla de símbolos correspondiente al ámbito se puede liberar en un paso.14.16 Índices Cubetas Contentdo de tabla de sirnbolos en vartor lueares en el codiao " de la figura 6. ya que no es necesario volver a procesar las declaraciones utilizando operaciones de elirninacih.

puede tener sentido.. SNOBO[. f . puede necesitarse asignar un nivel de anidaeión o profundidad de anideci6n a cada ámbito y registrar en cada entrada de la tabla de símbolos el nivcl de anidación de cada nombre. Ex. h.. en la figura 6.15. De esta forma. / / ésta es la definición de f en A 1 Todas las clases. las declaraciones de f (sus parámetros y vr~riables cales) tienen nivel de anidación 1 y las declaraciones locales tanto de g como de h tienen riive1 de anidación 2. / / f es una función miembro A::fO ( . Esta regla requiere que la :iplicaciGn cie los iimbitos anidados siga la trayectoria de ejecrtci&i.17).15 se distinguirían como Ex. Un cjonplo simple que muestra diferencias cnise. y algunos lenguaies de consulta de base de datos) se conoce como ámbito dinámico.16(b) en la que re Ililaion tablas reparadas . Esto puede ser empleado para conipletar la definición de funciones niienibro desde el exterior de la declaración de clase: . f . del .. i (utilizando el nombre del programa para identificar el ánrbito glob:~l). Una regla de ciinbito cilternativ:1 mie se utiliza en algunos lenguajes orientados dinámicamente (versiones antigtlas de LISP. dependiendo del lenguaje y los detalles de la operación del compilador.. Este operador permite que Se pueda tener acceso al ámbito de una declaración de clase desde el exterior de la declaración misma. Hasta ahora hemos estado comentando la regla de ámbito estándar que sigue la cstructura textual del programa. j. j . En situaciones donde estos ámbitos se pueden rcferenciar de manera externa. por medio del nombre asociado con el ámbito en el cu:il se declara el nombre no local. . De cste modo. en el código Pascal de la figura 6. 1:~'.3 . manera adicional o alternativa. donde puede ser referenciado utilizando una notación semejante a la selección de campo de registro. todavía ser6 visible como la variable Ex.il progr:ima.Ca tabla de rimbolos 305 gura 6 ií' . conviene construir una tabla de símbolos por separado para cada ámbito (como a en L figura 6.mctura de tabla de mbolo~correspandtente a la Fa 6. En ocasiones esto se llama ámhito léxico o estático (porque I:I tabla de símbolos se construye de manera estática).15 el ámbito global del loprograma tiene nivel de anidación 0. Un ejemplo es el requerimiento en Ada de que todavía sea visible un nombre no local dentro de un hueco de ámbito. g j y Ex.en Ada.a cada imblto temp (char)la ] Un procesamiento adicional y cálculo de atributos también pueden ser necesarios durante la construcciún de la tabla de símbolos. Un mecanismo que es semejante a las características de selección de iírnhito de Ada que se acaba de describir es el operador de resolución de ámhito : : de C f f. identificar cada h b i t o mediante un nombre y asignar un prefijo a cada nombre declarado dentro de un Ambito mediante sus nombres de ámbito anidados acumulados.zarse como representación de un ámbito nombrado. todas las ocurrencias del nombre j en la figura 6. con el conjunto de declaraciones locales como un atributo. Por ejemplo.. class A { int f 0 . Así. De. . dentro de la función f la variable entera global i. cii vcz tiel discíio text~i. mientras se construye la tabla de símbolos.. procedimientos (en Ada) y estructuras de registro pueden visuali.

y con el lenguaje que se está traduciendo. Si se utilizar:i ámbito dinámico. en vez de tener la tabla de símbolos construida directamente (y táticaniente) por el compilador.h> int i = 1. De este modo. De este {nodo.CAP. Ésta pueden variar con la clase de declaración. deberíamos ztdvertir que la operación de rli~nincición ha mostrado en L se a tigura 6. asociadas a un solo bloque). Si deben mantenerse en la tabla de sítnbolos. int i. Pascal. f0. el ámbito dinámico rara vez utiliza en los lenguajes rnodernos. por ello no comentaremos más a1 respecto aquí. 6 1 AHÁLISIS SEHANTIEO dos reglas es el cúdigo en C de la figura h.3.16 para eliminar del todo cieclaraciones de la tabla de símbolos. la legibilidad d los programas. De hecho puede ser en necesario mantener las tlecl~raciones la tabla (o por lo menos no dejar de asignar sus espacios en memoria). (Advierta el problema que se presenta en el c6digo de la figura 6. void f(void) { printf ("%d\n". El ámbito dinámico compromete. adem6s. ya que los tipos de datos de las variables deben mantenerse por medio de la tabl de símbolos.de sítnbolos se vuelva parte del ambiente y que el código se genere mediante un co pilador para mantenerlo.18 C que ilustra la :ia entre inibito y dinámico #include tstdio.el uso del ámbito dinámico requiere q tabla. porque otras partes del compiladov pueden volver a necesitar h x w referencia a ellas posteriormente. Finalmente.4 lnteracción de declaraciones del mismo nivel Otra cuestión que se relaciona con el &nbito es L interacción entre declaraciones del mismo a nivel de anidación (es decir. y main contiene una tleclaración de i (con valor 2 se extiende a f si se utiliza la secuencia de llamadas para resolver referencias no loc inibito dinámico requiere que la tabla de símbolos se construya durante la ejecució lizando operaciones de irrsercicín y rlirnincici(ít~ rnedida que los ámbitos se introdu a extraen en tiempo de cjecución. en C. 18.18 si la dentro de main se declara como double. Ada) es que no se puede volver a utilizar el mismo nombre cn declarxiories 111 rtiisino nivcl. Un requerimiento típico en muchos Icnguajes(C. Finalmente. 6. entonces el programa impritni porque f es llamado desde main. el ámbito dinámico es incompatible con la verificaci6n d tipo estútico.)De este modo. .1 void main(void) ( int i = 2. las siguientes declaraciones consecutivas provoc~irán error dc compilación: un typedef int i. porque las referencias iio locales no pueden resolverse sin simular la ejec ciún del p g r a m a . i). Este cítdigo imprime 1 utilizando la re ámbito estúndar de C debido a que el ántbito de la variable i al nivel de archivo se <leal procedimiento f . return O. entonces la 6.

De otro d o . Puede parecer natural que se utilice la declaración más reciente (la local). Esto es necesario en particular para declaraciones de procediniientoffunción. y algunos lenguajes funcionales. si se utiliza la declaración local para i o la declaración no local para F. Algo más difícil es la cuestión de cuánta información tienen disponible entre s í las declaraciones en una secuencia al mismo nivel. como en el siguiente fragmento de código en C void f (void) O . en un analizador sint5ctico descendente recursivo). como en el siguiente código en C. Finalmente.) . Entonces cualquier nombre en las expresiones dentro de las declaraciones haría referencia a las declaraciones anteriores. else return gcd(m. no a las nuevas declaraciones que se e s t b procesando. para una función que calcula el máximo común divisor de dos enteros: int gcd(int n. existe el caso de la estructura de declaracibn recursiva.. lo que se conoce como declaración secuencial. donde los grupos de funciones mutuamente recursivas son comunes (por ejemplo. considere el fragmento de código en C int i = 1. sino que se acumulen en una nueva tabla (o estructura temporal) y después se agreguen a la tabla existente cuando todas las declaraciones se hayan procesado. Por ejemplo. . Una regla para declaraciones de esta naturaleza requiere que las declaraciones no se agreguen inmediatamente a la tabla de símbolos existente. void g(void) O 1 . En casos niás complejos de un grupo de funciones mutuamente recursivas. una función recursiva se llama a sí misma. int m) { if (m 1 0) return n. En su forma más simple. La cuestión es si j dentro de f es inicializada al valor 2 o 3. Es posible que en vel: de esto todas las declaraciones se procesen "simultáneamente" y se agreguen a la vez a la tabla de símbolos al final de la sección de declaraciones. como ML y Scheme.. es decir. j = i+l. 1 -- Para que esto sea compilado correctamente..La tabla de simbolos 307 Para verificar este requerimiento un cornpilador debe realizar una búsqueda antes de cada inserción y determinar mediante algún inecanisnio (el nivel de anidación. el compilador debe agregar el nombre de la función gcd a la tabla de símbolos untes de procesar el cuerpo de ia función.n % m). Una estructura de declaración así se denomina declaración colateral. tienen una estructura de declaración como ésta. en la cual Ias declaraciones pueden hacer referencia a sí mismas o entre sí. mediante la regla de anidación más próxima. por ejemplo) si cualquier declaración preexistente con el mismo nombre está o no en el mismo nivel.. el nombre gcd no se hallará (o no tendrá el significado correcto) cuando se encuentre la Ilanlada recursiva. Pero esto presupone que cada declwación se agregue a la tabla de símbolos a medida que se procesa. Ésta es en realidad la manera en que se comporta C. void f(void) ( int i = 2.

/ * declaración de prototipo de.. todos los prototipos de g deben tener verifi de tipo para asegurar que son idénticos en estructura. Por plo.CAP. junto con una extensión que involucra declaraciones: S -+ exp exp -+ ( exp ) / exp + exp / id 1 num 1 let dec-list in exp dec-list -+ dec-list . el compilador agrega g a la tabla de símbol do se alcanza la (primera) declaración de prototipo para g íjunto con su propio at ámbito posicional). 63.jercicios la construcción de una graniitica no ambigua equivalente). Esto requiere un paso de procesamiento en el cual todos los p mientos y funciones se agreguen a la tabla de síq~bolos antes de procesar cualquiera cuerpos. debido a que la gramática con atributos contiene atributos heredados que necesitarán inicialización en las raíces del árbol sintáctico. El problema de la recursividad mutua puede ser resuelto de varias maneras. 6 1 AMALISIS SEMANTICO no es suficiente con agregar cada función a la tabla de símbolos antes que su cuerpo cesado. En Modula-2 la regla de ámbito para procedimientos y funciones (a de variables) extiende sus ámbitos para incluir el bloque completo de sus declaracion manera que son mutuamente recursivos de manera natural y no es necesario un mecan de lenguaje adicional. este problema se elimina agregando 1 minada declaración de función prototipo para g antes de la declaración para f : void g(void). so 1 . Naturalmente.. En realidad.. La gramática contiene sólo una operación fia adición.. extendiendo el á nombre g para incluir f . Declaraciones mutuamente recursivas similares también se encuentran dispon en varios otros lenguajes. También es ambigua. La gramática que zamos para este ejemplo es la siguiente condensación de la gramática de expresión aritm simple. . con token +) para hacerla más simple.Además. Sin embargo. I .5 Un ejemplo extendido de una gramática con atributos mediante una tabla de símbolos de las Ahora queremos considerar un ejemplo que demuestre varias de las claraciones que hemos descrito y desarrollar una gramática con atributos que haga exp tas estas propiedades en el comporfamiento de la tabla de símbolos. el cuerpo de g no existe hasta que se alcanza la ción (o definición) principal de g. De este modo.función */ void £ (void) C. y se parte del supuesto de que un analizador sintáctico ya construyii un irbol sintáctico o manej6 de :&una manera las ambigiiedades (dejamos para los e. En C. decl / decl deel-+ id = exp Esta gramática incluye un símbolo inicial S en nivel superior. en Pascal se proporciona una declaración fonvard como extensor de ámbito de dimiento/función. el fragmento anterior de código C producirá un error durante la lación para la llamada a g dentro de f .. void gfvoid) o 1 . Tal modificación se puede ver como un modificador de ámbito..

no habrá declaración repetida del mismo nombre dentro de la misma expresión let.) El valor de la expresión let es el valor de su cuerpo. y que las expresiones let representan los bloques de este lenguaje. el hmbito de cada declaración en una expresión let se extiende sobre cl cuerpo de lrt de acuerdo con la regla de anidación rnas próxima pard estructura de bloques. el valor de la expresión . De esta manera. necesitamos describir las reglas de ámbito e interacclones de las declaraciones en las expresiones let. y da como resultado un error. una expresión de la forma l e t x = 2 . En segundo lugar. y s ( l e t 2=3 in x+y+z) in ( x + Y ) ) Estableceremos las siguientes reglas de ámbito para las declaraciones de expresiones let. x representa el valor 3 (el valor de 2+1) y. Así. por otro lado. y = 3 in ( l e t x = x + l . utilizando gramáticas similares suponemos que num e id son tokcns cuya estructura está determinada por un analizador léxico (puede wponerse que num es una secuencia de dígitos y que id es una secuencia de caracteres). De esta Forma. La adición a la gramática que involucre las declaraciones es la expresih let: e. en el caso anterior. la semántica de una expresión let es como sigue. Para completar la exposición informal de la semántica de estas expresiones. la expresión l e t x =2 in x+y es errónea. Por ejemplo. Como en ejemplos anteriores. de manera que podamos escribir expresiones de manera no ambigua en esta gramática si así lo deseamos. En primer lugar. vemos que las declaraciones dentro de una expresión let representan una clase de declaración constante (o fija). en la expresión l e t x = 2.ip -+ l e t dec-list in exp En una expresión let las declaraciones se componen de una secuencia de declaraciones separadas por comas de declaraciones de la forma id = exp. que se calcula reemplazando cada nombre en las declaraciones por el valor de su exp correspondiente y posteriormente calculando el valor del cuerpo de acuerdo con las reglas de la aritmética. A partir de la semántica que se acaba de dar. Un ejemplo de esto es De manera informal. por ejemplo. De este modo. cuando aparecen en la exp después del token in.La tabla de simbolos 309 incluimos parhtesis. la expresión let misma tiene el valor 10 (= el valor de x t y cuando x tiene valor 3 e y tiene el valor 7).qimbolizan los valores de las expresiones que representan. también se obtiene un error. Advierta que la gramática permite anidaciones arbitrarias de expresiones let entre sí mismas. (La exp es el cuerpo de la expresión let. x = 3 in x + l es ilegal. y representa el valor 7 (el valor de 3+4). si no es declarado cualquier nombre en alguna expresión let circundante. Las declaraciones después del token let establecen nombres para expresiones que. En tercer lugar.

si e. únicamente un nivel de anidación. Ahora deseamos desarrollar ecuaciones de atributo que utilicen una tabla de sí para estar al tanto de las declwaciones en expresiones let y que expresen las reglas hito e interacciones que se acaban de describir. Haremos esto con las dos operaciones isin(s. Por simplicidad. Finalmente. no se necesita una operación delete explícita. pero con la tabla de símbolos original sin sufrir cambios. El valor de nestlevel es un entero no negativo que expresa el ni anidación actual de los bloques let. de a do con las reglas previamente establecidas. o . la segunda x tiene ) 5 (utilizando las declaraciones de let entre paréntesis) y la segunda y tiene valor 8 (em do la declaración anidada de y además del valor de la x apenas declarada). No escribiremos ecna para calcular el valor de las expresiones.1. nesrlevel y err de las expresiones. 6 1 ANÁLISIS SEMANTICO X ) let x=2 in (let x = 3 in es 3. ~inalmente. n). definimos la interacción de las declaraciones en una lista de decla en el mismo let para que sea secuencial. y lookup que devuelve un valor entero dando el nivel de anidación de la declaración más recie n. FI mente. expresión let x = 2 . ti. y = x + l in (let x = x + y . sólo utilizaremos 1 de símbolos para determinar si una expresión es o no errónea. volvemos ahora a la cscrititra de las ecuxiones de atributo para los tres atributos syrntub. si n no está en la tabla de s í m b o h s (esto permite expresa eaaciones uiilizando lookup sin tener que realizar primero una operación isin). no es necesario asociar un v a n. pero dejaremos que el lector lo haga como cio. De esta form inserción Ntsert(s. y también recuperar el nivel de anidación asociado co nombre que esté presente. Invitamos al lector a calcular de manera parecida el valo expresión let triplemente anidada de la página anterior. Con estas operaciones y convenciones para tabla de símbolos. debemos poder verificar la presencia de un n bre en una tabla de símbolos. Para hacer esto necesitamos tanto un a to heredado symtnh. 1) devolverá una nueva tabla de símbolos que contiene toda la infor ción de la tabla de símbolos s y además asocia el nombre n con el nivel de anidación 1 s cambiar s. y = x + y in y ) la primera y tiene valor 3 (utilizando la declaración anterior de x . (Como estamos determinando sólo la exactitud.9.xiste. que represente a la tabla de símbolos ("bol le"). .CAP. expresamos las operaciones de tabla de símbolos en una nera libre de efectos colaterales escribiendo la operación de irrserción para tomar una ta de símbolos como un parámetro y devolver una nueva tabla de símbolos con la nueva in mación agregada. debemos tener una notación para una tabla de símbolos inicial que no tenga entradas. no a la cicín x=2). como un atr heredado nestlevel ("nivel anidado") para determinar si dos declaraciones están d del mismo bloque Let. escribiremos esto como empfyrnble ("tabla vacía"). Como quer escribir ecuaciones de atributo. cada declaración utiliza las dec nes anteriores para resolver nombres dentro de SU propia expresión.) Puesto que esta notación garantiza que podamo cuperar la tabla de símbolos s original. para probar los dos criterios que deben satisfacerse para la exactitud (que tod nombres que aparezcan en expresiones se hayan declarado previamente y que no ocu declaraciones repetidas en el mismo nivel). que devue un valor booleano dependiendo de si n está o no en la tabla de símbolos S . La gramática con atributos completa se resume cn la tabla 6. Es decir. la sión completa tiene valor 8. En vez de eso calculamos el atributo booleano sintetizado err que tiene el valor ("verdadero") si la expresión es errónea y false ("falso") si la expresión es correcta. Así. De este modo. no 2 (puesto que la x en el ler interno se refiere a la declaración x=3. Se inicializa con el valor O en el nivel más extern El atnbuto syntub necesitará operaciones de tabla de símbolos típica.

silevrl = O - En el nivel niás alto.La tabla de rimbalor Regla gramatical Reglas semánticas S + exp ~~. asignainos los valores de los dos atributos heredados y elevamos el valor del atributo sintetizado. De este modo.syrntub rnii.rpiip. exp.. la regla gramatical S -+ exp tiene Itis ires ecuaciones de atributo asociadas .rie.iyuhlt.

id m m e ) Por otra parte.err + Advierta que el nivel de anidación también se incrementa en uno tan pronto como se introduce el bloque let. de modo que la ec de atributo es exp. mediante la regla secuencia1 para declaraciones. la expresión let tiene un error s dec-list contiene un error (en cuyo caso su outtab sería errtab) o si el cuerpo expz de 1 presión let contiene un error. las reglas siguientes expresan el hecho de expresiones del lado derecho heredan los atributos symtab y nest[evel de la expr lado izquierdo. Expresamos esto asociando dos tablas de símbolos por separado con decl-list: la entrante intab. declaraciones individuales a medida que el procesamiento de la lista continúa.wme para el nombre del identificador dremos que se calcula mediante el analizador léxico o el analizador sintáctico). . -+ l e t dec-list i n expz y las reglas asociadas para declaraciones.err La regla exp -+ i d produce un error sólo si el nombre del i d no puede hall tabla de símbolos actual (escribiremos i d . 6 1 ANALISIS SEMANTKO Para la regla exp. Una libta de declaraciones debe acumular.symtab dec-list. .intab = exp.outtab expz . . . mstlevel 1 exp. Resta desarrollar ecuaciones para lktas de declaraciones y para declaraciones individuales. que está heredada de expl. symtab exp3 .err or exp.symtab = dec-1ist.nestlevel = exp.err = (decl-1ist. debemos expresar esto teniendo en cuenta una tabla de símbolos esp errtab que se pase desde una dec-list.symtab. y la tabla saliente outtab. -+ expz + exp3 (como es habitual cuando numeramos los no te les escribiendo ecuaciones de atributo). . . .nestlevel exp.CAP. Las ecuaciones de atributo son 4 S dec-1ist. Finalmente.err = exp. Debido a que las vas declaraciones pueden contener errores (tales como la declaración repetida de los mos nombres). dada la regla . la regla exp -+ num nunca puede producir un error. de la ecuación de atributo asociada es exp. En la regla para las expresiones let. De este modo. . la cual contiene un error si por lo menos una de las expresiones derecho contiene un error: exp. symtab = exp.err = not isin(exp.err = false Ahora volveremos a un anilisis de las expresiones let con la regla gramatical exp.nestlevel = dec-list. mstlevel exp.symtab exp.nestleve1 = exp..outtab = errtab) or exp2 .symtab = exp. .nestlevel = exp. la decl-1' compone de cierto número de declaraciones que se deben agregar a la tabla de símbol tual. que contiene las n declaraciones (además de las antiguas) y que debe pasarse a exp. .nestleve1 exp.

la información de tipo es principalmente estática. como Pascal. o de manera más precisa.vp. Con esto concluye nuestro análisis de las ecuaciones de atributo. y esto puede utilizarse para simplificar el ambiente en tiempo de ejecución.) La ecuación completa para decl. y esto se pasa hacia atrás como la outtab sinteti~ada la declaración. que son proporcionadas por la definición del lenguaje. La información del tipo de datos puede ser estática o dinámica. Finalmente analizamos el caso de una declaración individual deel-+ i d = exp En este caso la decl. tales como + y *. decl la outtab de dec-list2 se pasa como la intab a decl. si no hay errores.o una cxpresidn cstructurada. La información de tipo de datos puede presentarse en un programa en varias formas diferentes. Teóricamente. y se hace referencia a ellas simplemente como verificación de tipos.1. que es un nombre de tipo. Esto se debe verificar realizando una búsqueda ("lookup"). La tercera.) En esta sección sólo nos ocuparemos de la verificación de tipos estática. La primera. un error de puede haberse presentado ya en una declaracibn anterior. la declaración puede ser una declaración repetida de un nombre en el mismo nivel de anidación. -+ úec-list2 . esto se indica mediante e. no del actual). en cuyo caso decl. estos conjuntos por lo regular se describen mediante una expresión de tipo. también debe provocar que outtub sea errtab. de modo que no se genera ningún error.9. tal como . Entonces. En la mayoría de los dialectos de LISP la información de tipo es enteramente dinámica. id.4 TIPOS DE DATOS Y VERIFICACION DE TIPOS Una de las tareas principales de un compilador es el cálculo y mantenimiento de la información en tipos de datos (inferencia de tipos). Las ecuaciones completas se muestran en la tabla 6. un tipo de datos es un conjunto de valores. se realizan juntas. se realiza copia y herencia estándar. (Advierta que si lookup falla en su intento de encontrar id. La segunda. En lenguajes más tradicionales. el tipo de datos integer ("entero") en un lenguaje de programación hace referencia a un subconjunto de los enteros matemáticos.nume se inserta en la tabla con el nivel de anidación actual. y el uso de tal información para asegurar que cada parte de un programa tenga sentido bajo las reghs de tipo del lenguaje (verificación d e tipo). con lo que se da acceso a decl a las declaraciones que la preceden en la lista. de modo que rxp debe encontrar sólo declaraciones anteriores del nombre de este i d . En el caso de una declaración simple ((lec-list 4 decl). un compilador debe generar código para realizar inferencia de tipo y verificación de tipos durante la ejecución.Tipos de datos y verificación de tipos dec-liut.outtab se proporciona en la tabla 6. C y Ada.9. el cual nunca coincide con el nivel de anidación actual.intub heredada se pasa de inmediato a exp (porque las declaraciones en este lenguaje no son recursivas.in#abes errtub.y este error también se debe informar obligando a que outtab sea errtab. La información de tipo estático también se emplea para determinar el tamaño de memoria necesario para la asignación de cada variable y la manera en que se puede tener acceso a la memoria. En el terreno práctico de la construcción de compiladores.err y. En un lenguaje así. junto con las operaciones aritméticas. Por ejemplo. entonces devuelve . 6. tal como integer. (Comentaremos esto en el capítulo siguiente. o una mezda de las dos. Estas dos tareas por lo regular están estrechamente relacionadas. si es verdadero. un conjunto de valores con ciertas operaciones sobre ellos. Pueden presentarse errores de tres maneras.name. y se utiliza como el mecanismo principal para verificar la exactitud de un programa antes de la ejecución. esta errtab debe ser pasada como outtuh. puede presentarse un error dentro de la enp.

Por ejemplo. . Éstas i declaraciones de variable tales como var x: array [l. un campo de exponente y un campo de fracción (o mantisa). . 6 1 AN~LISIS SEMANTICO array . el lenguaje C eitándar requiere que un tipo double de punto tlotante tenga por lo menos 10 dígitos decimale\ de precisión. y para los valores booleanos es la de un byte. Tales tipos de son tipos simples. ya sea a tip datos numéricos que son provistos internamente mediante diversas arquitecturas de m na y cuyas operaciones ya existen como instrucciones de máquina. Si a es d array [l. dependen todas de las clases de expresiones de disponibles en un lenguaje y las reglas de tipo del lenguaje que gobiernen el uso de e expresiones de tipo.(La cuestión de si i tiene un val esté entre 1 y 10 es una cuestión de verificación de intervalo. [l. O = "false" o falso). con un bit de signo. donde el tipo de greeting ("aaludos")es implícitaniente array [l.101 o£ real. del cual sólo se utiliza el bit menos significativo (1 = "true" o verdadero. con bres tales corno int y double. Por eje la expresión los tipos de datos de los nombres a e i se obtienen de la tabla de símbolos. ya sea explícita o implícita.entonces se determina que las presión a ti] es correcto en tipo y tiene tipo real. . la cual asocia un tipo con un nombre de variable. la form que una tabla de símbolos mantiene la información del tipo y las reglas que utiliza rificador de tipo para inferir los tipos. por ejemplo en la declaración de constante en Pascal const greetfng = " H e l l ~ ! ~ . se mantiene en la tabla de símbolos y se recupera mediante el verificador de ti pre que se hace referencia a los nombres asociados. o a tipos elementa mo boolean y char. o de punto flotante. 6. es de cuatro u ocho bytes.CAP. Una repr ración típica para enteros es en la forma de complemento a dos de dos o cuatro bytes. 6 ] o de acuerdo con las reglas de Pascal... ya que sus valores no exhiben estructura interna explícita.. Una representación típica para números reales. Una representación coniún para caracteres es la de los códigos ASCII de un byte. que est6 contenida en de nes.41 Expresiones de tipo y constructores de tipos Un lenguaje de programación siempre contiene un número de tipos ya incluidos. La información de tipo. Tal información de tipo es explícita. En ocasiones un lenguaje impondrá restricciones sobre cómo 5e van a iinplementar e w s tipos predefinido~. y declaraciones de tipo. También es posible que la inform tipo sea implícita.lo] o£ real. que en general no es e ticamente determinable.) La manera en que se representan los tipos de datos mediante un compilüdor.cuyo comportamiento es fácil de implementar.e i tiene tipo integer. tales co la cual define un nuevo nombre de tipo para utilizarlo en un tipo posterior o declar de variable.lo] of real.cuyas operaciones generalmente se suponen o impl' expresiones de tipo se pueden presentar en diversos lugares en un programa.Estos tipos predeñnidos corresponden. Los tipos nuevos se infieren e de estos tipos y se asocian con los nodos apropiados en el árbol sintáctico.

en Pascal un tipo de índice está limitado a los denominados tipos ordinales: tipos para los cuales todo valor tiene un predecesor inmediato y un sucesor inmediato. tales como array y record o struct. . así como para representar un apuntador que (de momento) apunte a un tipo desconocido. no hay palabra reservada que corresponda a array en C: los tipos arreglo \e declaran escribiendo simplemente como sufijo del nombre el intervalo entre corchetes. Por ejemplo.Tales constructores se pueden ver como funciones que toman tipos existentes como sus parámetros y devuelven nuevos tipos con una estructura que depende del constructor. crea un tipo array cuyo tipo-índice es color y cuyo tipo-componente es char. Tales tipos incluyen subintervalos enteros y de caricter. A menudo. en lenguaje C sólo se permiten intervalos enteros comenzando en O. o se podría utilizar una cantidad más pequeña de memoria que fuera suficiente para representar todos los valores. En la clasificación de estos tipos es importante comprender la naturaleza del conjunto de valores que un tipo así representa. un procedimiento). así como tipos enumerados. Tales tipos con frecuencia son denominados tipos estructurados. Ilustraremos esto mencionando varios constructores conlunes y comparfíndolos con las operaciones de conjuntos. y sólo se especifica el tamaño en lugar del tipo-índice. define un tipo Ar que tiene una estructura de tipo equivalente al tipo precedente (suponiendo que Color tenga tres valores). En contraste.9. Por ejemplo. en donde uno es el tipo-índice y el otro el tipo-componente y produce un nuevo tipo array. Las implementaci~nes tanto de subintervalos como enumeraciones podrían ser como enteros. Dado un conjunto de tipos predefinidos. Se le utiliza para representar una función que no devuelva algún valor (es decir. mientras que un tipo enumerado compuesto de los valores nombrados red.Tipos de datos y verificacion de tipos 315 Un tipo predefinido interesante en el lenguaje C es el tipo void. En una sintaxis tipo Pascal escribiríamos array [tipo-índice]o£ tipo-componente Por ejemplo.green. la expresión tipo Pascal array [Color] o£ char. Ejemplos típicos de éstos son los tipos subrange (3ubintervalo") y los tipos enumerated ("enumerados"). De modo que no hay un equivalente directo en C para la expre\ión anterior tipo Paical. .green y blue puede declararse en C como tygedef enum ired. así que representa el conjunto vacío. En algunos lenguajes es posible definir nuevos tipos simples. pero la declaración de tipo en C typedef char ArI31. en Pascal el subintervalo de los enteros compuesto de los valores del O hasta el 9 puede declararse como type Digit = 0. Array (Arreglo) El constructor de tipo ( i r r q ("arreglo") o ("matriz") toma dos parámetros de tipo.blue) Color. se pueden crear nuevos tipos de datos utilizando constructores de tipo. Con frecuencia existen restricciones en los tipos que se pueden presentar como tipos de índice. Este tipo no tiene valores. un constructor de tipo equivaldrá estrechamente a una operación de conjuntos en los conjuntos de valores subyacentes de sus parámetros. En realidad.

.redl... la cual se puede para asignar valorés a componentes o buscar valores de componentes: x := a [r a [bluel := y. . Una. mientras que la in ción en el segundo índice produce el orden en la memoria' de a [O. 91 o£ array [Color] of integer. ya que los índices difer )..Color] o£ integer.mientras que en el s do caso escribimos a [ 1. Por lo regular.red]...redl .. red] = [red] entonces se debe utilizar la forma de renglón mayor. se puede usar la declaración en C void sort (int a[]. a los arreglos se les asigna almacenamiento contiguo desde lo más pequeños a los más grandes... o al con. para permitir el uso de cálculos de desplazamient máticos durante la ejecución. De este modo. La indización sobre el primer índice produce una secuencia en la memoria p aa[O.a[l. . .redl. . En este ejemplo otros parámetros lo hacen..bluel.redl..<.A< array [O.. entonces el conjunto de valores c diente al tipo array [tipo-índice] tipo-componentees el conjunto de secuen of tas de elementos de C indizados mediante elementos de I.. Las operaciones asociadas sobre los valores d arreglo se componen de la operación única de subindización. ' .~: La subindización en el primer caso aparece como a [l][red]. o. array ~0. ... .bl a[O. ? En ocasiones un lenguaje permitirá el uso de un arreglo cuyo intervalo de índices no esté especificado..) .red]. io se pueden agregar por separado: a [O] debe tener t p array [Color] of inte debe hacer referencia a una pomión contigua de memoria El lenguaje FORTRAN. si tipo-índicetiene conjunto de y tipo-componentetiene conjunto de valores C.'. .-a . Un arrelo de indización abierta es especialmente útil en la declaración de parámetros de arreglo para funciones. una variable del tipo array [O.bluel..bluel. Es decir.complicación en la declaración de arreglos es la de los arreglos muftidi nales.a[l.. integer necesita 40 bytes de almacenamiento si cada entero ocupa 4 bytes. dependiendo de si : la indización se hace primero en el primer índice y después sobre el segundo índice. en no hay indización parcial de arreglos multidimensionales. y así sucesivamente (esto se denomina forma de columna mayor).red]. < . int last) para definir un procedimiento de clasificación que funcionará en cualquier arreglo de tamaño a..9. (Por supuesto. La cantidad de memoria necesaria es n * tamaño.S " 2: -:.. F debe emplear algún método para determinar el tamaño real en el e momento de la llamada..green]. .a[l..a[2. ..greenl. como en . en términos matem conjunto de las funciones I -+ C.p ..~ .= .a[l. int first... Por ejemplo. a [O. o una versión más breve en la cual los conjuntos de índice se enumeran juntos: . 6 1 ANALISIS SEHANTICO Un arreglo representa valores que son secuencias de valores del tipo compone zadas mediante valores del tipo índice. Si la versión repetida de la declaración de arre multidimensional va a ser equivalente a la versión abreviada (es decir.:.-">.. se ha implementado tradicio mente utilizando la forma de columna mayor.. trario.a[l. a[9.a[O. . Un problema con los subíndices múltiples es que la se 'S cia de valores se puede organizar de diferentes maneras en la memoria. se denomina forma de renglón mayor).don el número de valores en el tipo de índice y tamaño es la cantidad de memoria neces ra un valor del tipo componente.. Éstos a menudo se pueden declarar utilizando aplicaciones repetidas del con de tipo arreglo.. y asísucesivamente(est . - ..CAP.. a [ 0. de manera que la función pueda manejar arreglos de diferentes tamaños.

. sólo que se utilizan nombres en vez de posiciones para tener acceso a los componentes. Así. el registro dado corresponde al producto cartesiano ( r X R) X ( i X 0. entonces x. i significa el valor de x como un entero. si x es una variable del tipo de registro dado. por ejemplo la declaración union í double r. mientras que x. Matemáticamente esta unión se define escribiendo (r X R) u ( i X 4. La interpretación que se desea se aclara mediante el nombre del componente utilizado para tener acceso al valor: si x es una variable del tipo unión dado. r se refiere a su primer componente y x. si un número real requiere cuatro bytes y un entero dos bytes. 3.14) = 3. Los valores de un tipo "registro" corresponden aproximadamente al producto cartesiano de los valores de sus tipos componentes. que se asignan como se muestra a continuación: (4 bytes) 1 (2 bytes) 1 Union (Unión) Un tipo unión corresponde a la operación de unión de conjuntos. 1 Los registros difieren de los arreglos en que se pueden combinar los componentes de diferentes tipos (en un arreglo todos los componentes tienen el mismo tipo) y en que se utilizan nombres (más que índices) para tener acceso a los diferentes componentes. Estos nombres generalmente se utilizan con una notación punto para seleccionar sus componentes correspondientes en expresiones. donde los nombres r e i identifican los componentes. con un bloque de memoria para cada tipo de componente. Los valores del tipo i n t * r e a l se escriben como tuplas. i n t i. De este modo. Está disponible directamente en C por medio de la declaración u n i o n .Tipos de datos y verificacibn de tipos 317 Record (Registro) Un constructor de tipo record o structure ("estructura")toma una lista de nombres y tipos asociados y construye un nuevo tipo.14. Un lenguaje de esta clase es ML. el registro previamente dado corresponde aproximadamente al producto cartesiano R X I. como en el ejemplo en C S t N C t í double r. i al segundo. 1 la cual define un tipo que es la unión de los números reales y los números enteros. r significa el valor de x como un número real.14) = 2 y snd(2. Estrictamente hablando. porque cada valor se visualiza como un real o como un entero. Algunos lenguajes tienen constructores de tipo de producto cartesiano puros. y se tiene acceso a los componentes mediante funciones de proyección f at y snd (de las palabras en inglésfirst y second): f st ( 2 . Por ejemplo. pero nunca como ambos. ésta es una unión disjunta. i n t i. entonces x. De manera más precisa. tales como (2.14) . donde R es el conjunto correspondiente al tipo double e I es el conjunto correspondiente a i n t .3. 3. El método de implementación estándar para un registro o producto cartesiano consiste en asignar memoria de manera secuencial. donde i n t * r e a l es la notación para el producto cartesiano de los números enteros y los reales. entonces la estructura del registro dado anteriormente necesita seis bytes de almacenamiento.

tiene un mecanismo semejante. isReal := trua. por ejemplo. como x. una unión así conduce a errores en la interpretación de los datos. end. lo que ya no provoca que se asigne espacio para el discriminante en la memoria. De este modo.0. s pnmirá un valor erróneo. que la uni6n se interprete como una junta. puesto que la representación de un entero no concordará con su correspo presentación como uno real. sin algún medio para que un programador ga los valores.x. pero insiste . el cual se utiliza para distinguir los valores deseados. pero en lugar de que se imprima el valor 2. i.de tipo. De esta forma. entonces la estructura de unión previamente dada necesita sólo cuatro b almacenamiento (el máximo de los requerimientos de memoria de sus componentes) ta memoria se asigna de la manera siguiente: r i (4bytes) ( 2 bytes) 1 Una implementación así requiere. Así. si x e variable que tiene el tipo unión dado. no se provocará un error de compilación. 6 1 ANALISIS SEMANTICO El método de implementación estkdar para una unión es asignar L memoria e a lo para cada componente. por otra parte. Esta inseguridad en los tipos unión ha sido abordada por muchos diseños de le jes diferentes. En Pascal. Por ejemplo en C.r = 2. también se hería.i).CAP.I $ record case boolean of tme:(r: real). este mecanis es relativamente inútil (al menos para el uso det compilador en verificación de tipo) to que el discriminante se puede asignar por separado de los valores que se están minando. y el campo isReal está asignado en un espacio separado que no se su pone en la memoria. un tipo unión se declara utilizando el denomi registro variante. y u de dos bytes. entonces. end. En realidad. hacer la asignación x. false:(i: integer). Ada. Efectivamente. falae:(i: integer). si un número real requiere de cuatro bytes. isReal (un valor boolea x . los compiladores de Pascal casi nunca intentan usar el discriminante como una verificación de legaiidad. 0 . de hecho. En realidad. grintf (''%d". Ahora una variable x de este tipo tiene tres componentes: x. e tenor tipo unión se puede escribir en Pascal como record case isñeal: boolean o£ tme:(r: real). r := 2 . y tam proporciona una forma para burlar a un verificador. r y x. Cuando se asigna un valor real. entodcesd escribir x. en el cual los valores de un tipo ordinal se registran en un compon discriminante. Pascal permite que el componente discriminante (pero no el de sus valores para distinguir los casos) se omita eliminando su nombre en la decla . de manera que la memoria para cada tipo del componen perponga con todas las demás.

Tipos de datos y verificación de tipos 319 en que. Function (funcih) Ya advertimos que un arreglo se puede ver conlo una función de su conjunto de índicer hacia su ~nnjunto componentes. ocho bytes. en Pascal. del modo que el producto cartesiano corresponde al constructor de registro. o en ocasiones. Por ejemplo. no puede ocurrir ningún error de interpretación. por ejemplo. los tipos apuntador ocupan una posición algo especial en un sistema de tipos. En PC basadas en DOS. Los tipos apuntador reciben espacio de asignación sobre la base del tamaño de dirección de la máquina objetivo. En Pascal el carácter A corresponde al constructor de tipo apuntador. Por lo regular. porque ellos "construyen" los valores de este tipo. De esta forma. tales como desplazamientos de suma y multiplicación por factores de escala. A veces. Los nombres IsReal e IsInteger se conocen como constructores de valor. con un tamaño de dos bytes) y apuntadores far (direcciones que pueden estar fuera de un segmento simple. Los tipos apuntador son muy útiles para describir tipos recursivos. una expresión de tipo equivalente es int *. con un parámetro entero y produciendo un resultado entero. se haee una distinción entre apuntadores near (para direcciones dentro de un segmento. En noracih matemática este conjunto se describiría coino . Puesto que siempre se deben utilizar cuando se hace la referencia a valores de este tipo. porque se pueden realizar cálculos aritméticos en ellos. Mediante los lenguajes funcionales. un vdor de un tipo apuntador es una dirección de memoria cuya ubicación mantiene un valor de su tipo base. como en (IsReal 2. el A representa el operador de desreferenciación (además del constructor de tipo apuntador). Por ejemplo. Al hacerlo así. Una regla similar se mantiene en C. De este modo. en Modula-2 la declaraciiin VAR f: PROCEDURE (INTEGER): INTEGER. y dando nombres a cada componente para discriminarlos. de manera que la expresión de tipo "integer significa "apuntador a un entero". Los tipos apuntador se imaginan con frecuencia como tipos numéricos. el constructor de tipo apuntador se aplica m& frecuentemente a tipos de registro. declara la variable f corno de tipo funci0ti ( o procedimiento). los cuales comentaremos en breve. No son en realidad tipos simples. En C. como el ML. como en IsReal o£ real I IsInteger of int Ahora también se deben emplear los nombres IsReal e IsInteger siempre que se utilice un valor de este tipo. Y si g es una variable del tipo "integer.0) o en (Iainteger 2). se adopta un enbque diferente. donde * desreferencia a una variable apuntador y se escribe *p. se debe asignar el discriminante de manera simultánea con un valor que pueda ser verificado para asegurar la legalidad. ya que se construyen a partir de tipos existentes mediante la aplicación de un constructor de tipo apuntador. Pointer (Apuntador) Un tipo pointer o apuntador se compone de valores que son referencias a valores de otro tipo. En este lenguaje. en cualquier momento que se asigne un componente de unión. La operación básica estándar en un valor de tipo apuntador es la operación de desreferenciación. Tampoco existe una operación de conjuntos estándar que corresponda directamente al constructor de tipo apuntador. un tipo unión se define utilizando una barrü vertical para indicar unión. Muchos lenguajes (pero no Pascal o de Ada) tienen una capacidad más general para describir tipos función.entonces g A es el valor desreferenciado de g y tiene tipo integer. una arquitectura de máquina obligará a un esquema de asignación más complejo. con un tamaño de cuatro bytes). éste es de cuatro.

En algunos lenguajes. El apuntador de ambiente se comentará en el capítulo siguiente. El lenguaje C también tiene. Por ejemplo. una declaración similar (pero sin los nombres de campo) es la siguiente: type RealIntRec = realeint. porque permiten el uso de car rísticas que están más allá del sistema de tipos. 6 1 ANALISIS IEMANTICO #f'+ .> int.42 Nombres de tipos. $& pe. Las declaraciones de clase pu crear nuevos tipos en un lenguaje orientado a objetos (en C+ lo hacen).. tales como la herencia y el enlace din Estas iíltimas propiedades deben ser mantenidas por estructuras de datos separada$ tal mo la jerarquía de clase (una gráfica acíclica dirigida). + 6. define el nombre RealIntRec como un nombre pan el tipo de registro construido por la expresión de tipo struct que lo precede. excepto porque incluye la definición de nes. int (*f) (int). En el lenguaje ML. declaraciones de tipo y tipos recunivos Los lenguajes que tienen un rico conjunto de constructores de tipo por lo regular tam tienen un mecanismo para que un programador asigne nombres a expresiones de tipo. donde I es el conjunto de los enteros. código en C typeaef struct (: double ri int i r ) RealIntRec. en una notación algo te@$ .. denominadas mbtodos o funciones miembro. puesto que las subclases se visualizan como subtipos (un tipo S es iin subtipo del tipo T si todos sus valores se pueden contemplar como v:ilores de T o. como en C+ t . la herencia se refleja o se duplica en el sistema de tipos.CAP. En el lengu&$$ --. la cual implementa la herenc la tabla de método virtual. el conjunto de funciones { jI -+ I ] . un tipo función puede necesitar tener espacio asignado a un apuntador de c<idigos6&8 (apuntando al código que implementa la función) o a un apuntador de código y un a de ambiente (apuntando a una ubicación en el ambiente en tiempo de ejecución).. si S c T). en terminología de conjuntos. . . Volveremos a habl estas estructuras en el capítulo siguiente.. T declaraciones de tipo (en ocasiones también conocidas como definiciones de tipo) i cluyen el mecanismo typedef de C y las definiciones de tipo de Pascal. Por ejemplo. Se asigna espacio a los tipos función de acuerdo con el tamaño de dirección de la m na objetivo. je ML este mismo tipo se escribiríacomo int . . Dependiendo del lenguaje y la organización del ambiente en tiempo de ej&&@# ción." '>. las declaraciones de clase no son únicamente tipos. 10. la cual implementa el enlace dinámico. Class (Clae) La mayoría de los lenguajes orientados a objetos tienen una declaraci semejante a una declaracián de registro. pero se deben escribir como "apuntador a función". Incluso si és caso.:y pos función. la declaración de Modula-2 que acabamos de dar en C se debe escnbiG& como .&@$ .

/ * . Esos atributos incluyen el ámbito (el cual puede ser inherente en la estructura de la tabla de símbolos) y la expresión de tipo correspondiente al nombre de tipo. Un lenguaje de esta clase es ML. ya que los nombres asociados con las declaraciones struct o union se pueden colver a utilizar como nombres typedef: struct RealIntRec { double r. / * declara x como una variable de tipo RealIntRec * / Las declaraciones de tipo ocasionan que se introúuzcan los nombres de tipo declarados en la tabla de símbolos de la misma manera que las declaraciones de variable determinan que se introduzcan los nombres de variables..Tipos de datos y verificación de tipos 32 1 El lenguaje C tiene un mecanismo de nombrado de tipos adicional en el que un nombre se puede asociar directamente con un constructor de struct o union sin utilizar una typedef directamente. la cual es diferente del nombre de tipo RealIntRec introducido mediante la typedef. En ML. árboles y muchas otras estructuras. Una cuestión que se presenta aquí es si se pueden o no volver a utilizar los nombres de tipo como nombres de variable. como la unión del valor Ni1 con el producto cartesiano tle los enteros con dos copias (le intBST m i m o (una para el . 1. por ejemplo. Por lo regular no se puede (excepto cuando se permite mediante las reglas de anidación de ámbito). también declara un nombre de tipo RealIntRec. El primer grupo se compone de aquellos lenguajes que permiten el uso directo de la recursividad en declaraciones de tipo. Los nombres de tipo están asociados con atributos en la tabla de símbolos en una forma similar a las declaraciones de variable. Por ejemplo. un &bol de búsqueda binaria que contiene enteros se puede declarar como datatype intBST = Ni1 I Node o£ int*intBST*intBST Esto se puede ver como una definición de intBST. typedef struct RealIntRec RealIntRec.pero se debe usar con el nombre de constructor struct en declaraciones de variables: struct RealIntRec x.es una declaración legal! * / Esto se puede lograr considerando que el nombre de tipo introducido mediante la declaración struct es la cadena completa "struct RealIntRec". int i. El lenguaje C tiene una pequeña excepción a ebta regla. se presentan cuestiones acerca del uso recursivo de los nombres de tipo que son similares a las definiciones recursivas de las funciones analizadas en la sección anterior. Estos tipos de datos recursivos son muy importantes en los lenguajes de programación modernos e incluyen listas. el código en C struct RealIntRec i double r. Como los nombres de tipo pueden aparecer en expresiones de tipo.o bien. int ii ). Los lenguajes se dividen en dos grupos generales en su manejo de los tipos recursivos.

struct intBST *left. (En C.subárbol izquierdo y una para el subárbol derecho). C es representativo del seg grupo de lenguajes: aquellos que no permiten el uso directo de la recursividad en decl ciones de tipo. ento debe hacer tales declaraciones de tipo recursivo ilegales. Una declaración equivalente en Forma ligeramente alterada) es struct intBST { int isNull. La solución para tales lenguajes es permitir la recursividad sólo indirectamen través de apuntadores. typedef struct intBST * intBST. Tales dades para la.4. En esta sección comentaremos brevemente sólo las formas más comunes de equivalencia.right. int val.right. 1. sin embargo. pero el espacio para los valores se debe asignar manualmente por el programador a través del uso de procedimientos de asignación tales como malloc.*right. Esta declaración. En este análisis representamos la equivalencia de tipos como sería en un analizador semántica de compilador. Ésta es la cuestión de la equivalencia de tipos. typedef struct intBST struct intBST { int val. como una función . intBST 1eft. administración de memoria son parte del ambiente en tiempo de ejecuc se comentan en el capítulo siguiente. y ésos son los lenguajes que proporci un mecanismo de asignación y desasignación de memoria automático general. el cual es causado uso recursivo del nombre del tipo intBST.) En estas declaraciones el tamaño de la memoria de cada tipo se calcula directamente por el compilador. Existen muchas maneras posibles para que la equivalencia de tipos sea definida por un lenguaje. a saber. 6. un verificador de tipo con frecuencia drbe responder la cuestión de cuándo dos expresiones de tipo representan al mismo tipo. struct intBST left. que pueden aceptar tales declaraciones son aquellos q necesitan tal información antes de la ejecución. Una declaración correcta para intBST en C es struct intBST ( int val. como ML.El problema es que estas declaraciones n terminan el tamaño de la memoria necesaria para asignar una variable de tipo intBS clase de lenguajes. las declaraciones recursivas requieren del uso de la forma struct o union para la declaración del nombre de tipo recursivo.3 Equivalencia de tipos Dadas las posibles expresiones de tipo de un lenguaje. * intBST. generará un mensaje de error en C. 1. C no tiene un mecanismo de esta naturaleza.

Sin embargo. no hay declaraciones de tipo que permitan la asociación de nuevos norribres de tipo a expresiones de tipo (por lo tanto. Para tener un ejemplo concreto de tales representaciones considere la gramática para expresiones de tipo y declaraciones de variable dadas en la figura 6.Tipos de datos y verificación de tipos function t y e E q u a l ( r l .a p type-exp -+ simpie-<v. Considcre. a pesar de la presencia de tipos apuntador). Un inbtodo directo es utilizar una representación de árbol sintáctico.s end 1 union wr-clecls end pointer to iypc-e.vur-rlecls . t2 :TypeExp ) : Boolmn. Una cuestion que se relaciona directamente con la descripcitín de los algoritmos de equivalencia de tipos es la manera en que se representan las expresiones de tipo dentro de un compilador.rpl. la expresión de tipo record x : gointer t o real. En esa figura querernos describir una posible estructura de árbol sintáctico para expresiones de tipo correspondiente a las reglas gramaticales. iar-decl -+i d : g p e . no son posibles los tipos recursivos. ya que esto facilita traducir directamente desde la sintaxis en declaraciones hasta la representación intema de los tipos.vcir-cled / vczr-clecl .oe 1 structured-type simple-&!?e -+ int / bool 1 real / char 1 void crruclured-íype array inuml of rype-erp / record inr-t1ecl. y: a r r a y [lo] of i n t end Esta expresión de tipo se puede representar mediante el árbol sintáctico Aquí los nodos hijos del registro están representados corno una lista de hermanos.s ) Iyx-o-ql - .rp j proc ( l'pe-r. Allí aparecen versiones simples de muchas de las estructuras de tipo que hemos analizado.19 Una gramatica simple pan expresiones de tipo vur-decls . puesto que el número de componentes de un registro es arbitrario. Figura 6. que toma dos expresiones de tipo y devuelve verrkdero si representan el mismo tipo de acuerdo con las reglas de equivalencia de tipo del lenguaje yj¿t/. en primer lugar. Daremos varias descripciones en pseudocódigo diferentes de esta función para distintos algoritinos de equivalencia de tipos.19.so si no es así. Advierta que los nodos que representan tipos simples forman las hojas del árbol.

tras que el tipo de resultado (en este ejemplo void) se distingue haciéndolo direct un hijo de proc.20 proporciona una descri en pseudocódigo de la función typeEqual para la equivalencia estructural de dos expre nes de tipo dadas por la gramática de la figura 6. Por ejemplo. La primera clase de equivalencia de tipo que describimos. Es posible hacer diferentes lecciones en un algoritmo de equivalencia estructural. y también se podda permitir q los componentes de una estructura o unión se presenten en un orden diferente. Como un ejemplo de c equivalencia estructural se verifica en la práctica. utilizando la estructura de árbol sin tico que acabamos de describir. Con estas declaraciones ya no se puede escribir record x: pointer to real. t3 = record x: tl: y: t2 end .CAP. En figura 6. la expresión de tipo groc(boo1.int):void se puede representar mediante el árbol sintáctico * real char Advierta que los tipos de parámetro también están dados como una lista de hermanos. Observamos del pseudocódigo de la figura 6. t2 = array [lo1 of int. y: array [lo] of int end en vez de eso debemos escribir tl = pointer to real.19 para incluir declaraciones de tipo también restringimos declaraciones de variable y subexpresiones de tipo a tipos simpl y nombres de tipo. Una equivalencia de tipos mucho más restrictiva se puede definir cuando se pued declarar nuevos nombres de tipo para expresiones de tipo en una declaración de tipo. Si los árboles sintá emplean para representar tipos. b:char end.20 que esta versión de equivalencia es tural implica que dos arreglos no son equivalentes a menos que tengan el mismo tamañ tipo de componente. esta regla nos dice que dos tipos son equivalentes si si tienen árboles sintácticos que sean idénticos en estructura.21 modificamos la gramática de la figura 6. y la única disponible en sencia de nombres para tipos.19. es la equivalencia estructural. y dos registros no son equivalentes a menos que tengan los mism componentes con Los mismos nombres y en el mismo orden. 6 1 ANALISIS SEMAN'TIEO De manera similar.union a:real. el tamaño del m e g se podría ignorar en la determinación de la equivalencia. En esta vista de equiva dos tipos son los mismos si y sólo si tienen la misma estructura. la figura 6.

cluse = proc then begin pl := t1.hijo2 ) end else return falso .cluse = apuntador and r2. p2 : TypeExp . p2 := p2. = end. end.hijol.clase = unión and t2. begin if t l y t2 son de tipo sirnple then return tl = t2 else if t1. temp := verdadero . hermano .hijol ) then temp := falso else hegin p1 := pl. p2 : p2.nombt-e then temp := falso else if not typeEqual ( pl. p2.hijo2.hijol .p2.clase = arreglo then return tl.hijo1.hijol . end else if tl. temp := verdadero . return temp and pl = ni1 and p2 = ni1 .rlase = registro and t2.12 : TypeExp ) :Boolean.clase = unión then hegin pl := tl.hijol .Tipos de datos y verificación de tipos pleudo~Ódig~ una para función iypeEqual que pnieba la equivalencia de t~pode la gramática de la figura 6. return temp and pl = ni1 and p2 = ni1 and iypeEqual( tl.tamaño = t2. ( * typeEqual *) . pl.clase = registro or tl. t2. p2 := t2.tamaño and typeEquul( tl.clase = proc and t2. hermano .clase = urreglo and t2. while temp and pl # ni1 and p2 # ni1 do if not typeEqual (pl.19 function t)?peEquul(11.t2. t2. var temp : Booleun .hemtuno .hijol . while temp and pl # ni1 and p2 # ni1 do if pl.nombt-e # p2.hijol ) else if tl.hijol .hermano . p2 := t2. end .hijol .hijol ) then tenzp := falso else begin pl := pl.clase = apuntador then return typeEquul ( tl.hijol ) else if tl.

21 Expresiones de tipo con declaraciones de tipo var-decls -t vnr-ciecls . var-decl / vnr-decl var-decl -+ id : simple-type-exp type-decls -+ type-decls .La equivalencia de nombre pura es muy fácil de implement ya que una función typeEqual se puede escribir en unas cuantas líneas: function typeEqual ( t l . simple-iype-exp 1 . dadas las declaraciones de variable . Por cjemplo.simple-tyl~e-exp Ahora podemos definir la equivalencia de tipos basados en nombres de tipo. continúan permitiéndose en declaraciones de variable. puesto que ahora. És una clase muy fuerte de equivalencia de tipo. Una complicación en la equivalencia de nombre es cuando otras expresiones de tipo además de los tipos simples o nombres de tipo. type-decl / type-decl type-decl-+ id = type-exp iype-exp -+ simple-type-exp / structured-type sirtple-type-exp -+ simple-type / id simple-iype -+ int / bool / real / char / void structnr-ed-type -+ array [num] of simple-iype-exp / record var-decls end / union var-decls end / pointer to .simple-type-exp / proc ( type-exps ) simple-tye-e. las expresiones de tipo reales correspondientes a nombres de tipo se deben introducir en la tabla de símbolos para permitir el cálculo posterior del tamaño de memoria para asignación de almacenamiento y para verificar la validez de operaciones tales como la desreferenciación de apuntador y selección de componente. En tales casos una expresión de tipo puede no tener un noinbre explícito asignado. 6 1 ANALISIS SEH~NTICO Fipurt 6. t2 = int tos tipos tl y t2 no son equivalentes (porque tienen nombres diferentes) y tampoco s equivalentes respecto a int. una f o m equivalencia de tipos que se denomina equivalencia de nombre: dos expresiones de son equivalentes si y sólo si son el mismo tipo simple o el mismo nombre de tipo. eud. p l . p2 : TypeExp . begin i f t l y t2 son de tipo simple then return t i = t2 else if t l y t2 son nombres de tipo then return t l = t2 else return$zlse . var temp : Boolean . dadas las declaracione tipo tl = int. o como subexpresiones de expresiones tipo. Naturalmente. t2 : TypeExp ) :Boolenn.wp type-exps -+ type-exps .CAP. y un compilador tendrá que generar un iiombre interno para la expresión de tipo que es diferente a cuülquicr otro nombre.

Entonces esta llamada inmediatamente devolvertí true. se puede declarar un éxito en ese punto. Finalmente. t2). t2 = record x: int. Considere. ~xrqw existe scílo un número Iiriito de nombres de tipo en cualquier programa dado. Dejamos los detalles de las tnodificaciones al pseudocódigo de tjpeEqiud para un ejercicio (véase tamhiin la seccitín de notas y rel'erenci:~~). t: pointer to t2. Esto se puede llevar a cabo agregando el caso siguiente al ctídigo de la figura 6. o cuai~do menos aquellas cadenas de nombres de tipo resultantes de declaraciones de tipo tales como t2 = tl. porque el algoritmo que se acaba de describir puede resultar en un bucle infinito.Tipos de datos y verificación de tipos x: array [lo1 of int: y: array [lo1 o£ int. por ejemplo. Dada la Ilamada typeEqwl(tl. En este caso. la función typeEqual supondrá que t l y 12 son potencialmente iguales. int. . Entonces.yppeEquul(t1 t2) en el caso en que t l y t2 son nombres de tipo para incluir la suposición de que ya son potencialmente iguales.para analizar los tipos tl) hijos de las declaraciones de apuntador. En general. Debe tenerse cuidado al implementar la equivalencia estructural cuando sean posibles referencias de tipo recursivos. no puede entrar en un bucle infinito). cnsndo se encnentra un nombre. si la función ~ p e E q u a lsiempre regresa a la misma llamada. por supuesto. Esto se puede evitar modificando el coniportamiento de la llamada t.~pítl). este algoritmo necesitará efectuar suposiciones sucesivas de que los pares de nonrbres de tipo pueden ser iguales y ncumular las suposiciones en una lista. end. las declaraciones de tipo ti = record x. else if t l y t2 son nowzbres de tipo then getTvpeExp(t2)) return @prEquat(getTvl>~~E. Esto requiere que se inserte cada nombre de tipo en la tabla de símbolos con una expresión de tipo que represente su estructura. Es posible conservar la equivalencia estructural en presencia de nombres de tipo. conduzcan finalmente de regreso a una estructura de tipo en la tabla de sí~ribolos.20. debido a la suposición de igualdad potencial que se hizo en la llamada original. end . t: pointer to tl. las variables x y y tienen asignados nombres de tipo diferentes (y únicos) correspondientes a la expresión de tipo array t 101 of i n t . su expresión de tipo correspondiente debe obtenerse de la tabla de símbolos. el algoritmo debe obtener ixito o fallar (es decir. donde jietTyptErp es una operación de tabla de símbolos que devuelve la estructura de expresión de tipo asociada con su parimetro (que debe ser un nombre de tipo). Entonces se obtendrán las estructuras tanto de t l como de t2. y el algoritmo continuará con éxito hasta que se haga la llamada typeEq~~al(t¿>.

por ejemplo en la declaración type RealIntRec = real*int. la declaración datatype crea un tipo completamente nuevo. los nombres de tipo t 1 y t 3 son equivalentes bajo la equivalencia de declaración. a diferencia de una declaración t y p e . todo nombre de tipo es equivalente a nombre de tipo base. la cual obtiene el nombre de t base en vez de la efpresi6n de tipo asociada. Observe que la equiva de declaración. donde se utilizan diferentes declaraciones de tipo para diferen formas de equivalencia. como la declaración datatype intBST = Ni1 I Noae of int*intBST*intBST Advierta que una declaración d a t a t y p e también debe contener nombres de constructor de valores (Ni1 y Node en la declaración dada). 6 1 ANALBIS SEMANTICO Una variación Final acerca de la equivalencia de tipos es una versión menor de I valencia de nombres utilizada por Pascal y C. las declaraciones del tipo siguiente t2 = tl. Por ejemplo. pero n guno es equivalente a t 2 . ta = int tanto t l como t 2 son equivalentes a i n t (es decir. En esta versión de equivalencia de tipo. Pascal utiliza de manera uniforme la equivalencia de declaración. un lenguaje ofrecerá una selección de equivalencia estructural. denominada equivalencia de declaraci este tipo de método. Un nombre tipo puede distinguirse en la ta de símbolos como un nombre de tipo base si es un tipo predefinido o si está dado por expresión de tipo que no sea simplemente otro nombre de tipo.CAP. mientras que C e plea equivalencia de declaración para estructuras y uniones. debe proporcionarse una nueva o ración getBaseTypeName mediante la tabla de símbolos. Para implementar la equivalencia de declaración. se interpretan como las que establecen alias de tipo. pero equivalencia estructur para apuntadores y arreglos. declaración o de nombre. Esto declara RealIntRec como un alias del tipo de producto cartesiano r e a l * i n t . Ocasionalmente. o está dado mediante u presión de tipo resultante de la aplicación de un constructor de tipo. similar a la equivalencia de nombre. resuelve el problema de bucles 1 tos en la verificación de tipos recursivos. el cual es. el lenguaje ML permite que los nombres de tipo se claren como alias utilizando la palabra reservada t y p e . De este modo. o bien un tipo predefinido. t2 = array [lo] o£ int. Esto . en vez de nuevos tipos (como equivalencia de nombre). dadas las declaraciones tl = int. puesto que dos nombres de tipo base sólo pue ser equivalentes en declaración si son el mismo nombre. Por ejemplo. t3 = tl. son sólo alias para el nombre del ti i n t ) . Por otra parte. dad declaraciones tl = array [lo] of int.

y utilizamos el símbolo := en vez del de igualdad en las reglas de la tabla 6. Estas acciones no e s t h dadas en forma de gramitica con atributos pura. con un pequeño número de expresiones y senteiicias agregadas. También suponemos la disponibilidad de una tabla de símbolos que contenga nombres de vliriable y tipos asociados.10) es del tipo RealIntRec o real*int. la cual incluye un pequeño subconjunto de las expresiones de tipo de la figura 6. que devuelve el tipo asociado de un nombre. basadas en una representación de tipos y una operación iypeEquu1 como se comentó en sesiones anteriores. mientras que e1 valor Prod(2. El tipo asociado en esta inserción se construye de acuerdo con las reglas gramaticales para iype-exp. .19.10 (página 330). la regla gramatical tiene la acción semántica asociada que inserta un identificador en Id tdbla de símbolos y asocia un tipo a1 mismo. y de búsqueda (lookup). vur-decl / var-decl var-deel-+ id : type-uxp type-exp -+ int / bool / array [numl of iype-exp sents -+ sents .22 Una gramática simple para ilustrar la venficauán de tipos programa -+ var-decls . el valor (2.7. Analizamos las reglas de verificación de tipo y la inferencia de tipo para cada clase de construcción de lenguaje de manera individual.22.lO) es del tipo NewReallnt (pero no real*int). 6.4.sent 1 sent sent 4 if exp then sent / id := exp Derlanc. las cuales insertan un nombre y un tipo en la tabla.4 Inferencia de tipo y verificación de tipo Procederemos ahora con la descripción de un verificador de tipo para un lenguaje simple en términos de acciones semánticas.7. La lista completa de acciones semánticas se proporciona en la tabla 6.Tipos de datos y veriticacidn de tipos 3 29 hace los valores del nuevo tipo distinguibles de los valores de tipos existentes. No especificaremos las propiedades de estas operaciones en sí mismas en la gramática con atributos. dada la declaración datatype NewRealInt = Prod of real*int.ones Las declaraciones causan que el tipo de un identificador se introduzca en la tabla de símbolos. De este modo. De este modo. El lenguaje que usamos tiene la gramática dada en la figura 6. con operaciones de inserción (insert).senis vur-decls 4 var-rlecls .10 para indicar esto. Figura 6.

..S .. + exp3 Reglas sem&nticas insen(id .rp iype-exp . Se supone que los tipos simples itltejier y hooleun se construyen como nodos de hoja estándar en la representación de árbol.12 Regla gramatical vur-decl-.ype) if not typeEquul(exp..tye := integer type-exp. . type) la cual construye un nodo de tipo donde el hijo del nodo iway (arreglo) es cl árbol de tipo dado por el parámctro Vpe.. . ~.) .type.type..rp2 seut -+ i f esp then sent sent -+ i d := exp erp.fype.. .type := integer if not (typeEquul(exp2. .~ ~ .CAP..5 .oe := boolean exp.type := exp2. . -+ a r r a y [numl of type-e.me := inteper erp. i d : type-e. type-exptype) ype-e. . exp...type := lookup(id . booleun) and typeEqual(exp. .p ! . . ~~:.~ ... ' . .22 corresponde a la acc' semántica makeType&de(array size. . integer) then expl ... ~ . Situaciones rípicas se muestran mediante las dos . . Sentencias Las sentencias no tienen tipos en sí mismas. integer) and typeEquulfexp3..ype.. ype-exp2 . boolean)) then type-errotfexp. e. .ia. .finme) x. o r e. -+ exp. ...: ..ype := booleun if isArruyType(uxp2 .name.type.type) then type-errorfsent) if not (iypeEqualfexp.. exp.. sype := makeTypeNode(urrav....i n t i tvpe-exp -+ bool type-e.type. .nome).rp.rp.rn -+ nwn iirp -+ t r u e exn -+ f a l s e exp -+ i d Se supone que los tipos se mantienen como alguna clase de estructura de árbol.:S : .10 Gramática con atributos para veriíícación de tipos de la gramatica simple de la figura 6.: . .. num sire. . integer)) then type-error(expl). -+ exp.childl else type-error(e.type) and typeEquul(exp.. ... . sino subestructuras que es necesario verificar en cuanto a la exactitud de tipo.rpI) exn.. boolean) then type-errotfscnt) if not typeEqual(lookup(id .. de mane que el tipo estructurado array en la gramatica de la figura 6.type := boolean e. . 6 1 ANALISIS SEHANTICO Tabla 6.* .rp3 exp 1 -t expz i exp3 1 r...rype. exp..rp..n.type := boolean type-exp.

childl iype-errotfexp.10 utilizan ese hecho para asignar un tipo al resultado. como se indica mediante el procedimiento type-error en las reglas semánticas de la tabla 6. puede hacerse la suposición de que cl resultado que es significativo es de tipo entero o booleano. Si OpeError encuentra este tipo de error en una subestructura.. El resto describe el comportamientode un verificador de tipo así en la presencia de errores. entonces no se genera ningún mensaje de error. entonces debería suprimirse la generación de un mensaje de error. y las reglas de la tabla 6. la sentencia if y la sentencia de asignación.tpe. Al mismo tiempo. Por ejemplo.rp. un error simple podría causar que se imprimiera una cascada de muchos errores (algo que también puede pasar con errores de análisis sintáctico). En el caso de la sentencia de asignación. un tipo válido. entonces el verificador de tipo puede usar el tipo de error como su (en realidad desconocido) tipo. esto se indica mediante la regla if isArrayType(exp2. integer) then e.xp31. la expresión condicional debe tener tipo booleano.type) and typeEqual(e. En cada caso las subexpresiones deben ser del tipo correcto para la operación indicada.rp. tales como el operador aritmético +. El tipo de la expresión de subíndice resultante es el tipo base del arreglo. entonces no se puede asignar a C A P . como se expresó mediante la función typeEqua1. si expz no es un tipo arreglo. el operador booleano o r y el operador de subíndice [ 1. . dada una expresión con subíndice expl --+ e. y no hay asignación de un tipo en las acciones semánticas. En su lugar. En el caso de la sentencia if. Esto se puede señalar teniendo un tipo de error interno especial (el cual se puede representar mediante un árbol de tipo nulo). No se debería generar un mensaje de error cada vez que ocurre un error de tipo. si el procedimiento type-error puede determinar que un error de tipo ya debe haber ocurrido en un lugar relacionado.. Las cuestiones principales son cuándo generar un mensaje de error y cómo continuar la verificación de tipos en la presencia de e m e s . de otro modo. incluso en la presencia de un error de tipo. Esto se indica mediante la regla if not typeEyuul(exp. tienen tipos implícitamente definidos integer y boolean.wp2 ie. el requerimiento es que la variable que se está asignando debe tener el mismo tipo que la expresión ciiyo valor está por recibir. y los valores booleanos t r u e y f a l s e . Esto depende del algoritmo de equivalencia de tipos. es decir. Esto supone que los campos de tipo se han inicializado para algún tipo de error.Tipos de datos y veriiicacián de tipos 33 1 reglas de sentencias en la gramática simple. Otras expresiones se forman mediante operadores. en el caso de las operaciones + y or.10...type := e-~p~. el cual es el tipo representado por el (primer) hijo "child" del nodo raíz en la representación de árbol de un tipo arreglo. En el caso de los subíndices.) else Aquí la función isArrayType prueba que su parhetro es un tipo arreglo ("array"). boolean) then type-errar (sent) donde fype-error indica un mecanismo de informe de error cuyo comportamiento se describirá en breve. tales como números. Los nombres de variable tienen sus tipos determinados mediante una búsqueda en la tabla de símbolos.childl.type. y esto se indica escribiendo exp. si un error de tipo también significa que no se puede determinar el tipo de una cstructura. en las reglas semánticas de la tabla 6.type. Expresiones Las expresiones constantes. que la representación de árbol del tipo tenga un nodo raíz que represente el constructor de tipo arreglo.10. Por otra parte.type.

5 Temas adicionales en la verificación de tipos En esta subsecciún comentaremos brevemente algunas de las extensiones comunes p algoritmos de verificación de tipo que hemos analizado.donde se suma número real y un número entero. una declaración así es legal. En Pascal y C esto es ilegal. 2+3 representa la suma entera. donde se puede emplear el mismo nombre para ope nes relacionadas.y: inteser): integer. ya que representa una declaración repetida del mismo no dentro del mismo ámbito. Coerción y conver@n de tipor Una extensión común de las reglas de tipo de un lenguaje permitir expresiones aritméticas de tipo mezclado. donde un tipo único puede ser determinado de inniediato. Una sobrecarga así se puede extender a procedi y funciones definidos por el usuario. pero definido para parámetros de tipos diferentes. Ex1 dos métodos que un lenguaje puede tomar para taies conversiones.1 + 3.el valor entero 3 se debe convertir a p flotante antes de la suma. / \. como en .4.(diferencia en el tipo implica la conversión de 3 a 3. Una manera seda aumentar el procedimiento de búsqueda d tabla de símbolos con parámetros de tipo. po desear definir un procedimiento de valor máximo para valores tanto enteros como re procedure max íx. En tales casos. O representa la de punto flotante. mientras que 2 .1+3 . Un uso así de verificación de tipo para eliminar la atnbigüed significados múltiples de nombres puede implementarse de varias maneras. Por ejemplo. que debe ser implementada de manera interna mediante una instru conjunto de instrucciones diferente. dependi de las reglas del lenguaje. ya que el tipo inferido de una expresión cambia a partir de una subexpresión.CAP.y: real): real. Esto es útil en situaciones más complejas. Una solución diferente para la tabla de símbolos es mantener c juntos de tipos posibles para los nombres y devolver el conjunto al verificador de tipo p eliminar la ambigüedad. permitiendo que la tabla de símbolos encuen la coincidencia correcta. La otra posibilidad (utilizada en el lenguaje C) es que el verificador de tipo suministre la operación de conversión de manera automática. por ejem requiere que e l programador suministre una función de conversión. en Ada y Cf +.1 + FLOAT (3) . lobrecarga Un operador está sobrecargado si se utiliza el mismo nombre de operad dos operaciones diferentes. tal como 2. procedure max (x. en la expresión 2 1 + 3. La coerción se puede expresar de manera implícita mediante el verificador de tipo. Un ejemplo común de sobrecarga es el caso de los ope aritméticos. que con frecuencia representan operaciones sobre valores numéricos difere Por ejemplo. Modula-2.0) . Una conversión automática de esta naturaleza se conoce como coerción. debe hallarse un tipo común que sea c patible con todos los tipos de las subexpresiones. y la expresión resultante tendrá el tipo de punto flotante. de manera q ejemplo que se acaba de dar debe escribirse como 2 .o se causará un error de tipo. 6 / ANÁLISIS SE~AXTICO 6. basándose en los tipos de las s bexpresiones. Por ejemplo. y deben aplicarse operaciones para co vertir los valores en el tiempo de ejecución a las representaciones apropiadas antes de apli el operador. po el verificador de tipo puede decidir cual procedimiento max es significativo con base e tipos de los parámetros. Sin embargo.

más que un tipo real. y un verificador de tipo tlek determinar un tipo real en toda situación donde se utilice swap que coincida con este patr6n de tipo « declarar un crror de tipo. Por ejemplo. tal como en C.jemplo. El tipo de este procedimiento swag ( " i n t e r c a m b i o " ) se dice que está parametrizado mediante el tipo anytype ( " c u a l q u i e r t i p o " ) y anytype se considera una variable de tipo. donde generalmente es permitida la asignación de objetos de subclases a objetos de superclases (con una correspondiente pérdida de información). dado cl c6ctigo . Por e. como en Las coerciones y conversiones de tipo también se aplican a las asignaciones. Hasta ahora hemos analizado la verificación de tipo para un lenguaje que es esencialmente monomórfico debido a que todos los nombres y expresiones que se requieren tienen tipos únicos. un procedimiento que intercambia los valores de dos variables podría en principio aplicarse a variables de cualquier tipo (mientras que tengan el mismo tipo): procedure swap (var x . pero no lo contrario. y si x está en A y además y está en B. Por ejemplo. como podría ocurrir si la asignación se hiciera en la dirección opuesta (también legal en C): Una situación similar ocurre en los lenguajes orientados a objetos.) +. irnrnnytype): void donde cada ocurrencia de cinyiype se refiere a1 mismo (pero no especificado) tipo. Podemos expresar el tipo de este procedimiento como procedure(vnr anytype. Tales asignaciones pueden perder información durante la conversión. se aplica sólo a situaciones donde se realizan v¿uias declaraciones por separado al mismo nombre. se sujeta el valor de i a d o u b l e antes de ser almacenado como el valor de r . si r es de tipo d o u b l e e i es i n t . la conversión se puede suministra de manera explícita mediante el verificador de tipo insertando un nodo de conversión en el árbol sintáctico. en donde. entonces la asignación x=y está permitida. Pero la sobrecarga. fipificado polimórfco Un lenguaje es polimórfico si permite que las construcciones de lenguaje tengan más de un tipo. en C+ si A es una clase y B es una subclase. y : anytme) . Una situación diferente ocurre cuando sólo una declaración se puede aplicar a cualquier tipo.Tipos de datos y veriíicación de tipos 333 Esto requiere que un generador de código posterior examine los tipos de expresiones para determinar si se debe aplicar una conversión. (Esto se conoce como principio del subtipo. De manera alternativa. la cual puede suponerse de cualquier tipo real. Un tipo así es en reialidad un patrón de tipo o esquema de tipo. aunque es una forma de polimorfismo. Una relajación de este requerimiento de monomorfismo es la sobrecarga.

.

write fact { salida del factorial de x end ) Después de la generación de la tabla de símbolos para este programa. Sin embargo. primero debemos determinar qué información necesita mantenerse en la tabla. Como un ejemplo de la información que se generará mediante la tabla de símbolos. Luego describiremos la operación del analizador semántico mismo. el analizador semántica ofrecerá como $alida (con TraceAnalyze = TRUE) la 5iguiente información al archivo del listado: Symbol table: Variable Name x Location O Line Numbezs 5 7 f act 1 6 9 9 9 10 12 10 11 Advierta que niírltiples referencias en la misma Iínea generan múltiples entradas para esa Iínea en la tabla de símbolos. 6. regeat fact := fact * x. x : = x . puesto que no hay declaraciones en el árbol sintáctico. Por ahora. Primero comentaremos la estructura de la tabla de símbolos y sus operaciones asociadas.51 Una tabla de sirnbolos para TINY En el diseño de la tabla de símbolos para el analizador semántico de TINY. considere el programa muestra de TINY (con números de Iínea agregados): { Programa simple en lenguaje TINV calcula el factorial -- 1 read x. En casos típicos esta información incluye el tipo de datos y la información de ámbito. la tabla de símbolos es el lugar lógico para almacenar estas ubicaciones.1 until x = O. las variables necesitarán ser asignadas en ubicaciones de memoria y. . Como TINY no tiene información de ámbito. Con el fin de hacer más interesante y más útil la tabla de símbolos. y puesto que todas las variables tienen tipo de datos entero. f introduce un entero 1. las ubicaciones se pueden visualizar simplemente corno índices enteros que se incrementan cada vez que se encuentra una nueva variable. también la utilizamos para generar un listado de referencia cruzada de números de línea donde se tiene acceso a las variables. una tabla de símbolos de TINY no necesita contener esta información. durante la generación del código.Un analizador semantico para el lenguaje TINY 335 Separaremos el anilisis del código para el analizador semántico TINY en dos partes. if O c x then I no calcule si x <= O 1 fact := 1. incluyendo la construcción de la tabla de símbolos y la verificación de tipo.

6 1 AN~LISISSEMANTICO El código para la tabla de símbolos está contenido en los archivos symtab. la tabla de símbolos se puede cons diante uñ recorrido preorden del árbol sintáctico. a fin de mantener el orden numérico. insertando tipos de datos en los nodos del .1321. De modo. La estructura que usamos para la tabla de símbolos es la tabla de dispersión enca da separadamente como se describió en la sección 6. Iíneas 1350-1370).52 Un analizador semantico para TlNY La semántica estática de TlNY comparte con los lenguajes de programación estándar I propiedades de que la tabla de símbolos es un atributo heredado. respectivamente). mientras que el tipo de d tos de una expresión es un atributo sintetizado. void typeCheck(TreeN0de *). la verificación de tipos se puede e mediante un retorcido postorden.13 (pigina 298). c utiliza una lista ligada diná mente asignada con un nombre de tipo LineLf st (líneas 1236-1239) para almacenar números de Línea asociados con cada registro de identificador en la tabla de disp Los registros de identificador en sí mismos se conservan en una cubeta c m nombre BucketList (Iíneas 1247-1252). Como no hay infomacidn de &hito. Las otras do raciones necesarias son un procedimiento para imprimir la información del resumen se acaba de mostrar para el archivo del estado. y una operación de búsqueda lookup p tener los números de ubicación de la tabla (necesarios más adelante para el generador digo.h (apéndice B. se compone de dos procedimi tos. void grintSyloifab(F1LE * listing). El procedimiento st-insert (líneas 1262-1295) ga nuevos registros de identificador al frente de cada cubeta. Mientras que estos dos recorridos se pueden combi cilmente en uno solo. el archivo de cabecera symtab. llamando al procedimiento st-insert de la tabla de símbolos a medida que encuentra id&iicadores de wíable en el árbol. ni necesita aparecer como un parhetro para estos procedimient El código de implementación asociado en symtab. ). Puesto que sólo existe una tabla de símbolos.1179 y 1200.3. pero los números de Ií agregan al final de cada lista de números de línea. c listados en el apéndice B (líneas 1150. y la función de dispersión es 1 se dio en la figura 6. dados por las declaraciones y void buildSymtab(TreeNode *). para hacer clara la distinción de las operaciones de ambos pasos procesamiento. que ubicano el archivo analyze . Después que se completa el lransver\al. véanse los ejercicios. no hay n dad de una operación de eliminación delete.CAP. eficiencia de st-insert se podría mejorar utilizando una lista circular de apuntadores perioreslinferiores para la lista de números de línea. y también para el constructor de la tabla de símbolos a fin de verificar si ya visto una variable). su estructura no necesita ser declarad archivo de cabecera.1. Así. la interfase del analizador semántico para el restodel compilador.) 6. int loc int st-lookup ( char * name ). además del identificador. los divídimos en dos pasos por separado sobre el árbol sintáctico. El primer procedimiento realiza un recorrido preorden del árbol sintáctico. y la operación de inserción insert neces lo parámetros de ubicación y número de línea. h contiene las de o raciones siguientes: void st-insert( char * name. Por L tanto. int lineno. h y tab. El segundo procedimiento realiza un recorrido postorden del irbol sintáctico. llama entonces a grintSymTab vara imprimir la iriformxión almacenada al archivo del listado.

porque realiza inserciones en la tabla de símbolos. static void traverse( TreeNode * t.Un analizador remantito para el lenguaje TlNY 337 &bol a medida que se calculan e informando de cualquier error de verificación de tipo al archivo del listado. impiementamos tanto buildsymtab como typecheck utilizando la misma función de transversal genérica traverse (Iíneas 1420.name del nodo. el recorrido postorden requerido por typecheck (Ilneas 1556-1558) se puede obtener mediante la llamada simple donde checkNode es un procedimiento definido de manera apropiada que calcula y verifica el tipo en cada nodo.postPro~). donde el nombre nuevamente se almacena en attr . 1 postProc(t). cn esos tres I~~garcs. Para enfatizar las técnicas involucradas de recorrido de un árbol estándar. í int i.preProc.preProc. los únicos nodos de interés son los nodos de identificador. void ( * postproc) (TreeNode *) ) í if (t != NULL) [ preProc (t) . i++) traverse(t->child[il. Entonces el reconido preorden que construye la tahla de símbolos se llevará a cabo mediante la llamada simple dentro del procedimiento bui ldSymtab (Iíneas 1488-1494).En el caso de la tabla de símbolos de TWY. En el caso de nodos de expresión. De manera similar. void ( * preProc) (TreeNode * ) .name. for (i=O. basado en la clase de nodo del árbol sintáctico que recibe a través de su p'drámetro (un apuntador a un nodo de árbol sintáctico). El código para estos procedimientos y sus procedimientos auxiliares está contenido en el archivo analyze c 'péndice B.postProc). mientras pasa un procedimiento "que no hace nada" como gostgroc. uno para realizar el procesamiento preorden de cada nodo y el otro para efectuar el procesamiento postorden: . El procedimiento insertNode (Iíneas 1447-1483) debe determinar cuándo insertar un identificador (junto con su número de línea y ubicación) en la tabla de símbolos. procedimiento insertNode contiene una Ilarnada el . 1 1 Dado este procedimiento.De este inodo. traverse(t->sibling. i < NAXCHILDREN. donde el nombre de variable a asignar o a leer está contenido en el campo attr . En el caso de nodos de sentencia. para obtener un recorrido preorden necesitamos definir un procedimiento que suministre procesamiento preorden y lo pase a traverse como pregroc. el procesador de preorden se llama insertNode.1441). Resta describir el funcionamiento de los procedimientos insertNode y checkNode. los únicos nodos que contienen referencias de variable son nodos de asignación y nodos de lectura. que toman dos procedimientos (además del irbol sintáctico) como parámetros. El procedimiento "que no hace nada" Fe denoniina nullProc y se define con un cuerpo vacío (Iíneas 1438-1441). Iíneas 1400-1558).

y estos ti están definidos en un tipo enumerado en las declaraciones globales (véase el apén Iínea 203): . Las otras cuatro clases de sentencia necesitan alguna forma de verificación de tipo: las sentencias IFK y RepeatK necesitan sus expresiones de prueba verificada? para asegurarse que tienen tipo Boolean (líneas 1527-1530 y 1539-1542). primer lugar. debe determinar si. 6 1 ANÁLISIS SEHÁNTICO si la variable todavía no se ha contemplado (lo que almacena e increnienta el contador ubicación además del número de Iínea) y si la variable ya está en la tabla de símbolos (que almacena el número de línea pero ubicación). C sólo los nodos de expresión tienen tipos. Finalmente.Boolean) ExgType. Éste campo se conoce como c po tyge en TreeNode (definido en globals h. trpedef enum Woid. debe inferir un tipo para el nodo actual (si tiene un t y asignar este tipo a un campo nuevo en el nodo del árbol.En el caso de un nodo de expresión. En el caso de un nodo hoja (líneas 1517-1520). { el valor booleano no se puede asignar 1 un error también 1 - . entonces el tipo es booleano (Boolean).si no lo es.de modo que no es necesaria la verificación de tipo. { error write 1 = 2. las dos subexpresiones hijas deben ser de tipo Integer (y sus tipos ya s calculado. el tipo es siempre Integer (y no ocurre verificación de tipo). entero y booleano. En segundo lugar. esta inferencia del tipo únicamente se pres para nodos de expresión.! CAP. puesto que se está realizando un recorrido postorden). En el caso de un nodo de operador (líneas 1508-1516). es entero (Integer). de clase ConstK o IdK)o un nodo de operador (de clase OpK). En TINY existen sólo dos tipos. Cuando se presenta un error. Resta catalogar las acciones de checkNode. En el caso de un nodo de sentencia no se infiere ningún tipo. y las sentencias WriteK y AssignK necesitan una verificación (líneas 1531-1538) de que la expresión que se está escribiendo o asignando no es booleana (ya que las variables sólo pueden mantener valores enteros. ) El procedimiento checkNode del paso de verificación de tipo tiene dos tareas. el buildS efectúa una llamada a grintSymTab para escribir la información del número de archivo de listado. . basado en los tipos de los nodos hijo. y sólo los valores enteros se pueden escribir): x := 1 < 2. pero en todos los dem casos aparte de éste debe realizarse alguna verificación de tipo. bajo el control del marcador TraceAnalyze (que está estahleci main c . se ha presen un error de tipo. desputs que se ha construido la tabla de símbolos. El tipo del nodo O determina entonces a partir del operador mismo (sin tener en cuenta si se ha presentado u error de tipo): si el operador es un operador de comparación (< o =). donde la variable que se está leyendo debe ser automáticamente de tipo Integer. Iínea 216).véase el apéndice B. Integer. Éste es el caso de una sentencia ReadK. el nodo es un nodo hoja (una constante o un identificador. el cual imprime un mensaje de error al archivo del listad basado en el nodo actual. el procedimiento checkNode llama procedimiento tygeError. Aquí el tipo Void es un tipo "no-tipo" que se utiliza sólo en la inicialización y para v rificación de errores.

term exp' rerm +j¿fctor terni' term' -+*factor term' / E factor ( erp ) / número 1F Escriba una grarnática con atributos para el valor de una expresión dada por esta gramática...) únum 4 rtuin.2 Escriba una gramática con atrihutos para el valor de punto Ilotante de un número decimal dado por la gramática siguiente. i d / id f y e + integer 1 real ... :.. . (Sugerencia: utilice un atributo contro para contar el número de dígitos a la derecha del punto decinial... :. Por ejcnlplo ( 2 (1 ni1 nil) (3 ni1 ni1 f ) cstá ordenado. qtte los valores de los números del primer subirbol sean I que el valor del núniero aciual y los valores de todos los iiúmeros del segtindo subárbol sean 2 al valor del número actual. Se puede suponer ejemplo... 6. . que contenga la forma postfija para la expresión entera sim& Por *42 resultaría ser "34 3 ..5 Vuelva a escrihir la gramática con atributos de la tabla 6. el atributo postfijo para (34-3) un operador de concatenación II y la existencia de iin atribitto número... 6..:> .... EJERCICIOS 61 Escriba tina gramática con atributos para ei valor entero de un número dado por In gramática .42 *".4 Considere una gramática de expresión como se escribiría para un analizador sintáctico predictivo con la recursividad por la i~quierda eliminada: exp 4 term e-~p' crp' -+ + term exp' 1 . : ... .. . pero (1 (2 ni1 ni11 (3 ni1 nil) ) o« lo está.. 6. siguiente: número -+ dígito número / dígito díSito+0/1/2/3/4/5/6/7/8/9 6.6 Considere la gratiiática siguiente para irboles hinarios enteros (en forma linealizada): (irbol + ( número círbol drbol ) / ni1 Escriba una gramática con atributos para veri. 6....-. es decir.~ ..7 Considere la graniática siguiente para declaraciones simples estilo Pascal: clecl -+vcrr-ii. :.3 La gramática para números tleciinales del ejetcicio anterior se puede volver a escrihir de manera exponentes).vt : type + var-lis¡ ..... .wr-lis! .2 para calcular un atributo de cadena posIfijo en lugar de vul.-. que sea innecesario un atributo conte» (y se eviten las ecuaciones que invt~lucran Vuelva a escribir la gramática para hacer esto y proporcione una nueva gramática con atributos para el valor de un dntnn.. .vt~icaden«.iicar que está ordenado un árbol binzrio.num num -+ num clígito 1 dígito tligito+0/1/2/3i415/6/7/8/9 6.

Dibuje las gráficas de dependencias correspondientes a cada regla gramatical del ejempl 6.u el valor 3 antes de comenzar la evaluación de atributo.v + C.9 6. y. Dibuje las gráficas de dependencia correspondientes a cada regla gramatical para la gramática con atributos del ejercicio 6. si S. 0 . Describa los dos pasos que se requieren para calcular los atributos eti el árbol sintáctico 5 / 2 / 2 .u = S. Vuelva a escribir la gramiitica y gramática con atributos del ejemplo 6. Dibuje el árbol de análisis gramatical para la cadena abc (la Única cadena en el lenguaje).u = A.v ¿Qué valor tiene S. a. O . y proporcione u nueva gramática con aiributos para el tipo que tiene esta propiedad. z: real.v cuando ha terminado la evaluación? c. b. Escriba el p~eudocódigo para procedimientos que realilarían los cálculos descritos en el inciso b. Dibuje las gráficas de dependencia correspondientes a cada regla gramatical para la gramática con atributos del ejercicio 6. 6 1 AW~LISIS CO SEMANTI 6. Considere la siguiente gramática con atributos: Regla gramatical Reglas semánticas a. Suponga que se asigna a S. y para la cadena 3 * ( 4 + 5 ) * 6 .u C.4 (página 266) de ma que el valor de un num-base se calcule s61o por atributos sintetizados.CAP.ii = 3 antes de comenzar la cvaluación'? .7.u = B. Vuelva a escribir la gramática de manera que el ti de una variable se pueda definir como un atributo puramente sintetizado. incluyendo un posible orden en el cual se podrían visitar los nodos y los valor de ainbuto calculados en cada punto.1 1 6.8 6.10 6. y dibuje la gráfica de dependencia para los atributos asociados.v A. Describa un orden correcto para la evaluación de los atributos.14 (página 283) y para la expresión 5 / 2 / 2 . Suponga que las ecuaciones de atributo se modifican como sigue: Regla gramatical S-tABC Reglas semánticas B.4.v dcspués de la evaluación de atributo.7. y dibuje una gráfica de dependencia para la declaración x. c. ¿Cuál es el valor de S. b.12 6.13 Considere la gramática del ejercicio 6.

¿que ciidenas aceptarían un analizador sintáctico generado por Yacc? c.9 (página 31 1) para esta nueva gramática. Considere la siguiente gramQtica(ambigua) de expresiones: exp -+ exp + exp / exp .stl. pero que la gramática . Modifique el pseudocódigo para la función fypeiFqua1de L figura 6.dt).drype se mantiene en la pila de valores durante un análisis sintáctico LR. (Véaie la página 307.5.22 (piigina 129) para incluir Iliimadas y declaraciones de i'uncioi~es: .rlfype = id .dr)pe vur-/i.9 para utilizar declaraciones colaterales en lugar de declaraciones secuenciales. entonces la subexpresión en modo entero se convierte a iriodo de punto flotante. b. Dada la gramática del inciso a (con la producción E). dada la gramática con atributos.) Escriba unigramática con atributos que calcule el valor de cada expresión para la gramática de expresión de la sección 6.18 6.21 Considere la siguiente extensión de la gramática de la I'igiira 6. entonces este valor no se puede encontrar en una posici6n fija en la pila cuando ocurren reducciones a una vur-lisr.dtype = integer ryl>e. y viielua .B b I u es SLR( 1j. 6.i cscribir la gramática con atributos de la tabla 6.dtype epe.stl + id . Demuestre que la gramática B . Vuelva a escribir la gramática de expresión de la sección 6. Regla gramatical decl . Vuelva a escribir la graniática con atributos de la tabla 6.dtype = vrrr-li. type .exp j exp * exp / exp / exp / ( exp ) / num 1 num.dtypc - id si el atributo @pe. ¿Es probable que esta situación se presente durante el .20 [construida a partir de la gramática anterior al agregar una producciún E simple) no es LR1k) para ninguna k.16 6.i n t type -t f loat wr-li. Escriba una gramática con atributos que convertirá a tales expresiones en expresiones legtlcs en Modul.inálisis semántica de un lenguaje de programación "real"? Justifique su respuesta.rl~ype vor-listl.5 en una gramática no ambigua. var-/ist2 vur-list Reglas semánticas viir-list.3'.15 a.3.i-2: las conversiones de itíimeros enteros a núineros de punto flotante se expresan aplicando la funcih FLOAT. 6. y el operiidor de división / se considera como d i v si sus operitndos son enteros.dtype = vnr-list.st2.17 6.Ejertitios 6.tvpe vur-list i .num Suponga que se siguen las reglas de C al calcular el valor de cualquier expresión así: si dos subexpresiones son de tipo mixto.19 6. 6.<ltype= real id .20 (página 325) a fin de e a incorporar nombres de tipo y el algoritmo sugerido para determinar la equivalencia estructural de tipos recursivos como se describió en la página 327.14 Muestre que.pe = type. y se aplica el operador de punto flotante. de manera tal que 1% expresiones escritas en esa sección sigan siendo legales.

y A esta definida en una typedef como equivalente a double. 6 1 ANÁLI$IS SEMANTICO progrcima -+ r'ur-dec1. 10 (pígina 228) para una ciilculadora de enteros simple de inanem que imprima una traducción postfija eri lugar del wlor (véase el ejercicio 6.rp p a. Escriba regias semánticas para la verificación de tipo de declaraciones de frtnción y llai das de función (representada por la regla exp -t i d ( crps ) ) similares a las reglas de la hla 6.fiin-rfecl.26 a. exp ni e.27 a.1 (página 148) de manera que imprlma la traducción postfija en lugar del valor de una eupie~tón (véabe el e.xp 1 e. b.CAP. entonces esta expresi6n asigna el v:tlor de -x a double.23 Escriba una gramática con atributos correspondiente a las restricciones de tipo impuestas por el verificador de tipos de TINY. entonces esto calcula la diferencia en valor entero de las dos variables. b. 6.jercicio 6. var-decl iur-rlecl / vcrr-clrd -+ id : type-e.rp iype-exp -t int bool array / / [num] of @[)e-e. Vuelva a escribir la especilicacih Yacc de inanera que imprima i<uirr~ vdor como la irael (lucci6t1postfiin. vur-llec1.s -+ iar-úecls . - : . 6.5). Viielva a escribir la especificación Yacc de la figura S. b.imática del ejercicio 6.22 Considere la siguiente ambigüedad en expresiones en C.rp 1 /num/ true/false / id erps -t exps / id ( exps ) . 6.3). w i t s -+ sc.s . Vuelva a ewibir e1 evaluador descendente recuríivo de la figura 4.sent 1 sent senr -t if cxp then sent / id := exp exp -t exp + erp / exp or exp / e.cirerpo e.10 (página 330). Dada la expresión ( A ) -X si x es tina vuiahle entera. 6. b. 6.24 Escriba una gramitica con atributos para la construcci6n de la tabla de símbolos del analizador semántica de TINY. Vi~elva escribir el evaltiador descendente recursivo de manera que imprima trinto el vdor a com« la traducción postfija.nt.rp Jun-decls -+ fun id ( vnr-decls ) : Qpe-exp . para escriba una función @~~eEqual dos tipos de función. Uescriha cómo puede utilizar el analizador sintáctico la tabla de símbolos para eliminar la ainbigiiedad de estas dos interpretaciones.51.~ sents .i p.s .ici6n Yace que imprimir5 la traduccidn en Mtidula-2 de tina expresiijn i l a h para I. Por otra parte. a.xp cuerpo --. Disehe una estnichlra adecuada de árbol para las iiuevas estructuras de tipo de hinción.28 Ecriha iiiia ecpccitic. Describa cómo puede emplear el analizador léxico la tabla de símholos para eliminar la ambigüedad de estas dos interpretaciones. si A es una variable entera. 6. .

Vuelva a escribir la implementación de la tabla de símbolos para el analizador scni5ntico de TINY con el fin de agregar un apuntador inferior a la estrtictura de datos LineList y tncjorar la eficiencia de la operación insert. El analizador semántico de TINY no intenta asegurar que se ha asignado una variable :luces de utilizarse. (Se pueden abreviar los tokens let e in a caracteres simples y también restringir identificadores o usar Lex para generar un analizador léxico 6.30 6.9. El uso de gramiticas con atributos para especificar formalmente la semintica de los lenguajes de programación se estudia en Slonneger y Kurtz /1995].33 adecuado. Esto requerirá que se dé a las variables tipos de datos hooleano o entero en la tabla de símbolos y que la verificación de tipo incluya una búsqueda en la tabla a de símbolos. Escriba una gramática con atributos para el verificador de tipo TlNY como h e modificado en cl inciso a.29 Escriba una especificación Yacc para un programa que calculará el valor de ona expresi611 con bloques lrt (tabla 6. Leiscrson y Rivcst 11990j. Hopcrolt y IJllman [l983] o Corrnen. La cuestión de mantener análisis sintáctico LR determinístico mientras se agregan nuevas producciones E p:m programar acciones (conio en Yacc) se cstudia en Purdorn y Brown [IOXO].) Escriba una especificación Yacc y Lex de un programa para verificar tipos del lenguaje cuya gramiítica está dada en la figura 6. v6cw. Aho. L exactitud del tipo de un programa TINY ahora debe incluir el requerimiento de que todas las asignaciones a (y usos de) una variable sean consistentes con sus tipos de datos. Más propiedades matemáticas de las gramáticas con atribiitos se pueden encontrar en Mayoh [1981 J. <. con q~licriciones rniichos :i p~spriricilxiles estudio & la ciencia cornput.Qué impide a tales verificaciones ser infalibles? 6.22 (página 329). Vuelva a escribir el ~malizador seniántico de TI?SY para permitir que se almacenen valores booleanix en variables. cvactitud dc tipos e irifcrcncia de tipos forman uno de los c m tci>rica. De este modo.~cional del . La cuestión de la evaluación de atributos durante el análisis sintáctico se estudia con algo más de detalle en Fischer y LeRlmc [1991]. Una prueba para la no circularidad se puede hallar en Jazayeri. se pueden encontrar en muchos textos. Las condiciones que aseguran que esto se puede hacer durante un anilisis sintáctico LR aparecen en Jones [19801.31 6. L o s sistemits ilc tipos. b. incluyemlo tabl:is de de dispersión y análisis de sus eficiencias. donde se proporciona una gramitica con atributos completa para la semántica estática de un lenguaje similar a TINY. hnportantesestudios del uso de las gramáticas con atributos en la construcción de compiladores ap:uecen en Lorho [19841. Las cstmcturas de datos para la irnplen~entación la tabla de símbolos.Notas y referencias 343 6.34 a. Ogden y Rounds [1975]. NOTAS! REFERENCIAS EI trabajo inicial rtiis importante acerca de gramática con atributos es el de Knuth [1968]. página 3 11). por cjemplo.32 6. Vuelva a escribir el analizador semántico de T1NY de manera que s6lo realice un paso sohre el árbol sintictico. el siguiente código TINY se considera senxánticamente correcta: Vuelva a escribir e1 analizador TINY de manera que haga verificaciones "razonables" de que ha ocurrido una asignación a una variable antes de utilizar esa variable en una expresicin. Un cuidadoso estudio de la selección de tina fnncirín de tlispersiíín aparece en Knuth (1W731.

puesto que ninguna es de uso general (a diferencia de los g dores de analizadores léxicos y analizadores sintácticos Lex y Yacc). El algoritmo para equivalencia estructunl descrito en la 6. La versión inferencia de tipos polimórfica utilizada en ML y Haskell se conoce como inferencia de Hindtey-Milner [Hindley. Algunas herra interesantes basadas en gramáticas con atributos son LINGUIST [Fmow. 19841 [Kastens. 6 1 ANÁLISIS SENANTICO lenguajes.CAP. tales como FORTRAN77. Los sistemas de tipo polimórficos y algori de inferencia de tipos se describen en Peyton Jones 119871 y Reade 119893. 1969. C y Ada utilizan formas mi equivalencia de tipos similar a la equivalencia de declaración de Pascal. . 19781. Los lenguajes funcionales rnodernos como Haskell utilizan equivalencia de nombre estricta. Para una perspectiva general tradicional.4. véase Louden 119931. Milner. El Synthesizer Generator ("Generador Sintetiz [Reps y Teitelbaum. que son di de describir de manera concisa. Lenguajes más antiguos. véase Cardelli y Wegner [1985]. Para una pectiva más profunda. En este capítulo no describimos todas las herramientjs para la construcción auto de evaluadores de atributos. 19821. con sinónimos de tipo tornando el 1 la equivalencia estructural. una moderna aplicación de un algoritmo se encuentra en Amadio y Cardelli 119931. Al y Algo168 utilizan equivalencia estructural. 19891 es una herramienta madura para la generación de editores se bles a1 contexto basados en gramáticas con atributos. Hutt y Zimmermann.3 se puede encontrar en Koster [19691. Una de las cosas interesantes qu pueden hacer con esta herramienta es construir un editor de lenguaje que suministra de nera automática declaraciones de variable basadas en el uso.

análisis sintáctico y análisis semántico es. En este capitulo comentaremos cada una de estas clases de ambientes por turno. en el código real que es necesario generar para mantener el ambiente. Aquí. También pueden utilizar híbridos a partir de todos los anteriores. y el ambiente completamente dinámico de lenguajes funcionales como LISP. como el que realiza un optimizador. N o obstante. Esto incluyó análisis léxico.C+ +. Esto incluye cuestiones de ámbito y asignación. el cual es la estructura de la memoria y los registros de la computadora objetivo con los que se administra la memoria y se mantiene la información necesaria para guiar el proceso de la ejecución. Este análisis sólo depende de las propiedades del lenguaje fuente: es independiente por completo del lenguaje objetivo (ensamblador o de máquina) y de las propiedades de la máquina objetivo y su sistema operativo. En este aspecto es importante tomar en cuenta que un compilador puede mantener un . cuya estructura esencial no depende de los detalles específicos de la máquina objetivo. y parte de éste puede ser independiente de la máquina. Pascal y Ada. Esto es particularmente cierto para el ambiente en tiempo de ejecución o ambiente de ejecución.Capítulo 7 Ambientes de ejecución Organización de memoria durante la ejecución del programa Ambientes de ejecución completamente estáticos Ambientes de ejecución basados en pila 7. Estas tres clases de ambientes son: el ambiente completamente estático característico de FORTRAN77. Pero gran parte de la tarea de la generación de código depende de los detalles de la máquina objetivo. casi todos los lenguajes de programación utilizan una de tres clases de ambiente en tiempo de ejecución.6 Memoria dinámica Mecanismos de paso de parámetros Un ambiente de ejecución para el lenguaje TlNY En capítulos anteriores estudiamos las fases de un compilador que realiza análisis estático del lenguaje fuente. junto con las características del lenguaje que determinan qué ambientes son factibles. las caracterísdcas generales de la generación de código permanecen iguales a través de una amplia variedad de arquitecturas. De hecho. En este capitulo y el siguiente volveremos a la tarea de estudiar cómo un compilador genera código ejecutable. Esro puede involucrar análisis adicional. y las variedades de mecanismos para paso de parámetros. y cuáles deben ser sus propiedades. tático. el enfoque se centrará en la estructura general del ambiente y en el siguiente capítulo.5 7. la naturaleza de llamadas a procedimientos..4 7. el ambiente basado en pila de lenguajes como C.

o 1m3s probül>le que c l códi~o carga mediante un cargador en un &ea en memoria que eses se principio de la cjccución y. En contraste.1 ORGAN~ZACION DE MEMORIA DURANTE LA EJECUCIÓN DEL PROGRAMA L a memoria de una computadora típica está dividida en un área de registro y una memoria acceso aleatorio (RAM). $10es absolut:iniente predecible. l. En la mayoría de lenguajes com pilados no es posible efectuar cambios al área de código durante la ejecución. incluyendo ambientes completamente dinámicos y ambien orientados a obietos. y el área de c digo y de datos pueden visualizarse como separadas de manera conceptual. y el área de código se puede contemplar de la manera siguiente: Punto de entrada mediante el procedimiento 1 -+ Punto de entrada mediante el procedimiento 2 -+ r l el procedimiento el procedimiento cddigo para el procedimiento Código en memoria Punto de entrada mediante el procedimiento n 4 / 1 En particular. de Iorma que e1 principio de direcciones fij:is permanece igual. que es dimimica. puesto que puede mantener el ambiente directamen dentro de sus propias estructuras de datos. e l punto de entrada de cada procedimiento y función se conoce en tiempo de compilación. que se puede abordar directamente. La primera sección de este capítulo contiene un análisis de las caracteristicas ge les de todos los ambientes en tiempo de ejecución y sus relaciones con la arquitectura máquina objetivo.iducódigo relocalizahle. de este nrodo. de cir 14 ahigriada al . Eje~nplos eslo se pn)poi-ciitn:i~-. Además. Sin embargo. A continuación se tiene un análisis del efecto de diversas técnicas paso de parámetros sobre el funcionamiento de un ambiente. junto con ejemplos de su funcionamiento durante la ejecución. [.iiii t-cgislrc). En ucasioiies. Una sección posterior analiza tiones de memoria dinámica. E l área de RAM ta bién puede dividirse en un área de código y un área de datos. se proporciona algo de detalle acerca de las diferen variedades y la estructura de un sistema basado en pila. ya que solamente una pequeíia de la misma puede asignarse en ubicaciones fijas en memoria antes de que comience la c. en el cttiil 10s s:~It«s.I1am. todas las direcciones de código se pueden calcul en tiempo de compilación. interprete tiene una tarea más fácil. todas las direcciones re:iles son entonces c:ilculadas de riianera automitica tnediante despl. Como el ambi basado en pila es el más común.tzamiento ilestie una dirección de carg hase lija. por lo rcgnl. Gran parte del resto del capítulo se dedica a cómo manejar la asignación de datos que n o es fija.ir> el c:ipiiiil« higuie~irc.rdas y refewirci:is son twlas realizadas cn relaci~in y :ilg~~n:i c hase. más lenta. o bien.it. conlo área de código se fija antes de la ejecucibn.jecucibn. ya que debe generar código para realizar las oper ciones de mantenimiento necesarias durante la ejecución del programa. Las siguientes dos secciones analizan los ambientes estático y basa en pila.ambiente sólo de manera indirecta.' No se puede decir lo mismo de la asignación de datos. El capitulo se cierra con una breve descripción del ambiente simple necesario para implementar el lenguaje TINY. e l cscritor dcl compilndor debe tener cuidado (le gencrx el denon~in. 7.

z A menudo la arquitectura de la máquina objetivo incluirá una pila de procesador. un compilador tendrá que hacer preparativos para la asignación explícita de la pila del procesador en un lugar apropiado en la memoria. (En realidad. de modo que deben ser almacenadas de esta manera. Éstas incluyen Pas declaraciones const de C y Pascal. first-out") y el área de apilamiento es empleada para la asignación dinámica que no cumple con un protocolo LIFO (la asignación de apuntadores en C. donde el área de pila es utilizada para los datos cuya asignación se presenta en modo LIFO (por las siglas del término en inglés "último en entrar. En Pascal.) Tales datos por lo regular se a asignan de manera separada en un área fija de manera similar al código. En ocasiones. Una organización general de almacenamiento en tiempo de ejecución que tiene todas las categorías de memoria descritas puede tener el aspecto que se presenta a continuación: 1 u área global/est&tica área de código l ! apilamiento o montículo (heap) 1 Debería observarse que el apilümiciito es por lo regular uiia siinple irea de memoria lineal. además de valores de literales utilizadas en el código mismo.) El área de memoria utilizada para la asignación de datos dinámicos puede ser organizada de muchas maneras diferentes.Organización de memoria durante la ejetucian del programa 347 Existe una clase de datos que pueden ser fijados en memoria antes de la ejecución y que comprenden los datos globales y10 estiticos de un programa. por lo regular se insertan directamente en el código por medio del compilador y no se les asigna ningún espacio de datos. se almacenan una vez en el inicio y posteriormente se buscan en esas ubicaciones mediante el código de ejecución. a diferencia de L niayoría de los lenguajes. tal como la cadena "Hello%d\nn y el valor entero 12345 en la sentencia C grintf ( "Hello %d\n". en particular. Una organización típica divide esta memoria en un área de pila y un'área de apilamiento o montículo (heap). Las constantes pequeñas de tiempo de compiiación. 12345) . y al usar esta pila permitirá emplear soporte de procesador para llamadas de procedimiento y retornos (el mecanismo principal que utiliza asignación de memoria basada en pila). "last-in. lo mismo que las variables estáticas y externas de C. a los valores enteros grandes. todos los datos son de esta clase. tales como O y 1. Una cuestión que surge en la organización del área globaWestática involucra constantes que son conocidas en tiempo de compilación. puesto que el compilador conoce sus puntos de entrada y también se pueden insertar directamente en el código. denomina ripiliiniiento o montículo (heap) por rnmnes histhricas. las variables globales se encuentran en esta clase. y no estli relaciotiado coi1 la estructura de datos de pilns utilimda en :ilgoritnios taics como el de or<bn:imientode pilns. Se . Sin embargo. (En FORTRAN77. primero en salir". las literales de cadena se les asigna por lo regular memoria en el área globaííestática. es decir. . Tampoco se necesita asignar espacio en el área de datos global para procedimientos o funciones globales. en las literales de cadena C son visualizados como apuntadores. los valores de punto flotante y. por ejemplo).

la pila se representa incrementándose hacia abajo en la memo manera que su tope o parte superior en realidad está en la parte inferior de su área ilu El apilamiento también se representa de manera similar a la pila. que . como mínimo. tales como el contador de programa (pc. Los detalles pecíficos. e inclu de las preferencias del escritor del compilador. el espacio para la información de administración. Algunas partes del registro de activación también pueden ser asignadas de manera automática mediante el procesador o llamadas de procedimiento (el almacennmiento de la dirección de retorno. frame pointer). variables locales o incluso variables glohales. Pascal) o el área de apilamiento (LISP). Dependiendo del lenguaje. ~ a m b i é npuede haber registros específicamente diseñados para mantenerse al tanto de las activaciones de procedimiento. Los procesadores también tienen registros de propósito específico para mantenerse a1 tanto de la ejecución. dependerán de la arquitectura la máquina objetivo. po ejemplo). por ejemplo. los registros de activación pueden ser asignados en el área estática (FORTRAN77).CAP. Un registro de activación. stack pointer) en la mayoría de las arquitecturas.4). como el espacio para los argumentos y datos locales. 7 1 AMBIENTES DE EJECUCI~N Las flechas en esta imagen indican la dirección de crecimiento de la pila y el apila (heap). incluyendo el orden de los datos que contiene. Otras partes (como el espacio temporal local) pueden necesitar ser asignadas de manera explícita mediante instrucciones generadas por el compilador. el cual contiene la memoria asignada a los datos locales de un procedi to o función cuando son llamados o activados. program counter) y el apuntador de pila (sp. Los registros de procesador también forman parte de la estmdtura del ambiente de ejecución. como es el caso en los procesadores RISC más recientes. y su incremento y decremento son más complicados de lo que indica la flecha la sección 7. En algunas organizaciones se asigna a la pila y al apilamiento seccion paradas de memoria. Otras partes. pero variarán entre un procedimiento y otro. espaao para datos locales locales 1 / a I : Aquí hacemos énfasis (y lo haremos de manera repetida en las secciones siguientes). en qu esta imagen sólo ilustra la organización general de un registro de activación. Algunas partes de un registro de activación tienen el mismo tamaño para todos los procedimientos. en ocasiones se hace referencia a ellos como mar de pila. el área de la pila (C. Los registros pueden ser utilizados para almacenar elementos temporales. en vez de ocupar la misma área. de las propiedades del lenguaje que se está compilando. contener las secciones siguientes: 1 1 / espacio para argumentos (parámetros) -1 espacio para inlormacion de adm~nistrachn. Cuando un procesador tiene muchos registros. Una unidad importante de la asignación de memoria es el registro de activac procedimiento. Cuando los registros de activación se conservan en la pila. pero no es una est LIFO. toda el área estática y los registros de activación completos puedenser conservados por completo en registros. Algunos registros típicos de esta naturaleza son el apuntador de marco (fp. incluyendo 1 direcc~ones retorno de .Tradicionalmente. pueden permanecer fijas para cada procedimiento individuai.

Más adelante se tratarán estas cuestiones con más detalle. haremos referencia a la parte de la secuencia de llamada que se realiza durante una llamada como la secuencia de llamada y la parte que es efectuada al regreso como la secuencia de retorno. o de manera parcial por ambos. y el apuntador de argumento (ap. por lo general también se consideran parte de la secuencia de Ilamada. y en el cual los procedimientos no puedan ser llamados de manera recursiva. Un ambiente de esta clase puede utilizarse para implementar un lenguaje en el cual no hay apuntadores o asignación dinámica. Si es necesario. El punto 1) es un asunto particularmente espinoso. es devuelto. ya sean locales o globales. Los aspectos importantes del diseño de la secuencia de llamada son 1) cómo dividir las operaciones de la secuencia de llamada entre el elemento que llama y el elemento llamado (es decir. el cálculo y almacenamiento de los argumentos. el estado de la máquina en el punto de Ilamada. En un ambiente completamente estático no sólo las variables globales. Además. quizá de alguna manera en la que intervengan tanto el elemento que llama como el elemento llamado. mediante direcciones fijas. 72 AMBIENTES DE EJECUCION COMPLETAMENTE ESTATICOS La clase más simple de un amhiente de ejecución es aquella en la cual todos los datos son estáticos y permanecen fijos en la memoria mientras dure la ejecución del programa. El ejemplo estándar de un lenguaje así es FORTRAN77. posiblemente. . tal como la ubicación del valor de retomo al que se puede tener acceso mediante el elemento que llama. cuánto código de la secuencia de llamada colocar en el punto de llamada y cuánto colocar al principio del código de cada procedimiento) y 2) hasta qué punto depender del soporte del procesador de llamadas en vez de generar chdigo explícito para cada paso de la secuencia de Ilamada. y la memoria completa del programa \e puede visualizar como sigue: 3. son asignadas de manera estática. argument pointer)." Una parte particularmente importante del diseño de un ambiente de ejecución es la determinación de la secuencia de operaciones que deben ocumr cuando se llama a un procedimiento o funciún. incluyendo la dirección de retomo y. Así. los regirtros que se encuentran en uso. sino todas las variables. Estas operaciones por lo regular se conocen como secuencia de Ilamada. Estos nonihres son tomados de la arquitectura VAX. Se puede tener xceso directamente a todas las variables. puesto que el mismo código debe ser duplicado en cada sitio de llamada. ya sea por el elemento que llama o por el elemento Ilamado. que es asignado estáticamente antes de la ejecución. Las operaciones adicionales necesarias cuando un procedimiento. puesto que por lo regular es más fácil generar el código de la secuencia de llamada en el punto de llamada en vez de hacerlo desde el interior del elemento llamado. Finalmente. tambien debe ser establecida cualquier información adicional de administración. que apunta al área del registro de activación reservado para argumentos (valores de parámetro). el elemento que llama es responsable de calcular los argumentos y colocarlos en ubicaciones donde puedan ser encontrados por el elemento llamado (quizhs directamente en el registro de activación del elemento llamado). pero nombres sinlilares se presentan cn otras :~rquitc~turils. Tales operaciones pueden incluir la asignación de memoria para el registro de activación. Como mínimo. pero al hacerlo así se provoca que crezca el tamaño del código generado. el reajuste de los registros y la posible liberación de la memoria del registro de activación.Ambientes de ejecution completamente esdticos 349 apunta al registro de activación actual. de nueva cuenta. y el almacenan~iento y establecimiento de los registros necesarios para efectuar la llamada. o una función. deben ser guardados. cada procedimiento tiene sólo un registro de activación Único.

se hace un simple salto a la dirección de retorno: Ejemplo 7. Entonces la dirección de retorno en el código del elemento que llama se g ba. . y se efectúa un salto al inicio del código del procedimiento llamado. cada argumento es calculado y alma cenado en su ubicación de parámetro apropiada en la activación del procedimiento que está llamando. En la mayoría de las arquitecturas. 5. tal vez. esta dirección también se recarga (le nianera automática cuando se ejecuta una instrucción de retorno.1. Ignoramos la función de biblioteca SQRT. FOWRAN77 permite que v.iri. 6.~ MAXSIZE tanto en el procedimiento principal como en Q U A D M ~ . En un ambiente así hay relativamente poco gasto general en términos de administració información a mantener en cada registro de activación.LlON teiigdn notiihres diferentes en procedimientos diferentes.CAP.1 Como un ejemplo concreto de ebta clase de ambiente. la cual es Il:tmnd~por QUADMEAN y cstd vinculada iintes de la ejeci~ción. Cuando se llama a un procedimiento. ' 4. Este programa tiene un procedimiento principal y un solo procedimie Existe una sola variable global dada por la declaración COMMON adicional QuADMEWI. Desde ahora en este ejemplo ign~rarcrnos discret:iriiente tales complejidades.ihles COM. De Iiecho. La secuencia de llamada para un ambiente de esta naturaleza también es parti larmente simple. 7 1 AMBIENTES DE EIECUCION código para el procedimiento principal 1 cddigo para el procedimiento 1 1 Área de código código para el procedimiento n área de datos globales / registro de activación del procedimiento principal registro de activación del procedimiento 1 Área de datos registro de activación del procedimiento n ~ ~. la dirección de retorno) en un registr activación. considere el programa en FORTRAN de la figura 7. Al regreso. y no es necesario mantener info ción extra acerca del ambiente (aparte de. nrientras que todavía Iiaccn referencia a la misma uhicaciijn de ~netrioria. un salto de subrutina guarda automáticamente la dirección de retomo.

iinpleincntaLas ciones realcs pueden dikrir en li~rriia sust. SIZE REAL A(SIZE). .l)) GOTO 99 DO 10 K = 1.is prop«rcio~~ad.5.2 que requiere ser explicada es la ubicación sin nombre que se asignó al final del registro de activación de QUADMW.is Je :rqrií. En QUADMEAN existen dos cilculos donde esto pnede 7.itivt)s.(SIZE. S i Z E y Q M W del procedimiento QUADMEAN tienen durante la llamada desde el procedimiento principal. Esta ubicación es una localidad "improvisada" que se utiliza para almacenar valores temporales durante el cilculo de expresiones aritméticas. La primera es que.0 IF ((SIZE.~i~ci:il I.SIZE.SIZE TEMP = TEMP + A(K)*A(K) CONTINUE QMEAN = SQRT(TEMP/SIZE) RETURN ENE 10 99 Ignorando la posible diferencia en tamaño entre los valores enteros y de punto flotante cn memoria. La tercera es que los argumentos constantes. TABLE(l). de modo que las ubicaciones de los argumentos de la llamada (TABLE.QMEAN.MAXSIZE). TEMP END SUBROUTINE QUADMEAN(A.) Otra característica de la figura 7.OR. deben aimacenarse en una localidad de memoria y ésta debe utilizarse durante la llamada.2 (página 352): En esta imagen indicamos con flechas los valores que los parámetros A. Lo anterior tiene varias consecuencias.GT. 3 y TEMP) se copian en las ubicaciones de paránietro de QumMEAN. La segunda es que los parámetros de arreglo no necesitan ser reubicados y eopiados (así.TABLE(2). En FORTRAN77.TEMP MAXSIZE = 10 READ *. TEMP INTEGER K TEMP = 0.QMEAN) COMMON MAXSIZE INTEGER MAXSIZE.LT. ilustramos un ambiente de ejecución para este programa en la figura 7.TABLE(3) CALL QuADMEAN(TABLE. los valores de parámetro son implícitamente referencias a memoria. (Los mecanismos de paso de parámetro se comentan más ampliamente en la sección 7. se requiere otra derrefereneiación para tener acceso a valores de parámetro.~. que apunta a la ubicación base de TABLE durante la llamada). Nuevaniente enf:itiz:irnoi que los c1ctalles de csta figura son sGlo ilustt~.TEMP) PRINT *. como el valor 3 en la llamada.Ambientes de ejecución completamente estáticos PROGRAM TEST COMMON MAXSIZE INTEGER MAXSIZE REAL TABLE ( 10 ) . se asigna al parjmetro de arreglo A en QUADMEAN sólo un espacio.

y en el cual las variabl locales sean nuevamente asignadas en cada Ilamada. o si se hace una llamada requiriendo que est lor sea guardado.2 Un ambiente de ejecutih para el programa de la figura 7. de manera que A ( K ) * A ( K ) se calcu luego se suma al valor de TEMP en el siguiente paso. los registros de activación no pueden ser asignados de manera estática.ser necesario. y la secuencia de llamada también debe incluir los pasos . En vez de esto. La pila de registros de activación (también conocida como pila de ejecución o pila de llamadas) cntonces se inerementa y decrementa con la cadena de llamadtis que se han presentado en el programa en ejecucibn. En particular. entonces el valor ser6 almacenado en el registro de activación an culminación del cálculo. y hacer preparativos para la asignación del número (y tamaño) ap do de las ubicaciones temporales. de hecho. Si no hay suficientes registros nibles para mantener este valor temporal. Ya coment la necesidad cle asignar espacio a valores de parámetro (aunque en una Ilamada a una fu de biblioteca la convención puede ser. diferente). y cada una representrirá una llamada distinta. la información de administración adicional debe inantenerse en 10s registros de activacibn. Un compilador siempre puede predecir si esto será neces rante la ejecucih. La razón de que una localid memoria tentporal también pueda ser necesaria para el cálculo del ciclo es que cada I ción aritmética debe aplicarse en un solo paso. los registros de activación deben ser asignados dé manera basada en pila. El primero es el cálculo de TEMP + A ( K ) * A ( K) en el ciclo. Figura 7. y el segu es el cálculo de TEMP/SIZE como ei parámetro en la h m a d a a SQRT. en la cual cada nuevo registro de activación es asignado en la parte superior de la pila a medida que se hace una nueva llamada de procedimiento (una inserción del registro activación) y desasignado cuando la llamada termina (tina extracción del registro de activación). Un ambiente de esta clase requiere una estrategia m:is compleja para su administraciGn y acceso n variables que un ambiente completamente cstcitico.1 Área global Registro de activación del procedimiento principal Registro de activación del procedimiento QUADMEAN 73 AMBIENTES DE EJECUCIÓN BASADOS EN PILA En un lenguaje en el que se permitan las llamadas recursivas. Cada procedimiento puede tener varios registros de activación diferentes a la vez en la pila de llamadas.

h> C para int x . 1 main ( ) { scanf ( "%ddad" &Y). printf("%d\n". En esta sección consideraremos 13 organización de tos ambientes basados en pila en un orden de comple. top of stack pointer).2 return O. int V) ( if (V == O) return u.y)). else return gcd(v. porque apunta al registro de activación del eleinento que Ilama durante la ejecución). por las siglas del término en inglés. depende fuerteinente de las propiedades de lenguaje que se está compilando. es decir. El apuntador a la activación actual por lo regular se conoce como apuntador de marco.gcd(x. clasificados por las propiedades de lenguaje involucrado. un ambiente basado en pila requiere de dos cosas: el mantenimiento de un apuntador hacia el registro de activación actual para permitir acceso a variables locales y un registro de la posición o tamaño del registro de activación inmediatamente precedente (el registro de activación del elemento que llama) con el fin de permitir que el registro de activación sea recuperado (y la activación actual sea descartada) cuando la llamada actual finalice.2 de Considere la implementación recursiva simple de1 algo~itmo Euclides para calcular el máximo común divisor de dos enteros no negativos. Adicionalmente.&x. Por lo general este apuntador se conserva en algún lugar a la mitad de la pila. el ejemplo 7. '13. En ocasiones este apuntador es denominado fp antiguo. .u % v). Veamos algunos ejemplos.jidadcreciente. int gcd( int u. La información acerca de la activación anterior por lo común se conserva en la activación actual como un apuntador hacia el registro de activación previo al que se conoce como vínculo de control o vínculo dinámico (dinkmico. Ejemplo 7. o sp (por las siglas del término en inglés. o fp. y la cantidad de información de administración requerida. y apunta a la ubicación del vínculo de control del registro de activación previo. 1 . y .3. cuyo código (en C) se proporciona en la figura 7.Ambientes de ejecución basados en pila 353 necesarios para estabtecerla y mantenerla. o tos. Fisura '13 Código en #incLude <stdio. puede haber un apuntador de pila. frame pointer. porque representa el valor anterior del fp. stack pointer). La exactitud de un ambiente basado en pila. es decir.1 Ambientes basados en pila sin procedimientos locales En un lenguaje donde los procedimientos son globales (corno el lenguaje C). entre el área de parámetro y el irirea de variable local. y generalmente \e mantiene en un registro (a menudo también conocido como fp). es decir. por las siglas del término en inglés. que siempre apunta a la ú1tima ubicación asignada en la pila de llamada (la cual a menudo se denomina tope del apuntador de pila.

Figura 1. lo que devuelve entonces el v Durante la tercera llaniada el ambiente de ejecución puede visualizarse como en la 7.4 Ambiente basado en para el ejemplo 1. O ) (puesto que 10 % 5 = O).CAP. y en cada nuevo regis activación el vínculo de control apunta al vínculo de control del registro de activaci terior. Ejemplo 7. y ésta produ tercera llamada gcd ( 5 . Advierta también que el fp apunta al vínculo de control del registro de activac' tual. 7 1 AMBIENTES DE EJE(UCI~N Suponga que el usuario introduce los valores 15 y 10 a este programa.4. Este punto se analizará con más detalle en la sección 7.2). ( inos el registro de activación de main como vacío.2 pila X: 15 Área globallestática Registro de activación de main rvínculo de control dirección de retorno u: 10 Registro de activación de la primera llamada a gcd vinculo de control dlrecc~ón retorno de Registro de activación de la segunda llamada a gcd vínculo de control Registro de activación de la tercera llamada a gcd Dirección de crecimiento de la pila Después de la llamada final a gcd. Esta llamada da como resultado u gunda llamada recursiva gcd (lo.) Finalmente. Advierta cómo cada llamada a gcd agrega un nuevo registro de activación de e mente el misino tamaño a la parte superior o tope de la pila. sólo el tro de activación para main y el &rea globallestática permanecen en el ambiente. Este código contiene variables que serán utilizadas para ilustrar otros puntos niás adelante en esta sección. ya que el lenguaje C emplea parámetro~ valor.5. En realidad. de manera main inicialmente hace la llamada gcd (15.5. contendría inform sería utilizada para transferir el control de regreso al sistema operativo. de modo que en la siguiente llamada el (P actual se convierte en el vínculo de del siguiente registro de activación. de manera que cuando la sentencia grintf es ejecutada en main. obwvamos que no ue necesita espacio en el elemento que llama para los valores de argumento en las llamadas a gcd (a diferencia de la constante 3 en el ambiente de FORTRAN77 de la figura 7. 1 0 ) . 5 ) (puesto que 15 % LO = S).3 Considere el código C de la figura 7. pero su Funcionamiento básico cs de . cada una de las activaciones es eliminada por tu de la pila.

C void g(int).Ambientes de ejecución basados en pila ura ?. El ambiente de ejecución en este punto (durante la segunda llamada a g) se muestra en la figura 7.6b). Observe también que la variable esttítica x en f no puede ser asignada en un registro de activación tle f . 1 void g(int m) main ( ) < 1 g(x): return O.5 grama en int x = 2. g(n).6b). Entonces. En efecto. De este modo. Una vez que ya no se hacen Ilamadas adicionales. Ahora g disminuye la variable externa x y hace una llamada adicional g ( 1). x. sus registros de activación son extraídos de la pila y se devuelve el control al punto directamente a continuación de la llamada a f en la primera llamada a g .ihle correcta para tener la :receso a cada puritti del progrartxi.-. atril criandt) no w a una variable global. n o puede haber coiilusión con la variable extcrna x porq~ie tabla (le símbolos sicnipre las tlistirigikí y dctwminari la v:iri. m se convierte en 2 micritras que y toma el valor de 1.m se convierte en I y y en O. de modo que no se hacen mis llarnadas. el registro de activación de la tercera Ilnmada a g ocupa (y sobreescribe) el irea de memoria previamente ocupada por el registro de activación de f . En esta llamada.3 static int x = 1. puesto que debe corttinuar n triivcs (le todas I:is Il:tnt. lo que da conio resultado el ambiente de ejecución mostrado en la figura 7. y el programa llega a su fin. la cual establece m a I y y a O. Ahora las llamadas a g y f salen de escena (con f disniinuyendo su variable local estática x antes de regresar). los registros de activación restantes son extraídos de la pila. Ida primera Ilamada desde main es a g ( 2 ) (puesto que x tiene el vülor 2 en ese punto). / * prototipo * / void £ (int n ) { ejemplo 7. da a g. .idas a f . la manera siguiente.En esta llanta. Advierta cómo. en la figura 7. g hace la Ilainada f (1) y f a su vez hace la llamada g ( 1).6a) (página 356). debe ser asignada en i I irea glohiillcstitica jimto con la variable externa x.

5 se ilustra en la Figura 7. En v c de eso.5 durante la tercera llamada x (from f ) : Area global/estática Registro de act~vación Registro de activación de la llamada a g m: 1 fp-.6 representan los ambientes durante las llamadas representadas por cada una de las hojas de los árboles de activación.6 (a) (a) Ambiente de ejecución del programa de la figura 7. el árbol de activación del grama de Ia figura 7. Adkierta que los ambientes inoatrados en las figuras 7.5 durante la regunda llamada a cr x (from f): 1 Área global/estática Registro de activación de main vinculo de control dirección de retorno Registro de activación de la llamada a g vinculo de control dirección de retorno vínculo de control dirección de retorno Registro de activación de la llamada a f Registro de activación de la llamada a g (b) Ambiente de ejecución (b) del programa de la figura 7.Firj~lra1. En general. vínculo de control dirección de retorno Registro de activación de la llamada a g Una herramienta útil para el análisis de estructuras de llamada complejas en un pr grama es el árbol de activacián: cada registro (o Ilamada) de activación se convierte en u nodo en este &bol. tlchen ser eiicontradas mediante despl:tmmiento desde cl apuntador ~ . y los descendientes de cada nodo representan todas las llamadas hechas durante la llanada conespondiente a ese nodo. Por ejemplo. la pila de los regi5tros de activación al principio de una llamada particular ticnc una e5tructura equivalente n la trayectoria desde el nodo correspondiente en el irbol de activación hasta la r a í ~ .3 es lineal y se representa (para las entradas 15 y 10) en la figura 7 mtentras que el árbol de activación del programa de la figura 7. ya no se puede tener acceso a los parámeIros y las variables iocales rnediante direcciones fijas como en un ambiente completan~cnte cst5tico. Acceso a nombres En un ambiente basado en pila.4 y 7.

6). como lo demuestra el ejemplo siguiente. y las referencias a m y a y pueden escribirse en código de máquina (suponiendo convenciones de ensamblador estándar) como 4 ( fp) y 6 ( fp).respectivament+.Ambientes de ejecucion basados en pila main ( ) l de marco actual. Cada registro de activación de g tiene exactamente la misma forma. char c) { int aflO1. se encuentran siempre en exactamente la misma ubicación relativa en el registro de activación. . double y. - Ejemplo 7. puesto que las declaraciones de un procedimiento esrán fijadas en tiempo de compilación y el tamaño de memoria para asignarse a cada declaración está fijado por su tipo de datos. dirección de retorno / t L rn~ffset Se puede tener acceso tanto a m como a y mediante sus desplazamientos ("offsets") fijados desde el fp. Llamenios a estas distancias moff set y yOf fset. En la mayoría de los lenguajes el desplrzamiento para cada declaración local se calcula todavía de manera estática mediante el compilador.5 (véanse también los ambientes de ejecución ilustrados en la figura 7. tenemos que mof f set = +4 y yOff set = -6. que las variables enteras requieren de 2 bytes de almacenamiento y que las direcciones requieren de 4 bytes.4 Considere el procedimiento C void f (int x. durante cuaiquier llamada a g. Las estructuras y arreglos locales sólo tienen dificultad para asignar y calcular direcciones en el caso de las variables simples.Entonces. Con la organización de un registro de activación como el que se muestra. Consideremos el procedimiento g e n el programa C de la figura 7. Por ejemplo. tenemos la siguiente ilustración del arnhiente local £ P / vínculo de control ~ . y el parámetro m así como la variable local y. supongamos concretamente que la pila de ejecución se incrementa desde las direcciones de memoria más altas hasta las más bajas.

6.itica ~ i j . se pucdc tener . dependiendo de la ubicación de i y la arquitectura. ciiatro bytes para direcciones. § No puede tenerse acceso a los nombres estiticos y no locales en este ambiente de 13misrna manera que :i los nornbrcs locales.y .CAP. De este motlo. puede necesitar una simple instruccióri.dos los nombres no 1oc.iles son glob:iIcs y. tendríamos los siguientes valores de desplazamiento (suponiendo nuevamente una dirección negativa de creeimientu para la pila todos los cuales se calculan en tiempo de comp. en la Figura 7. en el caso que estamos considerando aquí (Icnguajes 'on auscncia de procediniientos locales) ti. un byte para caractere ocho bytes para punto flotante de precisión doble. suponiendo dos bytes para enteros. por 10 tanto.icccsi) a ella directairicnte ( o n~edi:iritc ~ :%sí . la variable x externa (global) d<:C ticne una uAicacicín est. Eri realidad. 7 1 AMBIENTES DE EJECUCIÓN El regi~tro activación para una llamada a f aparecería como de 7 1 De8pkir - ----_Desplazamiento vinculo de control I I i dirección de retorno y. csti(iicos.24 -32 a Y (aquíd factor de 2 en el producto 2 * i es el factor de escala resultante de la suposición de que los valores enteros ocupan dos bytes).ilación: Nombre X C Desplazamiento +S +4 . Un acceso a memoria así.

. Copia el fp al sp.ir un virlor <leretorno cn tina iihic. Almacena la dirección de retorno en el nuevo registro de activación (si es necesario). Realiza un salto hacia el código del procedimiento a ser llamado. esto se consigue copiando éste en el fp en este punto).sos siguientes. 4.iciOn dispoiiihle .5 Considere la situación precisamente antes de la última llantada a g en L figura 7. 2. entonces se requiere de un niecanistno de acceso niis complejo (éste se describirá rnás adelante en esta sección). Cambia el fp de manera que apunte al principio del nuevo registro de activación (si existe un sp. Cuando un procedimiento sale de escena. 5. T:~nibiénigiiiirn iiccesidad dc siru. 1. 3. Realiza un salto hacia la dirección de retorno 4. Calcula los argumentos y los almacena en sus posiciones correctas en el nuevo registro de activaciún del procedimiento (esto se logra insertándolos en orden en la pila de ejecución). Cambia el sp para extraer los argumentos.6b): a / 1 1 fR-+ (resto de la pila) 1 1 Registro de activación de la llamada a g i vínculo de control 1 / dirección de retorno Cuando se realiza la nueva llamada a g. Almacena (inserta) el fp como el vínculo de control en el nuevo registro de activaci0n. Se tiene acceso a la variable local estática x de f exactamente de la misma manera. Advierta que este mecanismo implenienta ámbito estático (o léxico).Ambientes de ejecución basados en pila 359 desplazamiento desde algún otro apuntador base aparte del fp). Si se desea ámbito dinániico. 1. se inserta primero el valor del p a r h e t r o m en la pila de ~jecución: I:i 8. 2. como se describió en e1 capítulo anterior.* Cuando un procedimiento es llamado. Ejemplo 7. Esta <lescripcidn ignora coiilquier grab:icih~de registros que deba tener lugar. 3. La secuencia de llamada La secuencia de Ilamüda ahora comprende aproximadamente los p. Carga el vínculo de control en el fp.

7 : Entonces el f p se inserta en la pila: 1 1 (resto de la pila) 1 Registro de activación de la llamada a g vinculo de control dirección de retorno / 1 En (resto de la pila) 1 1 Resistro de activación de a llamada a g ¡ vinculo de control .CAP. dirección de retorno i Registro de activac~ón de la llamada a g vinculo de control Nuevo registro de activación de la llamada a g '1 espacio libre . 7 1 AMBIENTES DE ~ J E C U C ~ ~ N .f1 dirección de retorno vínculo de control Ahora se copia el sp en el fp. la dirección de retorno se inserta en la pila y se hace el a la nueva llamada de g: (resto de la p~la) vínculo de control i fP -+ SP .

Otra opción cs utilizar tiii tnecanisrno preprocesador corno el ap íarguinent pointer. g asigna e inicializa la nueva y en la pila para completar la construcción del nuevo registro de activacibn: 1 1 (resto de la pila) m: 2 1 Regfstro de activación de la llamada a g -vínculo de control dirección de retorno y: 1 f~ - m: 1 vínculo de control dirección de retorno Nuevo registro de act~vación de la llamada a g l v l Tratamiento de datos de longitud variable Hasta ahora hemos descrito una situación en la cual todos los datos. Ésta y otras posibilidades se tratan con mis dctnlle en los I I . y 2) el tamaño de un paráinetro de arreglo o una vanable de meglo locd puede variar de llamada en llamada. En ocasiones un compilador debe tratar con la posibilidad de que los datos puedan variar. Un ejemplo típico de la situación número 1 es la función p r i n t f de C. Por consiguiente. tanto en el número de objetos de dato? como en el tamaño de cada objeto. ya sean locales o globales. pueden encontrarse en un lugar fijo o en un desplazamiento fijo del fp que puede ser calculado por el cornpilador. tiene sólo un argumento. mientras que tiene cuatro argumentos (incluyendo la cadena de formato "%¿i%s%cl'). Dos ejemplos que se presentan en lenguajes que soportan amh~entes basados en pila son 1) el número de argumentos en una llamada puede tener variaciones de una llamada a otra. apuntador de argutnentos) en las itrquitecttir.Ambientes de ejecucibn basados en pila 36 I Finalmente. Entonces.~~ VAX. c1 primer parhmetro (el cual le dice al código para p r i n t f cuántos parámetroi 1n5s hay) está siempre ubicado en ctn desplazamiento fijo desde el fp en la implementación descrita anteriormente (de hecho -+4. Los compiladores de C por lo regular manejan esto insertando los argumentos para una llamada en orden inverso en la pila de ejecución.crnpleando las suposiciones del ejemplo anterior). donde el número de argumentos 5e determina a partir de la cadena de formato que se pasa como el primer argnmento.

Eho en re:ilid:td no es siilicicnte para Ada.. .. end Sum. en tanto que se almacena un apuntador a los datos reales en una uhicaci que puede ser predecida en tiempo de compilación.) Un método pico para manejar esta situación es emplear un nivel extra de indirección para los datos longitud variable. el cual perniite pmccdimientos . una llamada Sum con un arreglo de tamaño 10): f 1 (resto de la pila) low: high: 1 Registro de activación de la llamada a sum . grocadure Sum (low. en concreto. .' podríamos implementar un registro de tivación para sum de la manera siguiente (esta imagen muestra. mientras se realiza la asignación rea el tope de la pila de ejecución de una manera que pueda ser administrada mediante el sp rante la ejecución.6 Dado el procedimiento Sum de Ada.ini<fados:véase cl J . (Advierta que la variable local temp también tiene un tamaño impredecible. .. el acceso a A [ i ] puede con\eguirse calcu1. Area de datos de longitud variable Ahora.. . 7 1 AMBIENTE5 DE EJECUCI~N Un ejemplo de la situación número 2 es el arreglo no restringido de tida: type Int-Vector is array(1NTEGER ranga e > ) of INTEGER.indo ! .. Ejemplo 7. A: Int-Vector) return INTEGER is temp: IntArray (low. t tamaño de A : 10 vínculo de control dirección de retorno i: . como se definió anteriormente.high). .. begin .iiite en esta sccciiín.CAP. y suponiendo 1 misma organización para el ambiente que antes.high: INTEGER.. por ejemplo..iii:ilisis inis :iciel..

. Vale la pena reiterar que los arreglos en C no caen dentro de la clase de tales datos de longitud variable. de modo que los parámetros de arreglo son pasados por referencia en C y no asignados localmente (y no conducen información de tamaño). y el cociente i / k (pendiente de la suma con el resultado de la llamada f ( j ) ). pero el tamaño de la parte de la variable local. En efecto.Ambientes de ejetution basados en pila 363 donde la @ significa indirección. esta implementación requiere que el compilatlor calcule previamente un atributo de tamaño de variable locd para cada procedimiento y lo almacene en la tabla de símbolos para su uso posterior. De manera alternativa.esta expresión. En este último caso la pila de ejecución puede tener el aspecto que sigue en el punto justo antes de la llamada a f : Il (resto de la pila) vinculo de control dirección de retorno dirección de x I i 1 Pila de temporales resultado de i/j Nuevo registro de activación de la llamada a f (por ser creado) Registro de activación del procedimiento que contiene la expresión En esta situación. Las variables locales de longitud variable pueden ser tratadas de manera similar. por ejemplo. la suma i+j (en espera de la multiplicaciún). ya que el número de teniporales requeridos es una cantidad cn tiempo de compilación. los arreglos en C son apuntadores. por lo común no se conoce en el punto de Ilamada. 9 Advierta que en la implementación descrita en el ejemplo anterior. es necesario grabar tres resultados parciales por medio de la llamada a f : la dirección de x [i] (para la asignación pendiente). la secuencia de llamada descrita previamente utilizando el sp funciona sin cambio. Declaraciones anidadas y temporales locales Éstas son dos complicaciones más para el ambiente de ejecución básico basado en pila que merecen mencionarse: las declaraciones anidadas y temoorales locales. y donde de nueva cuenta suponemos que hay dos bytes para enteros y cuatro bytes para direcciones. el compilador también puede calcular fácilmente la posición del tope de la pila a partir del fp (cuando no hay datos de longitud variable). De este modo. Las temporales locales son resultados parciales de c6lculos que deben ser grabados a través de las Ilamacias de procedimiento. la expresión en C En una evaluación de izquierda a derecha de. Considere. o podrían almacenarse como temporales en la pila de ejecución antes de la llamada a f . el elemento que Ilarn:i debe conocer el tamaño de cualquier registro de activación de S w n El tamaño de la parte del parrímetro y la parte de administración es conocido por el compilador en el punto de llamada (puesto que se pueden contar los tamaños de argumentos y la parte de administración es igual para todos los procedimientos). Estos resultados parciales podrían calcularse en registros y grabarse y recuperarse según algún mecanismo de administración de registros.

1 1 . int k.CAP.... R- dirección de retorno Registro de activación de la llamada a P Área asignada al bloque A 3 P . y las declaraciones del bloque el bloque B no necesitan asignase simultáneamente. Sin embargo. I espacio libre . Un método más simple es manejar las declaraciones en bloques anidados como se inanejan las expresiones temporales. Por ejemplo. esto sería inefic puesto que tales bloques son mucho más simples que los procedimientos: un bloque así tiene parimetros ni dirección de retorno y siempre se ejecuta de inmediato. A: { double x. justo después de introducir el bloque A en el código C de muestra que se acaba de proporcionar.. int i. Un compilador trntariu un bloque la misma manera que trataría un procedimiento y crearía un nuevo registro de activación c da vez que se entre a un bloque y lo descartaría a la salida. Las declaraciones locales de cada uno de estos ques no necesitan asignarse hasta que se entre al bbque. char * a. asignándolas en la pila a medida que se entra al bloque y desasignándolas a la salida. la pila de ejecución tendría el aspecto que sigue: l i resto de la pila) Y: vínculo de control f-. cada uno con dos declaracio locales cuyo ámbito se extiende sólo sobre el bloque en el cual se encuentran localizados decir. . anidados dentro del cuerpo del procedimiento g... en vez del que se llama de otra parte. 7 1 AMBIENTES DE EJECUCI~N Las declaraciones anidadas presentan un problema semejante.. En este código tenemos dos bloques (también llamados sentencias compuestus) etiquet A y B. double y) í char a. Considere el código e void p ( int x. 1 B:{ .. . hasta la siguiente llave de ciene)... int j. .

.idas (observe que la n cn r tiene un desplazamiento diferente de la n cn g).8.2 Ambientes basados en pila con procedimientos locales Si se permiten declaraciones de procedimiento local en el lenguaje que se está compilando. Como podemos ver en la figura 7. cualquier mención de n dentro de q debe hacer referencia a la variable entera local n de p. Considere. esta n no se puede encontrar utilizando algo de la información de administración que se conserva en el ambiente de ejecución hasta ahora.9. Se podría encontrür a n empleando tos vínculos de control. De cstc niodo.3.17 desde el fp de p (suponiendo otra vez 2 bytes para enteros.9 vemos que la n en el registro de activación de r se podría encontrar siguiendo el vínculo de control. si estamos dispuestos a aceptar el ámbito dinámico. cn tina iniplcnienlacicín . no sólo esto irnple~nenta ámbito dinümico. En particular. el código en Pascal de la figura 7. puesto que no se ha previsto nada para referencias no locales y no globales. tales datos deben ser asignados antes de cualquier dato de longitud variable. por ejemplo. Desgraciaciatncnte. Si se utiliza la regla de ámbito estático estándar. sino que los tiesplazamientos pael ra los cuales n puede encontrarse pueden variar con tfifercntcs ll:tm. 4 bytes para direcciones. un niétodo que veremos de nuevo en breve). en el código que se acaba de dar.Ambientes de ejecución basados en pila y justo después de entrar al bloque B tendría el aspecto que sigue: (resto de la pila) fu .onces la n de p se podría encontrar siguiendo un segundo vínculo de control (este proceso se conoce como encadenamiento. 7. Durante la llamada a q el ambiente de ejecución podría parecerse al de la figura 7. mientras que k en el bloque B tendría un desplazamiento de . Por ejemplo. la variable local j para el bloque A tendría un desplazaniiento de . y si r no tuviera declaración d e n . Al observar la figura 7. - vínculo de control dirección de retorno Registro de activación de la llamada a P Área asignada al bloque B Al realizar una implementación de esta naturaleza se debe tener cuidado de asignar declaraciones anidadas de manera tal que los desplazamientos desde el fp del bloque de procedimiento circundante se puedan calcular en tiempo de compilación. entonces el ambiente de ejecución que hemos descrito hasta ahora es insuficiente.13. 8 bytes para números reales de punto flotante y I byte para caracteres). página 366 (en Ada podrían escribirse programas similares). ciit.9.

y determinar su desplazamiento. procedure q. las tablas de símbolos locales de cada procedimiento deben conservarse durante la eie-:.8 Programa e Pascal q e n u o muestra la referencia n local y no global program nonLocalRef. procedure p. Fi~ura1..-i cución con el fin de permitir que un identificador sea buscado en cada registro de activ para ver s i existe.8 vinculo de control direcc~ón retomo de vinculo de control Registro de activación del programa principal Registro de activación de la llamada a p Registro de activación de la llamada a r Registro de activación de la llamada a q SP - vínculo de control dirección de retorno espacio libre . 7 1 AMBIENTES DE EjECuClbN . end. Figura 7. ( * g * ) procedure r(n: integer). begin S.9 Pila de ejecución para el programa de la Cgura 7. i I así. var n: integer..1 CAP. ( * p *) begin ( * main * ) P1 end. Ésta es una complicaciún adiciona portante para ekambiente de ejecución. begin (* (* r *) p *) n := 1. r(2). begin (* una referencia a n es ahora no local no global * ) end. end.

9 modificada para incluir vínculos de acceso. Por qjemplo. en donde n se encontrará en un desplazümiento tl. En este nuevo ambiente. pero no la uhicaci<in exacta de sii registro de activirciún. apuntan al registro de activación de g. aun cuando no es una cantiJüd propia del tiempo de compilación. eslo puede conseguirse en código cargando el vínculo de acceso en un registro y posteriormente accediendo a n mediante desplazamiento desde este registro (que ahora funciona como el fp). entonces se puede tener acceso a n dentro de p como -6(f) después de que r se haya cargado coi1 el valor 4jfp) (el vínculo de acceso tiene desplazamiento +4 desde el fp de la figura 7. Por esta razón. y se tiene acceso a él mediante el mecanismo de referencia global. Este vínculo de acceso es como el vínculo de control.10). Considere.11. Ahora una referencia no local a n dentro de q provocará que se siga el vínculo de acceso. por ejemplo. es agregar una pieza extra de inforinación de administración que se conoce como vínculo de acceso a cada registro de activación. se puede insertar un vínculo de acceso nulo o de otro modo arbitrario sólo para ser consistentes con los otros procedimientos.ya que tanto r como q están declarados dentro de p. El procedimiento de definicid es por supuesto conocido.10 muestra la pila de ejecución de la figura 7. Por lo común.jado.Ambientes de ejecución basados en pila 3 67 La solución a este problema. puesto que éste siempre será un registro de activación de p. tanto de r como de q. no hay necesidad de un vínculo de acceso. los vínculos de acceso de los registros de activación. utilizando las convenciones de tamaño descritas anteriormente. el vínculo de acceso en ocasiones también se conoce como vínculo estatico. sólo que éste apunta al registro de activación que representa el ambiente de d@nición del procedimiento en vez de hacerlo al ambiente de llamada.'" La figura 7. de modo que ninguna referencia no local dentro de g debe qer una referencia global. Esto se debe a que g es un procedimiento global. (De hecho. si el registro r se utiliza para el vínculo de acceso. que también irnpleinenta el ámbito estático. Firlura 7 10 P~lade elecuoón para el programa de la C p a 7. donde la referencia no local es a una declaración en el siguiente ámbito exterior. como se mdica por el comentario entre picoparéntesis en la ubicación donde podría ir.8 con vinculos de acceso agregados & - - <no hay vinculo de acceso> vínculo de control dirección de retorno - Registro de activación del programa principal Registro de activación de la llamada a Q Registro de activación de la llamada a r n: 1 n: 2 vínculo de acceso - vinculo de control dirección de retorno vinculo de acceso--vínculo de control dirección de retorno Registro de activación de la llamada a q Advierta que el registro de activación del procedimiento g mismo no contiene ~ínculo de acceso. De este modo. . También es posible que las referencias no locales hagan referencia a declaraciones en ámbitos más distantes. el código en la figura 7.) El caso que hemos estado describiendo es realmente la situación más simple. LO.

Como ejemplo concreto. un proceso que se denomina encadenamiento de acceso. en el código de la figura 7. el nivel de anidación se incrementa en I. grocedure g. Cargar 4(r) en el registro r. x debe ser alcanzada siguiendo dos vínculos de acceso. Esto requiere que el compilador calcule previamente un atributo de nivel de anidación para cada declaración. se puede tener acceso a x de la figura 7. end. La if ra 7. var x: integer.debe atravesar dos niveles de ámbito para encontrar x. porque el nivel de anidacidn se increnienta cuando se introduce p: el proccdiniiento q también riene asignado un nivel de anidación 1. Finalmente. En este ambien te. hace referencia a la x de g. la asignación para x dentro de r. porque nuevamente el nivel de anidación se incrernenta cuando se introduce q.y e1 procediniiento r tiene asignado el nivel de anidación 2. . end. procedure q. end. Para que el método de encadenamiento de acceso funcione.1 1 e1 procedimiento g tiene asign~do nivel de anidación O porque es global. ( * q * ) begin 9. dentro de r el nivcl tic anid. Por lo regular.12 (utilizando las conuenciones de tamaño anteriores) de la manera siguiente: Cargar 4(fp) en el registro r. begin x := 2. En este código el procedimiento r es declarado en el procedimiento q. la variable x tiene asignado el nivel de anidación 1.tción nuev:nnente se incrcrncnta a 3.12 muestra la pila de ejecución después de la (primera) llamada a r (puede haber má de una llamada a r.. Por ejemplo. porque es local a g.CAP. De este modo.11 ra Código en Pascal que muestra el encadenamiento de acceso grograni chain. ( * p * ) begin ( * main *) B. 1 1 AMBIENTES DE EIECUCI~N ~ i ~ uj. ( * r * ) begin r. el compilador debe ser capaz de determinar a través de cuintos niveles de anidación se hará el encadenamiento antes de tener acceso al nombre localmente. Este encadenamiento de acceso es implementado al ir a buscar de manera repetida el vínculo de acceso utilizando el vínculo previamente alcanzado como si fuera el fp. procedure r .. y cada vez que se introduce una función o procedimiento (durante la compilación). if then p. end. el ámbito más remoto (el nivel de programa principal en Pascal o el ámbito externo de C) tiene asignado el nivel de anidación 0.puesto que r puede llamar de manera recursiva a p). . el cual a su es declarado en el procedimiento p. y se tiecrementa en la inisrna cantidad a la el salida. Acceder ahora a x como -6(r).

precisamente como si fuéramos a tener acceso a una vuflable en el tnisrno nivel de anidacirín que el del proccdimento que se e\tá llamando. Existe un método para implementar vínculos de acceso en una tabla de búsqueda indizada mediante nibe1 de anidación que no conduce al gasto de ejecución del encadenamiento. En general. En la implementación mostrada. \i el procedimiento es local (la ditcrencia cn nicele\ de anidacilin e5 O). Esto se puede conseguir empleando la información del nivel de anidacibn (en tiempo de coinpilacibn) adjunta a la declaración del procedimiento que se e\tá llamando. el número de vínculos de acceso a seguir es la diferencia entre estos dos niveles de anidación. La estructura de datos utilizada para este método se denomina despliegue ("display"). En redidad.. los niveles de anidación rara vez tienen más de dos o tres niveles de profundidad. Sin embargo. si la diferencia entre niveles de anidación es m. y después de una salida el sp debe ser ajustado mediante una cantidad extra para eliminar tanto el vínculo de acceso como los argumentos.ie puede continuar teniendo acceso mediante los métodos directos previamente analizados. Su estructura y uso se tcitan en los ejercicios. ya que es necesario ejecutar una larga secuencia de instrucciones para cada referencia no local con una gran diferencia de anidación. y la mayoría de las referencias no locales son para variables globales (nivel de anidación O). durante una llamada.r . en la práctica.Ambientes de ejecución basados en pila i ) t Registro de activación del programa principal C- <no hay vínculo de acceso> vínculo de control dirección de retorno x:... Registro de activación de la llamada a p Registro de activación de la llamada a q Registro de activación de la llamada a r vinculo de acceso vinculo de control dirección de retorno vinculo de acceso vincuto de control dirección de retorno espacio libre fo- 8D -. la asignación a x se presenta en el nivel de anidación 3. el vínculo de accew y el vínculo de control wn iguales (y también wn iguales a l fp en el punto dc la Ilarna<la). en la situación anterior. Naturalmente. y x tiene nivel de anidación 1. el vínculo de acceso debe ser insertado en ia pila de ejecución justo antes del fp. y el resto utilizando r. todo lo que necesitarnos hacer es generar una cadena de acceso. La dirección así calculada cerá la del vínculo de accew apropia(lo. la primera utilizando el fp. a las cuales . Por ejemplo. la secuencia de llamada Los cambios a la secuencia de Ilamada necesarios para tmplementar los vínculos de acceso son relativamente directos. de modo que deben seguirse dos vínculos de acceso. Puede parecer que el encadenamiento de accew es un método ineficiente para el acceso de variables. entonces el código que debe uef generado para el encadenaniiento de acceso debe tener m cargas para un registro r.1 Ahora la cantidad de encadenamiento necesaria para tener acceso a un nombre no local 5e puede determinar comparando el nivel de anidación en el punto de acceso con el nivel de anidación de la declaración del nombre. . El único problema es el de encontrar el vínculo de acceso de un procedimiento durante una Ilamada.

ya que el cálculo es real en tiempo de ejecución (utilizando los niveles de anidación en tiempo de compilación tiempo de compilación. se reqiiiere un paso de acceso para calcular el vínculo de acceso de q..8. Por ejemplo. por ejemplo.13 Pila de ejecución despues de la segunda llamada a r en el código de la Cgura 7. En un lenguaje así. Advierta que incluso en la presencia de activaciones múltiples del ambiente de d ción.10.C P 7 1 AMBIENTES DE EJECUCION A. En esta ilustración r tiene dos registros de activacidn diferen dos vínculos de acceso diferentes. de la manera e: en que se describió en la sección anterior. efectiv en la figura 7.1 1. se debe calcular previamente el . cuando se llama un procedimiento que ha sido pasado como un parámetro.1 1 <no hay vínculo de acceso> vínculo de control dirección de retorno x:. este proceso calculará el vínculo de acceso correcto.13.3 Ambientes basados en pila con parámetros de procedimiento En algunos lenguajes no sólo se permiten procedimientos locales. mientras que la declaración de q conlleva un anidación de l (porque q es local a p y dentro de p el nivel de anidación es 1).. d - Registro de activación del programa principal Registro de activación de la llamada a p Registro de activación de la llamada a q r 1 vínculo de acceso vinculo de control 'dirección de retorno P vínculo de aecesovínculo de control dirección de retorno vínculo de control dirección de retorno vínculo de acceso vínculo de control dirección de retorno Registro de activación de la llamada a r Registro de activación de la llamada a p Registro de activación de la llamada a q Registro de activación de la llamada a r 7. De e ma. la pila de ej después de la segunda llamada a r (suponiendo una llamada recursiva a g) se p al de la figura 7. es imposible para un compilador generar cddigo que permita calcular el vínculo de acceso a1 niomento de la Ilamada. el vínculo de acceso de q apunta al registro de activación de p (y es mo que el vínculo de acceso de r). Figura 1. la Ilamada ri q desde dentro de r en la figura 7. apuntando a los diferentes registros de aCtivación los cuales representan diferentes ambientes de definicidn para r.y. En el de r estanios a nivel de anidación 2.3. dado el código de la figura 7. Considere. En v r de esto. sino que los procedimientos también se pueden pasar como paránietros.

begin x := 2. end . un apuntador de instrucción y un apuntador de ambiente)juntos representan el valor de un parimetro de procedimiento o función y se tes conoce coniúnmente como cerradura (porque el vínculo de acceso "cierra" los "huecos" causados por las referencias no locales). Este par de apuntadores (un apuntador de código y un vínculo de xceso. el ep de a es utilizado como el vínculo estático en su registro de activación. en la cual el procedimiento local r de q es pasado a g.16. el cual tiene un proccdirnieuto p3con un parámetro a que también es un procedimiento.15 (página 372). end. Después de la llarnada a p en q. ' Considere el programa en Pascal estándar de la figura 7. Cumdo p es llamado. De esta forma. esta llamada todavía debe encontrar la variable no local x en la activación de q. ep>. . y ep para referirnos al apuntador de ambiente (vínculo de acceso) del procedimiento. procedure ~(procedure a).14. begin a. un valor de parimetro de procedimiento ya no puede ser visto como un simple apuntador de código. ip para referirnos al apuntador de instrucción (apuntador de código o apuntador de entrada) del procedimiento. end. apunta 4 ambiente de ia llamada a q en la cual está definida r). procedure g. begin writeln(x). donde ip es un apuntador :ti código de r y ep es und copia del fp en el punto de la llamada (es decir.unbicntc en el cual las referencias no locales son resueltas. end. cuando a es llamada dentro de p. pfr). Entonces. var x: integer. la cual representa el ambiente justo después de la llamada a g en q. como se indica en la figura 7. ep>. sino que también debe incluir un apuntador de acceso que defina cl . grocedure r. ( * q * ) begin ( * main * ) 9.la Ilamada a a dentro de g en realidad llama a r .' Escribiremos las cei~aduras como <¡p. El valor del ep de a se indica mediante la línea discontinua de la figura 7. o bien. a es constmido como una cerradura <ip.Ambientes de ejecución basados en pila 37 1 vínculo de acceso para un procedimiento y pasarse junto con un apuntador al código para el procedimiento cuando éste se pasa como un parámetro. 5 F~qura7 14 Cbdigo en Parcal estándar ton un procedim~entocomo parámetro program closureEx(output).

En realidad...~. . . no obstante. ~ . ya dispone de un vínculo de acceso almacenado en el registro de activación el cual debe ser obtenido e insertado en el nuevo registro de activación.. .16 Pila de ejecución justo después de la llamada a a en el código de la ligura 1.17 muestra que el ambiente de la figura 7. .. ~ .-<: . . vínculo de control dirección de retorno ospacio libre Registro de activación de la llamada a p figura 1. .. ...14 <no hay vínculo de acceso> vínculo de control Registro de activación del programa principal . .' .. ep> de los procedimientos se vuelve un requcriniiento todas esas situaciones. . por razones de simplicidad o uniformidad. La figura 7. . Por ejemplo. esta distinción entre procedimientos ordinarios y parjmetros de procedimiento y conservar tetlos los procedimientos como cerraduras en el ambiente. . o si los valores de procedimiento se pueden calcular dinámicaniente. / Registro de activación de la llamada a Q I \ Registro de activación de la llamada a p vinculo de acceso vinculo de control Registro de activación de la llamada a a La secuencia de llamada en un ambiente como el que acabamos de describir debe ah ra distinguir claramente entre procedimientos ordinarios y parámetros de procedimiento. Un parametro de procedimient otra parte...---.14 <no hay vínculo de acceso> vinculo de control Registro de activación del programa principal Registro de activación de la llamada a Q . . U procedimiento ordinario. como antes. .icen...15 Pila de ejecución justo después de la llamada a g en el código de la figura 1. 1 / AMBIENTES DE EJECUCI~N Figura 7.372 CAP. cuanto mis general es un lenguaje en su tratamiento de procedimientos. A .' .'. . ... £9 sg ..+$ . - a:<ig. . La ubicación cúdigo para el procedimiento...dos en el mibiente como cerraduras.. / . en su lugar debe realizarse una llamada indirecta al ip almacenado en el registro de activación actual. m& razonable se vuelve un método así.. I . Un escritor de compiladores puede querer evitar.16 ditría la impresión de que todos los valores de procedimiento están alm.._ep> íño3áfvi~culode acceso. ...... .. . es llamado buscando el vínculo de acceso por medi del nivel de anidación del procedimiento y saltando directamente al código del proce miento (el cual es conocido en tiempo de compilación). si se permiten variables de procedimiento. ~~ ~.... no se conoce directamente por el compilador. .. ~ . entonces la representación <¡p.~".. . .. .

p. observamos que tanto C como Modula-2 y Ada evitan las complicaciones dc~critas esta subsección: C. Pascal y Ada. En otras pal. .tbras. C evita este problema declarando simplemente que un programa así es erróneo (aunque el compilador no dará un mensaje de error). ya que el registro de activación del procedimiento será desasignado de la pila.) Una asignación addr = dangle í ) causa ahora que addr apunte a una ubicación insegura en la pila de activxiún cuyo valor puede ser cambiado arbitrariamente por llamadas posteriores a cualquier procedimiento.. tales ambientes tienen limitaciones. Sin embargo. El ejemplo más h p l e de esto es cuando la dirección de una variable local es devuelta.. la semántica de C está constririda alredcdor del ambiente tundaincntal htsado cn pila. Registro de activación de la llamada a q vínculo de acceso> vínculo de control vínculo de acceso vínculo de control dirección de retorno espacio libre Registro de activación de la llamada a p Regfstro de activacidn de la llamada a a 1 Finalmente. porque no tiene procedimientos locales (aunque tiene vaen riables y parámetros de procedimiento).-> ~ r e globallestática a Registro de activación del programa principal <no hay vínculo de acceso> vínculo de control dirección de retorno r: <ig. porque no tiene variables o parámetros de procedimiento. En particular. como en el código de C: int * dangle(void) í int x . un ambiente basado en pila producirá una referencia colgante cuando se salga del procedimiento.Memoria dinámica P:~. ya sea de manera implícita o explícita. y Ada.-> q:<iP.P. MEMORIA DINAMICA 1 Ambientes de ejecucibn completamente dinámicos Los ambientes de ejecución basados en pila que se analizaron en la sección anterior son las formas más comunes de ambiente entre los lenguajes imperativos estándar tales como C. en un lenguaje donde una referencia a una variable local en un procedimiento puede ser devuelta al elemento que llama...> a:<ip. Modula-?. return &x. eg. porque tiene una regla especial que restringe los valores de parámetro de procedimiento y variable de procedimiento a procedimientos globales. e.

para esta amplia clase de lenguajes. 1 Sin embargo.id¡cional de csw . el registro de activación que wle pennnnecc en la rnernoria. pasarse como parámetros y devolverse como resultados. proc gfint x) { int f (void) / * función local ilegal */ { return x. Por ejemplo. Naturalmente.18 produciría una referencia colgante indire parámitro x de g.mhiente puede ser encapsulada en un administrador de menioria que i-eetnpliiia las operaciones de la pila de ejecucirín con rut¡ti:is inis gciici-:iIcs de asignacitin . Un principio esencial en el diseño de un lenguaje funcional es que las funciones sean tan generales como sea posible.) Fivure ? 18 CSdigo preudo-l que muestra una refertncta colgante causada por el retorno de una functón local typedef int (* proc) (void) . y esto significa que las funciones deben poderse definir localmente. y la capacidad de encontrar y desasignar áreas inaccesibles de memoria en momentos arbitrarios durante la ejecución (este proceso se denomina recolección de basura). deben establecer una regla especial que erróneos a tales programas. 1 main ( ) { proc c. De esta forma. C evita este problema prohibiendo procedimientos locales. conlo LlSP y ML. lenguajes. registro de activación permanece igual: el espacio debe ser asignado para par6mctros y variables locales. Naturalmente. la estructura básica de un . el código de la figura 7. Llamaremos a un ambiente así completamente dinámico. a1 cual se puede tener acceso llamando a f después de que g ha sali escena. c = g(2). porque pueden desasignar registros de activación sólo cuando han desaparecido todas las referencias a ellos. como Modula-2. y esto requiere que los registros de activación sean dinámicamente liberados en momentos arbitrarios durante la ejecución. De este modo. existe una extensa clase de lenguajes donde tales reglas son inaceptable es decir. ahora c~i'indoel control es tleiuelto al elemento que llama (y el vínculo de control es utilizado para restaurar el ambiente :interior). que tienen procedimientos locales adenxís de variables de cedimiento.CAP. / * debería imgrimir 2 * / return O. los lenguajes de programación funcionales. Un antbiente de ejecución completamente itináinico es mucho más coinplicado que un ambiente basado en pila. para ser desusigriatlo en algún momento posterior. parámetros y valores devueltos. } return E. tod:i la complejidad . . y todavía se neccsitan vínculos de acceso y de control. y se requiere una forma de ambiente más general. ya que involucra el seguimiento de las referencias durante la ejecución. (En Modula-2 la regla es que sólo los procedimientos glob pueden ser argumentos o valores devueltos: una retirada importante incluso de paráme de'procedimiento estilo Pascal. 1 1 AMBIENTES DE EJECUCION Un ejemplo algo más complejo de una referencia colgante se presenta si una futici' cal puede ser devuelta mediante una Llainacka. printf("%d\nw. un ambiente de ejecución basado en pila es inadecuado. si C permitiera dclinicio función local. A pesar de la complejidad adicional de esta clase de anihiente.c0).

74. Fin la necesidad de administración de memoria dinámica autom6tica. Esta estructura difiere de un registro tradicional en la manera en que se accede a las características de herencia y métodos. en veL de a la ratructura de clase. . un apuntador a su clase de definición. + 12.'" Los lenguajes orientados a objetos varían en gran medida en sus requerimientos para el ambiente de ejecución. Cada objeto conserva entonces. mientras que las variables de instancia pueden tener desplazamientos preúecibles (del mismo modo que las variables locales en un ambiente estándar). Ahora cada objeto contiene un apuntador a la tabla de función iirtual apropiada. El an6lisis siguiente iarnhi&n supone que siilo es13 disponible la herencia Ginple. mientras que gran parte del sfuerzo de diseño en C + + se ha ido en retener el ambiente basado en pila de C. En esta subsección proporcionamos una breve perspectiva general de la variedad de técnicas de implementación para estas características.2 Memoria dinámica en lenguajes orientados a objetos Los lenguajes orientados a objetos requieren de mecanismos especiales en el ambiente de ejecución para incrementar sus características adicionales: objetos. Es el rnétodo de clección en C+ t . Sin embargo. ésta es una estructura razonable para un lenguaje aitamente dinámico como Smalltalk. con las variables de instancia (miembros de dalos) como los campos del registro. donde pueden ocurrir cambios a la estructura de clase durante la ejecución.:I iwrciici:~ inúltiple se irata en alguno de los trabajos citados en I:I scccicin de notas y ~refcrenci. Una alternativa es mantener una descripción cornpleta de la estructura de clase en la memoria en cada punto durante la ejecución. Una alternativa a conservar toda la estructura de clase dentro del ambiente es calcular la lista de apuntadores de código para métodos disponibles de cada clase. Smalltalk y C+ son buenos representantes de los extremos: Smailtdk requiere un ambiente completamente dinimico similar al de LISP.) E m cimplificación rOlo funciona rt la misma estructura de claw e\tá tijada antes de la ejecución. y dmacenarla en la memoria (estática) como una tabla de función virtual (en la terminología de C+ +). más adelante en esta sección. y todos los apuntadores de método conservados como campos en la estructura de clase (esto se corioce en ocasiones como grófica de herencia). (Por supuesto. En ambos lenguajes un objeto en memoria puede ser visto como una combinación de una estructura de registro tradicional y un registro de activación. Analizaremos algunas de estas cuestiones en el diseño de un administrador de memoria de esta naturaleza. I. métodos. la ubicación de este . esto implica un gasto excesivo de eFpacio. De esta manera.Memoria dinimica 375 y desasignación. los apuntadores de método se registran sólo una vez (en la estructura de clase) y no se copian en la memoria para cada objeto. porque tos métodos son encontrados mediante una búsqueda de la jerarquía de clase. no ocurre lo mismo con los métodos. Aun así. junto con campos para sus variables de instancia. y ya no es necesario un recorrido en la jerarquía de clase con una serie de búsquedas en tabla. con la herencia mantenida mediante apuntadores de superclase. a través del cual son encontrados todos los métodos (tanto locales como heredados). herencia y fijación dinámica. Ésta tiene la ventaja de que puede Eer arreglada de manera que cada método tenga un desplazamiento predecibIe. Un mecanismo directo para iniplementar objetos podría Fer copiar por código de inicialización todas las cnructerísticas (y métodos) comúnmente heredadas de manera directa en la estructura del registro (con los métodos como apuntadores de código). Este mecanismo también irnplementa herencia y fijación dinimica. La desventaja de esto es que.is. Suponemos que el lector está familiarizado con la temiinologia y conceptos básicos orientados a objetos. por lo que deben ser mantenidos por nombre en una estructura de tabla de símbolos con capacidad de búsqueda..ipuntador también debe tener un despla~ainientopredecible.

un objeto de clase A aparecería en la memoria (con su tabla de función ra siguiente: apuntador de tabla tabla de funcibn vrrtual para A mientras que un objeto de clase B aparecería como se muestra a continuación: de función virtual tabla de función virtual para B Advierta cómo el apuntador de función virtual. y . La estructura de datos que maneja una asignación así se denomina cipilumirnto o montículo thetrp). Advierta tainbikn que la función f no obedece a la fijación dinámica en C+ + (puesto vru". no es declzada " i t d )y de esta forma no aparece en la tabla de función virtual (ni en gún otro sitio en el ambiente). en la mayoda de los lenguajes. si queríamos que las funciones generales tuvieran soporte completo. virtual void h( ) . una llamada a f se resuelve en tiempo de compilación. void f0. y este apilamiento por lo regular es asignado como un bloque lineal de mcmoria. 7. de manera que se conoce su desplazamiento antes de la ción. de tal . void £0. Sin embargo. 7 1 AMBIENTES DE EJECUCI~N Considere las siguientes declaraciones de clase de C + + : class A { public: double x .. 1. una vez agregado a la estructura del objeto manece en una ubicación fija. virtual void g 0 . incluso un ambiente basado en pila necesita de algunas capacidades dinámicas con el fin de manejar la asignación y desasignadn de apuntadores.8 CAP.4.3 76 Ejemplo 7.1 analizamos la necesidad de un ambiente de ejecución que fuera más dinámico que el ambiente basado en pila utilizado en la mayoría de los lenguajes compiladores.3 Administración del apilamiento o monticulo (heap) En la sección 7. class B: public A { public: double z.4. k.

de manera que la información de administración pueda niantenerce fkilmente en los misnios bloques de memoria. al tiempo que interfiere lo menos posible con la pila. Sin esta fusión. El código se proporciona en la figura 7. y definimos el arreglo del apilamiento para que tenga elementos de tipo Header. por lo regular en bytes. Un método estándar para mantener el apilamiento e implementar estas funciones es emplear una lista ligada circular de bloques libres.) Estas dos operaciones existen bajo diferentes nombres en muchos lenguajes: se denominan new y d i a p o s e en Pascal. y new y delete en C + +. ya sea de manera implícita o mediante un parámetro explícito. (En la página 347 mostramos el apilamiento situado en un bloque de memoria eti el extremo opuesto del irea de la pila. de manera que resulte un bloque libre de tamaño máximo. Una segunda. es decir. desventaja es que debe tenerse cuidado de fusionar bloques que son devueltos a la lista libre con bloques que se encuentran adyacentes. pero también se podría emplear una llamada del sistema operativo para asignar el apiI d e n l o . La operación de liberación toma un apuntador a un bloque de memoria asignado y lo marca como libre de nuevo. pero las versiones básicas son conocidas como malloc y f ree y son parte de la biblioteca estándar ( s t d l i b . hloqtie en la I~rta de la forma c\ . de modo que la asignación de un bloque grande puede fallar. entonces el apilamiento se corrompería rápida y fácilmente. Este código utiliza un arreglo asignado estáticamente de tamaño MEMSIZE como apilamiento.Memoria dinamica 377 manera que pueda aumentar. Definimos un tipo de datos Header ("Encabezado") que mantendrá la itifomüción de administración de cada bloque de memoria. el apilamiento rápidamente se fragmenta. Un a ilamiento o montículo proporciona dos operaciones. y cómo se pueden extender las operaciones de apilamiento para proporcionar la clase de asignación dinámica requerida en lenguajes con capacidades de función generales. y devuelve un apuntador a un bloque de memoria del tamaño correcto. (La operación de liberación también debe ser capaz de descubrir el tamaño del bloque que será liberado. aun cuando haya suficiente espacio total disponible para asignarlo. void free (void * ap) i Utilizaremos estas declaraciones como la base para nuestra descripción de la administración del apilamiento.19 (página 379). donde tienen esencialmente las declaraciones siguientes: & void * malloc (unaigned nbytes). de los cuales la memoria se toma mediante malloc y se devuelve mediante free. Si el usuario llegara a pasar un apuntador inválido. El lenguaje C tiene vanas versiones de estas operaciones. se divide en un gran número de bloques pequeños. asignación (ullocare) y liberación ee). (La fragmentación por supuesto puede oc&r incluso sin que se produzca este fenómeno de fusión. pero también tiene desventajas. pero mucho menos grave. La operación de asignación toma un parámetro de tamaño (ya sea explícita o implícitamente). h). En esta sección queremos describir cómo se puede administrar el apilamiento. Esto tiene la ventaja de la simplicidad. El tipo Header contiene trei \egmentos de información: un apuntador al siguiente bloque en la l i m el tamaño del eípacio 'tsignado actualmente (que viene enseguida cn memoria) y el tmafio de cualquier eípxio t i hre que le siga (zi es que hay alguno).) Aquí ofrecemos una implementación hasta cierto punto diferente d e m a l l o c y f ree que utiliza una estructura de datos de lista ligada circular. o un apuntador nulo si no existe ninguno. Una de éstas es que la operación free no puede decir si su argumento de apuntador esta en realidad apuntando a un bloque legítimo que fue previamente asignado mediante malloc. si es necesario.) Hasta ahora en este capítulo nos hemos concentrado en la organi~ación los registros de de activación y de la pila de ejecución. Por con\ig~~icnte. la cual se mantiene al tanto de bloques asignados y liberados (por lo que es menos susceptible a la corrupción) y tiene también la ventaja de proporcionar bloques autofusionables.

Este apuntador se denomi m e m p t r y siempre apunta a un bloque que tiene algún espacio libre (por lo regular e1 tno espacio por asignarse o liberarse). y el resto del código de r n a l l o c busca la lista y devuelve un nuevo bloque a partir del primer bloque que tenga suficiente eipacio libre (esto es un algoritmo de acceso primero). Esta complicación puede ignorarse sin consecuencias en resto de esta descripción. Esto con el fi "alinear" los elementos de memoria en un límite de byte razonable y. 7 1 AMBIENTES DE E~EcUCI~N /h + freesize 1 espaciousado 1 La definicibn del tipo Header en la figura 7. De este modo.tAP. El único segmento adicional de datos necesario para las operaciones del apilamiento un apuiitador a uno de los bloques en la lista ligada circular.19 también utiliza una declaración u n i o un tipo de datos A l i g n (que hemos establecido a d o u b l e en el código). dependiendo del tema. puede o no ser necesario. tlliora liay un bloque en la lista. pero en la primera llam malloc se ejecuta el ccítligo de inicialización que est~blece memptr a principio del arr a glo del apilarniento e inicializa el encabezado en el arreglo de la manera siguiente: memptr > next Este encabe~ado inicial que se asigna en la primera llamada a malloc nunca \era liberado. después de. Es inicializado a NULL. tres llamadas a m a l l o c . digamos. la lista tendría un aspecto como el siguiente: usado usado memptr + usado .

(p!=bp) && (g!=memptr). / * no hay blogue suficientemente grande * / newp = p+p->s.freesize += p->s. *p. p->s. 1 void free(void *ap) i Header *bp.next).next = p->s. *prev. prev->s.freesize = p->s.next = newp.Memoria dinámica Código en C para mantener #define NULL O #define ElEMSIZE 8096 / * cambio para tamaños diferentes * / typedef double Align. memptr = prev. if (p!=bp) return. typedef union header { struct ( union header *next. bp = (Header * ) ap 1. memptr = newpt return (void * ) (newp+l). newp->s.nunits.next. p->s. nunits = (nbytes+aizeof(Header)-l)/aizeof(Header) + 1.usedsize + p->s. static Header mem[MEMSIZEI . / * lista dañada. memptr->s.usedsize = 1.freesize = MEMSIZE-1.p=p->s. newp->s. if (p->s. (p->s.freesize.next = memptr = mem. unsigned freesize. 1 Header. no hacer nada * / prev->s.freesize = 0. g=g->a.next = p->s.freesize < nunits) return NULL.p=memgtr->s. if (memptr == NULL) i memptr->s. 1 S. prev=g.next!=memgtr) && (p->s.next). Align a. lista de apuntadores hacia blaques tanto usados como - 1 .freesize<nunits). *newp. memgtr->s. newp-zs. for ~prev=memptr. unsigned usedsize. unsigned nunits.usedsize = nunits.next. static Header *memptr = W L L I void *malloc(unsigned nbytes) i Header *p.usedsize. 1 for(p=memptr.freesize .next.

con lo que protege a la lista de la cormpción. Cuando se encuentra.I3 En este método no se libera memoria hasta que falla una llamada a m a l l o c . Una alternativa más simple Ilamada eonteo de referencia se emplea tamhih ocasionalniente. Observe que m también se establece rr auuntar al bloque que contiene la memoria recién liberada. debido a que los registros de activación deben mantenerse hasta que hayan desaparecido todas las referencias a ellos. . la admini\tración de memoria automática involucra el reclamo del almacenamiento previamente asignado pero ya no utilizado. la pila de ejeeución es administrada automáticnmente mediante la secuencia de llamada. 7 1 AMBIENTES DE EJECUCI~N ! 1 L I Advierta que a medida que los bloques se asignan en sucesión. de manera que el encabe protegido de ser sobreescrito mediante el programa cliente (mientras que en la me vuelta s61o se utilizan desplazamientos positivos).4. Entonces el apilamiento y la lista de bloque asociad él tendrían el aspecto siguiente: menptr -F R \ usado I usado I 7. Por desgracia. . memptr sigue la construcción de los nuevos bloques. se crea un nuevo bl cada vez. y t permite que se calcule el apuntador a i bloque anterior. En contraste. En un lenguaje que necesite un ambiente de ejecución completamente dintímico. Primero decrementa e tador pasado por el usuario para encontrar el encabezado del bloque. 'Tambien. De este modo. Vhse la seccih de notas y referencias. Luego busca en un apuntador que sea idéntico a éste. Reconocer cuando un bloque de almacenamiento ya no está referenciado. ya sea directa o indirectamente a través de apuntadores. fusionando así de manera automática el espacio libre. La técnica estándar consiste en realizar recolección de basura de marcado y barrido. Como ejemplo supongamos que se libera el bloque de en medio de los tres bloqu lizados en la ilustración anterior. Ahora consideremos el código para el procedimiento f r e e . y tanto su espacio utilizado como el libre se agregan al espacio libre e bloque anterior. aa . puesto que el programador debe escribir llamadas explícitas para asignar y liberar memoria. y sin una llamada explícita a free.e s t do a cero). Este proceso se denomina recolección de basura. es una tarea mucho más difícil que la de mantener una lista de bloques de alniacenamiento en forma apilada. el apilamiento debe ser administrado automáticamente de la misma manera.4 Administración automática del apilamiento o montículo (heap) El uso de malloc y f ree para realizar asignación y desasignación dinámica de apuntador es un método manuai para la administración del apilamiento.CAP. tal vez tiempo después de que fue asignado. el bloque es minado de la lista. en cuyo punto se activa un recolector de basura que 13. y así a siempre a un bloque con algo de espacio libre. las llamadas a f ree no se pueden programar de manera similar al salir de escena. mientras que las llamadas a m a l l o c se pueden programar fácilmente en cada llamada de procedimiento. Advierta también que m a l l o c sie crementa el apuntador al bloque recientemente creado. y el espacio libre que deja el bloque anterior se va con él (de manera que el es libre del bloque desde el cual tiene lugar la asignación siempre tiene f r e e a i z e .

todos los bloques alcanzados son inmediatamente copiados a la segunda mitad del almacenamiento que no está en uso. Este método se denomina recolección de basura de paro y copia o de espacio doble. mientras que el almacenamiento que permanece asignado durante algún tiempo tiende a permanecer de cualquier manera. lo que puede tomar algunos minutos. El segundo paso hace entonces un barrido lineal a través de la memoria. Por lo tanto. La administración de este proceso se puede mejorar dividiendo la memoria disponible en dos mitades y asignando almacenamiento s61o a partir de una mitad a la veL. Naturalmente. es posible que la memoka esté todavía tan fragmentada que aún no pueda satisfacer un requerimiento grande de memoria. durante el paso de marcado. Esto ~ignifica no se requerirá un que bit de marcado extra en el almacenamiento. antes de saltar al código del pmedimiento Ilamüdo. porque el almacenamiento temporal tiende a desaparecer con rapidez. Una vez que todos los bloques alcanzables en el área utilizada han sido copiados. * 7. especialmente con un sistema de memoria virtual. Recientemente. agrega un área de almacenamiento permanente al esquema de petición del párrafo anterior. Esto significa que el recolector de basura necesita buscar sólo una sección muy pequeña de memoria para las asignaciones de almacenamiento más recientes. y marca cada bloque de almacenamiento alcanzado. Esto evidentemente es . los parámetros corresponden a ubicaciones en el registro de activación. un parámetro representa un valor puramente formal al que no está ligado ningún código. mediante el elemento que llama. con lo que el tiempo para una búsqueda así se reduce a una fracci6n de segundo. todavía es posible que la memoria permanente llegue a agotarse con el almacenamiento no alcanzable. las cuales son rellenadas con los argumentos. comenzando con todos los valores de apuntador actualmente accesibles. pero que sirve sólo püra establecer . Desgraciadamente.5 MECANISMOS DE PASO DE PAR~METROS Hemos visto cómo. y el doble paso a través de la memoria provoca un retardo importante en el procesamiento. Este proceso requiere un bit de almacenamiento extra para el marcado. pero esto es un problema mucha menos grave que antes. Entonces. la mitad utilizada y la no usada de la memoria son intercambiadas y el procesamiento continúa. o valores de parámetros. De este modo. Los objetos asignados que sobreviven tiempo suficiente simplemente son copiados en espacio permanente y nunca son desasignados durante las peticiones posteriores de almacenamiento. dejando sólo un bloque grande de espacio libre contiguo en el otro extremo. incluso después de que se haya realizado la recolección de basura. La recolección de basura de marcado y barrido tiene varias desventajas: requiere de almacenamiento extra (para las marcas). cada vez que se invoca el recolector de basura. El primer paso sigue a todos los apuntadores de manera recursiva. El procedimiento se realiza en dos pasos. y sólo se requerir6 de un paso. devolviendo los bioctues no marcados a la memoria libre. en una llamada a procedimiento. Remitimos al lector a las fuentes enumeradas en la sección de notas y referencias para m6s detalles acerca de éste y otros métodos de recolección de basura. para el código del procedimiento llamado. en ocasiones tanto como algunos segundos. una recolección de basura por lo regular también realiza compresión de memoria moviendo todo el espacio asignado a un extremo del apilamiento. no hace mucho por mejorar los retardos del procesamiento durante la petición de almacenamiento. fue inventado un método que reduce este retardo en forma significativa. Este proceso también debe actualizar todas las referencias a aquellas áreas en la memoria que fueron desplazadas dentro del programa en ejecución. Aunque este proceso por lo general encontrará suficiente memoria libre contigua para satisfacer una serie de nuevos requerimientos. Se ha demostrado que este proceso funciona muy bien. inaceptable para muchas aplicaciones que involucran una respuesta inmediata o interactiva. Denominado recolección de basura generacional.Mecanirmor de paro de parametros 38 1 busca todo el almacenamiento que puede ser referenciado y Libera todo el almacenamiento no referenciado. También realiza la compresión de manera automática.

5. de modo que diferentes órdenes de evaluación tienen diferentes resultados. llamadade función en c provoca un cambio en el valor de x. y es el mecanismo predeterminado en Pascal y Ada (Ada también permite que tales parámetros sean explícitamente especificados como parámetros in). que ofrece sólo paso por valor. pero los cambios en ellas nunca provocan que tenga lugar algún cambio no local. Como ya indicamos. En ese caso. Esto ttene en cuenta un número variable de argumentos (tal como en la función grintf).CAP. por eficiencia u otras razones. mientras que C contempla todos los argumentos co lores.1. en cuyo caso a el resultado de una llamada puede variar de una implementación a otra. un orden de evaluación estrúidar puede especificarse como el de i~yuierda derecha. Otros lenguajes. En ocasiones se refiere proceso de construir estos valores como a la fijación de los parámetros a los argum tos. es iniposible escrihir directamente un procedimiento que tenga efecto haciendo cambios a sus paráinetros. como se conientó en la sección 7. muchos lenguujes permiten argumentos para llamadas que causan efectos laterales (cambios en la memoria). Éste es el único mecanismo de paso de parámetros disponible eii C. En su forma mrís simple.1 Paso por valor En este mecanismo los argumentos son expresiones que son evaluadas en el momento de la llamada. esto significa que los parámetros de valor se comportan como valores constantes durante la ejecución de un procedimiento. la . 7. Iü siguiente función inc2 escrita co C no logra el efecto deseado: . Una visión rnás relajada es la de C y Pascal. donde los parknetros de va 1or son contemplados esencialmente como variables locales inicializadas. como C + +. Esta forma de paso por valor es empleada por Ada. FORTRAN77 adopta un mecanismo que fija los parame ubic:tciones en vez de a valores. página 361. donde tales parámetros no pueden ser asignados o utilizados de otro modo.. Por ejemplo.0 de parámetros en particular adoptado por el le fuente. un compilador puede elegir variar el orden de la evaluación de los argumentos. el paso valor-resultado y el paso par nombre (también denominado evaluación retarda Algunas variaciones de éstos serán analizadas en los ejercicios. referidos a veces como llrimuda por v y Ilarnnila por rejirenciu). En la mayoría de las situaciones este orden no es impo te para la ejecución de un programa. y cualquier orden de evaluación producirá los mis resultados. además de dos métodos adicionales importantes. Sin embargo. Cómo interpreta el código del procedimiento los valores del argumento depen mecanismo o mecanismos de pa. donde el código puede encontrar su valor el cual sólo existirá una vez que ha tenido lugar una llamada. En tales lenguajes. En esta sección comentaremos los dos mecanismos de paso de parámetros más co nes (el paso por valor y el paso por referencia. que se pueden utilizar como variables ordinarias. y sus valores se convierten en los valores de los parámetros durante ta 'ejecución del procedimiento. Una cuestión que el mecanismo de paso de parámetros en sí no aborda es el orden que se evalúan los argumentos. y se puede interpretar el paso por valor como el reemplazo de todos los parjmetros en el cuerpo de un procedimiento mediante los valores de los argumentos. Los comp~ladores C de por lo regular evalúan sus argumentos de derecha a izquierda. 7 / AMBIENTES DE EJECUCION una ubicación en el registro de activación. o puede dejarie al escritor del compilador. En un lenguaje como C.3. Por ejemplo. Pascal y Ada. ofrecen una selección de mecanisnio paso de pwámetros. en vez de izquierda a derecha. por ejemplo como variables locales.

for(i=O. En Pascal. El paso por rci'crcncia requiere que el cotripilador calcule la direcciún del argumento (y debe tener tal dirección). ahora.2 Paso por referencia En el paso por referencia los argumentos deben (al menos en principio) ser variables con ubicaciones asignadas.tcena entonces en el registro de activ:tci<jn local.++x.il se üIm. 3 Mientras que en teoría es posible. esto toma la forma de pasar la dirección en lugar del valor (con lo que se cambia el tipo de datos del parámetro): void inc2( int* x) / * ahora está bien ( ++(*X).int size) 1* esto funciona bien cuando se llama como init(a). En FORTRAN77. ya que éstos son apuntadores de manera implícita. 3 */ Por supuesto. donde a es un arreglo * / f int i. Se implementa fiícilmente tomando la vi~ibn directa del cálculo del argumento y la constnicción del más registro de activación. el paso por referencia se consigue utilirando la palabra reservada var y en C + + el h h o l o eyecial 6 en la declaración de partímetro: void inc2f int & x) / * garámetro de referencia de C++ */ ( ++x. de manera que el parámetro Fe convierte en un alias para el argumento. En C. 3 El paso por valor no requiere de esfuerzo especial por parte del compilador.Mecanismos de paro de parámetros void inc2( int x ) / * iincorrectol * / i ++x. En vez de pasar el valor de una variable. esta funcibn debe ser llamada como inc2 (&y).++(*X). los lenguajes como C por lo regular ofrecen un método en el que se eniplea el paso por valor de manera tal que obtenga cambios no locales. la cu. realizar todos los cálculos devolviendo valores apropiados en vez de cambiando valores de parámetros. y cualquier cambio que se haga al partímetro también se hace al argumento.++x. el paso por referencia pasa la ubicación de la variable. y de este modo el paso por valor permite que los elementos individuales del arreglo sean cambiados: void init (int xll .ya que se requiere la dirección de y y no su vaior. . Este método funciona especialmente bien en C para arreglos. con una adecuada generalidad de funciones.i<size. 3 Esta función ahora puede scr llamada sin un uso especial del operador de direcciún: inc2 (y) funciona bien.++i) x[il=O. pura incrementar una variable y. 75. el paso por referencia es el único mecanismo de paso de parámetros.

ya que el "valor" local es realmente la dirección en cualquier sitio ambiente. puede ser cambiada. y posteriormente pasar la dirección a la llam Por lo general esto se realiza creando una localidad temporal en el registro de activ del elemento que llama (en FORTRAN77. éste podría ser llamado paso por resultado. (Ada también tiene simplemente un parámetm out.53 Paso por valor-resultado Este mecanismo consigue un resultado similar al paso por referencia. Una opción así es proporcionada por C+ +. Por ejemplo. Esto en ocasiones puede ser importante cuan valor a ser copiado es una estructura grande (o un arreglo en otro lenguaje aparte d C+ +). Éste es el mecanismo del parámetr out en Ada. Un aspecto del paso por referencia es que no requiere que se haga una copia del pasado. sólo que no se esta blece un alias real: el valor del argumento es copiado y utilizado en el procedimiento.El compilador también debe convertir los accesos locales a un piuimetro de referen accesos indirectos.) El paso por valor-resultado sólo es distinguible del paso por referencia en presencia de alias. En Iug hacer una llamada como ilegal en FORTRAN77.rcer\e de manera cornpletmente wgura . éste será estático).jes como FORTRAN77. Así. en el código siguiente (en sintaxis de C). de otro modo. dond puede escribir una llamada tal como void f ( conat MuchData & x ) donde EluchData es un tipo de datos con una estructura grande. un compilador debe "inventar" una dirección para la expre 2 + 3. copia de salida o copia de recuperación. ++y. donde el valor 3 se pasa como un argumento c do una localidad de memoria para el mismo en el registro de activación del procedi principal. entonces el valor final del parámetro es copiado de vuelta a la ubicación del argumento cuando el procedimiento sale de escena. E m no \lernpre puede h. En 1engua. calcular el valor en esta dirección. de esta manera se consigue el pa valor sin el gasto de copiar el valor. p regular se ofrece un acuerdo para argumentos que sean valores sin direcciones.1 (pigina 3 0 . pero el compilador también debe realiar una verificación estática de que x nunca va a aparecer a la izquierda de una asignación o. 1 14. a diferencia del paso por valor. este método se denomina a veces como de copia de entrada. Esto todavía es paso por referencia. per hibir que se hagan cambios al valor del argumento. int y ) ( ++x.14 1. donde sólo se dispone del paso por referencia. void p(int x . Un ejemplo de esto se 5) cuentra en el ejemplo 7. que no tiene valor i pasado. En tal caso puede ser importante poder pasar un argumento por referencia.

son el orden en el que los resultados con copiados de vuelta a los argumentos y si las ubicaciones de los argumentos se calculan sólo a la entrada y se almacenan o si vuelven a calcularse a la salida. int. 1 si se hace una llamada tal como p (a[i 1 ) . Por ejemplo. ya \ea del que se obtuviera mediante el paso por referencia o del que se obtuviera por medio del paso por valor-resultado. int i. ++Xt 1 . el elemento que llama debe insertar las direcciones de los argumentos como temporales en la pila antes de comenzar la construcción del nuevo registro de activación. debe volver a calcular estas direcciones al regreso del procedimiento llamado. si i fuera a cambiar antes del uso de x dentro de g. 7. Ada tiene otra peculiaridad: su definición establece que los parámetros i n out pueden de hecho ser implementados como paso por referencia. También se le conoce como de evaluiición diferida. y que posiblemente difieran en lenguajes o implementaciones diferentes. a[101. De esta forma.54 Paso por nombre Éste es el más complejo de los mecanismos de paso de parámetros. el paso por valor-resultado requiere varias modificaciones a la estructura básica de la pila de ejecución y a la secuencia de Ilamada. puesto que los valores (locales) que s e r h copiados deben continuar disponibles para el elemento que Ilma. en el código a void p(int X) f ++x. el registro de activación no puede ser liberado por el elemento llamado. o bien. piara): return O. a tiene el baior 3 después de que p es llamado si se utiliza el paso por rei'erencra. o su representación textual en el punto de llamada. int a = 1.el resultado sería diferente. void p(int X) ( ++i. el efecto es el de evaluar ++ (a[ i ] ) . En segundo lugar. el nombre del argumento. puesto que la idea del paso por nombre es que el argumento no sea evaluado hasta su uso real (como un parámetro) en el programa llamado. reemplaL al nombre del parhmetro que le corresponde. En primer lugar. en el código (con sintaxis de C).Mecanismos de paso de parámetros mainí ) f. Como ejemplo. mientras que a tiene el valor 2 si x emplea el paso por valor-resultado. De este modo. y cualquier cálculo que fuera diferente bajo los dos mecanismos (lo que involucraría un alias) sería un error. Desde el punto de vista del escritor de compiladores. Cuestiones que se dejan sin especificar por este mecanismo.

El texto de un argumen e1 punto de llamada es contemplado como una función por derecho propio. mientras que el procedimiento será ejecutado en su am de definición. . el amhicnte de qjecucicín se vcría c«nio sigue: . digamos. estas variables obtendrían las direcciones absolutas del O basta el 3 en la ptirte inferior de la ~iicinoria. variación de este mecanismo denominada evaluación diferida que se ha vuelto popul los lenguajes puramente funcianales. el argumento siempre será evaluado en el a te del elemento que llama. . TINY no tiene procedimientos. La tercera es que es inefi puesto que no sólo convierte una simple evaluación de argumento en una llamada de cedimiento. En realidad. cuatro variiibles x. consiste en evitar la reevaluación memorizan suspensión con el valor calculado la primera vez que se llama. describiremos la estructura de un ambiente de ejecu para el lenguaje TINY.6 UN AMBIENTE DE EJECUCION PARA EL LENGUAJE TINY En esta sección final del capítulo. La segun& es que es difi . La evaluación diferida d cho puede producir una implementación rnds eficiente. Así. a [ l l = 1. <ladoun programa que utilice. de manera que no hay necesidad de una pila o registros de activación. y. es procedimiento (en ocasiones conocido como suspensión o interrupcih [<'thunk"l) que. y el único almacenamiento d i n h i c o necesario es el de temporales durante la evaluación de la expresión (éste incluso podría ser estático como en FOR'TRAN77: véanse los ejercicios). pero perdió popularidad por tres razones. a[21 = 2. lacual e hada cada vez que el nombre de parámetro correspondiente se alcanza en el códi procedimiento llamado. implementar. Remitimos al lector a I sección de notas y referencias para más inforn~ación respecto.. IJn esquema simple para un ambiente TINY es colocar las vtiriables en direcciones absolutas en el extremo inferior de la memoria del programa. Los lenguajes que ofrecen la evaluación diferida mo un niecanismo de paso de parámetros son Miranda y Haskell. al 4 7. Haremos esto aquí en una forma independiente de máquina y remitiremo al siguiente capítulo para un ejemplo de una implementación en una máquina espec El ambiente que TINY necesita es mucho más simple que cualquiera de los a comentados en este capítulo. puesto que cada argumento debe ser convertido en lo~que esencialmente un. nuestro ejemplo ejecutable de un lenguaje pequeño y simple para c pilación. giaiil)t r e t u r n O. Sin embargo. y todas bles son globales.CAP. mera es que puede dar resultados sorprendentes y contrarios a la intuición en la pres de efectos colaterales (como lo muestra el ejemplo anterior). puesto que un argumento que n utiliza nunca tampoco se evalúa nunca.. y asignar la pila temporal en el cvtremo superior. sino que también puede provocar que ocurran evaluaciones múltiples.. el resultado de la llamada a p es que a 121 se establece a 3 y a [ 11 se deja sin c m La interpretación del paso por nombre es como sigue. 2 debe ser llamado siempre que el argumento es evaluado. z y w. 7 1 AMBIENTES DE EJECUCIÓN main ( ) i = l . en un punto (iurante la ejecuci611donde se estén almacenando los tres teniporay les. El paso por nombre se ofreció como ud mecanismo de paso del parámetro (junt paso por valor) en el lenguaje AlgolóO.

asignar direcciones a las variables cuando se hayan encontrado la primera vez. int loc int st-lookup ( char * name ). en cualquier momento que se encuentre una variable (en una sentencia de lectura. línea 1413): static int location = 0. y ci contador de uhic:ici(in se incrcrnenta. línea 1454): C'uarido st-lookug dcvuelvc . Naturalmente. s i q i s t r n una nucv:i iihic:icií. ). El analizador semántica debe. De otro riiodo. En cse caso.Un ambiente de ejecucion para el lenguaje TINY parte superior o tope de memor. o bien. como se describió en el último capítulo.parte superior o tope parte inferior de la memoria Dependiendo de la arquitectura. también se podría utilizar la pila del procesador como la pila temporal. int lineno. lineas 1166 y 1 171): void st-insert( char * name. a su vez. y emplear el apuntador de la parte superior de la memoria como el "tope (parte superior) de la pila oral". una sentencia de asignación o una expresión de itlentiticador). si está disponible. calcular los desplazamientos para los temporales desde un apuntador fijo en la parte superior.1. 13 . Esto se hace manteniendo un contador de ubicación de memoria estático que es inicializado para la primera dirección (apéndice B. el analizador sernántico ejecuta el cítdigo (apkndice B. mantener las direcciones de las variables en memoria. 13 variable ntín no está cri 13 tabla. para entonces utilizar las direcciones "absolutas" de las variables como desplazamientos desde d apuntador de la parte inferior. Entonces. Para implernentar este ambiente de ejecución 1~tabla de símbolos en el compilador de TlNY debe. Se hace esto proporcionando un parámetro de ubicación en la función st-insert e incluyendo una función st-lookug que recupere la ubicación de una variable (apéndice B. podemos necesitar establecer algunos registros de administraci6n para apuntar a la parte inferior ylo superior de la memoria.n.ia 1 tew3 1 c.

Después de la entrada en el bloque A en la función f..SIZE) *.2 Dibuje una posible organización para el ambiente de ejecucicin del siguiente programa en C.I 10 READ READ *. ambiente de ejecución del siguiente progrma e FORTRAN77.AVE(A.N) INTEGER 1. int a1101. A:{ int i-j.1 Dibuje una posible organización pan e. 7 1 AMBIENTES DE EJECUCI~N variable ya esta en la tabla.N 20 SUM=SUM+B ( 1) AVE = SUM/N END 7. 6. la así c i h de las variables temporales en la parte superior de la memoria. e1 se analizará en el siguiente capítulo.O. Después de la entrada en el bloque B en la función g.CAP.0 DO 20 I=1.N) GOTO 10 99 CONTINUE END REAL FUNCTION AVE(B.2 (página 352). char * s = "hallo". en cuyo caso la tabla de símbolos ignora el parámetro de u cibn (y se escribe O como una ubicación ficticia). similar a la de la figura 7. REAL A(S1ZE) .I=l. 1 .GT.N) PRINT ". Asegúrese de incluir los apunta de memoria como se presentarían durante la llamada a AVE. (A(I).LE. 'AVE = '. int b [ l ) C int j = i . 1 return O. serán responsabilidad del generador de código.4 (página 354). DERCfCfOS 7. char c = b[il. int f(int i.N REAL B(N).OR. Esto maneja la asignación de las variables nombradas en un programa TINY..AVE INTEGER N. y las operaciones ne sarias para mantener esta asignación.SUM SUM = 0. a. N GOTO 99 IF (N.N. similar a la de la tigura 7. .

g(a). Dibuje la pila de registros de activación para el siguiente prograrna en Pascal a. program env. begin ( * main * ) a. Despuks de l a 1l. b. begin (* b * ) c. grocedure a. var x: integer. . grocedure b.irn. Dibuje la pila de registros de activación para el siguiente programa en Pascal. después de la segunda llamada al procedimiento c. begin ( * a * ) b. main ( ) ( int x=l. begin x := 2.5.Ejercicios void g(char * e) ( c h a r c = e[Ol. end. end.a). x = f(x. end. 7. end. 1 Dibuje una posible organizaciún para el ambiente de ejecución del programa en C de la figura 4.1 (página 138) después de la segunda llamada a f a c t o r . return O. que muestre los vínculos de control y de acceso.ida a a en la primera llamada de P. Describa cómo se tiene acceso a la vaiable x desde el interior de c. B:í i n t at51. dada la cadena de entrada ( 2 ) . grocedure c.

begin writeln(x). grocedure two. c. 2 1.output). begin writeln(x). incluya todos los vínculos de control y de acceso. p(one). Después de la llamada a ' a en la segunda llama& de p. 2. procedtire newprint.Quéimprime el programa y por qué? proqram closureEx(output~. begin a. ( * q *) beqin ( * main * ) x := '. P(tw0). además de todos los pntámetros y variables glob~les. suponga que todos los procedimientos son :tlmacenados en el y ambiente como ccmduras. beqin print . var x: integer. 7.CAP. writeln(x). beqin x := a. ¿. procedure g(procedure a). end. var x : integer. end. O y dibuje la pila de' registrosde aetivaciún cuando el número 1 es impreso la primera vez. procedure dolist (grocedure print). 7 1 AflBIENTES DE EJECUCIÓN b. end. end. var x: integer. endi grocedure q.6 Considere el siguiente programa en Pascal. . und . Suponga una entrada de usuario compuesta de los tres números 1. proqram procenv(input. procedure one.

7. página 362) y esrablece que una solución semejante funciona para variables locales de longitud variable. n:Integer) is y: Array(1..3. begin .9 El texto describe cómo manejar los parámetros de longitud variable (tales como arreglos abiertos) que son pasados por valor (véase el ejemplo 7. 7.. end f . Describa el problema y una solución utilizando el siguiente procedimiento de Ada como ejemplo: 7. Otra alternativa para calcular los argumentos en reversa es emplear un tercer apuntador (aparte del sp y el fp). Conciba un método para estimar el número de temporales requeridos para calcular una expresión realizando un recorrido del árbol de expresihn.8 En lenguajes que permiten números variables de argumentos en 1l:madas de procedimiento. Suponga que las expresiones son evaluadas de izquierda a derecha y que cada subexpresicín i~quierda se debe gnardar en un teinpral. página 361. Describa tal organización de registros de activación y la secuencia de llamada que necesitaría. .n) o£ Integer.1. Sin embargo. el cual por lo regular se denomina ap ("argument pointer". Una alternativa para calcular los argumentos en reversa sería reorganizar el registro de activación para tener disponible el primer argumento incluso en la presencia de argumentos variables. procedure f(x: IntAr. a.. una manera de encontrar el primer argumento es calcular los argumentos en orden inverso. como se describió en la sección 7. . if x = O then begin print. existe un problema cuando se tienen presentes tanto parámetros de longitud variable como variables locales. Describa una estructura registro de activación que utilice un ap para hallar el primer argumento y la secuencia de llamada que necesitaría. b.6.. apuntador de argumento). i: Integer. .Ejercicios begin ( * dolist * ) readln(x). print. (* main * ) dolist(nul1). begin end. ( * doliat * ) grocedure null. begin end. un compilador FOKTRAN 77 necesita constituir una estimación del número mtiximo de temporales requeridos para cualquier cilculo de expresión en un programa. end else dolist(newprint). Para efectuar asignación completamente estática. end.7 t m e IntAr is ArrayfInteger range c > ) of Integer.

b Vuelva a hacer el ejercicio 7.CAP.12 (pitgina 369) se vería como sigue con un despliegue display [ l ] Registro de activación display [al de la llamada a q Registro de activación de la llamada a r mzentras que la pila de ejecucidn de la figura 7. la pila de ejecuc la figura 7. Describa la secuencia de llamada necesaria para implementar un despliegue. Este arreglo se conoce como despliegue ("display"). . . a.4 utilizando un despliegue. Existe un problema al utilizar un despliegue en un lenguaje con paránietros de procedimiento. I D Una alternativa al encadenamiento de accesv en un lenguaje con procedimientos locales mantener vínculos de acceso en un arreglo exterior de la pila. c.5. indicado por el nivel de a ción. 7 1 AMBIENTES DE EJECUCI~N 7.13 (página 370) tendría el aspecto que sigue: Registro de activación del programa principal Registro de activación display [l] display [a] Describa cúmo un despliegue puede mejor= la eficiencia de referencias no locales de procedimientos profundamente anidados. d. Describa el problenia utilizando el ejercicio 7. Por ejemplo.

y suponiendo que los tamaños de datos son prua enteros = 2 bytes. char = 1 byte. x) x = 3. 1 void main( ) int *p. virtual void g 0 . *p = 1.t+. printf ("%d\n". int yf51.12 Ejecute el siguiente programa en C y explique su salida en términos del ambiente de ejecución: a. c. b. return kx.2: class A ( gublic: int a. 3) y 12 1 . 7. printf("%i\nW. .4.1 1 Considere el siguiente procedimiento en sintaxis de C: void f( char c. junto con las tablas de función virtual como se describieron en la sección 7.hetros son pasados por referencia. > 7. void gívoid) í (int x. 1: .x) .) (int Y.13 Dibuje el diseño de la memoria de los objetos de las siguientes clases de C . virtual void f0. Repim el inciso a suponiendo que todos los pruámetros son pasados por valor (incluyendo aneglosf. char sL101. double r ( int * x. f0. determine los desplazümientos desde el fp de los siguientes. ) Con las convenciones de paso de parimetros estándaes de C. douhle = 8 bytes. Repita el inciso a suponiendo que todos los par.Ejercicios 7. printf ( "%d\nn. p = f0. dirección = 4 bytes.) 1 int* f(void) i int x. utilizando la estructura de re-' gistras de activacih descrita en este capítulo: 1) c. 2) 8 [71.y). 90.

i n t t x = x t y . 7. y) i y ) t= 1.14 Una tabla de función virtud en un lenguaje orientado a objetos ahorra recorrer la gráfica de herencia en la blísqueda de un mktodo.a[O1. v i r t u a l void g 0 .y . pero tiene un costo. 1 y) . a i i l ). void swap(int x . 1 7. i n t ( x t = 1. +' rnain ( ) ( i n t a[21={1. p r i n t f ( " % d %d\nn. c l a s a C : public B ( public: i n t c. v i r t u a l void f 0 . r e t u r n O.a[ll). 7 1 AMBIENTES DE E J ~ C U C I ~ H A claaa B : public ( public: i n t b. h > i n t i=0.1). 1. void h 0 . p(aCi1 . h > i n t i=0.5: #include < s t d i o .y .CAP. x = x .15 I'mporcitine la salid^ del siguiente programa (escrito con sintaxis de C ) utilizando los cuatro inEtodos de paso de p. void p ( i n t x.whetros comentados cii la sección 7. y = x . f 7. 1. Explique cuál es este costo.16 Propnrcione la wlida del siguiente programa (con wtaxis de C ) ut~li~ando cuatro métodos lo^ de paso de parámetros comentados en L ~ección a 7.5: #include < s t d i o .

6.4) puede hacerse en un paso separado de la recolección de basura 7. Explique cómo ambos comportamientos pueden ocurrir en términos del ambiente de ejecuci6n. a. La cornpresidn (secci6117. pero cada argumento es evaluado en el 'unbiente del procedimiento llamado en vez de hacerlo en el ambiente que llama. puede imprimir el valor 2.17 Suponga que la subrutina P de FORTRAN77 estj declarada corno sigue SUBROUTINE P ( A ) INTEQER A PRINT *. cia del paso por nombre al memorbar el valor de un argumento la primera cez que er evaluado. Vuelva a eicribir su código del ejercicio anterior para implementar dicha mnetnorización.5. y vetifique que el resultado es en realidad equivalente al paso por nombre. A + 1 RETURN ENn y que es Ilamada desde el pmgrama principal de la manera siguiente: En algunos sistemas FORTRAN77. 7.19 Como le describió en la sección 7.16 para implementar los parámetros de la función swap de esta manera.3 para incluir un piso i& .ireticia de un bloque suticictitcmente largo. EJERCICIOS DE PROGRAMaCIdN 7. La memorización puede provocar diferentes resultados del paso por nombre.4. puede ser visto como el empacamiento de un argumento en un cuerpo de función (o suspensión). el paso por nombre. b. $e puede conseguir un mejoramiento en la eíicien. Describa una organi~aciónde ambiente de ejecución y secuencia de llamada que podrían utilizarse para implementar el paso por texto.18 Una variación del paso por nombre es el paso por texto. a. A A .Ejercicios de programación 7. y compare los resultados con los de ese ejercicio. Vuelva a escribir el código en C del ejercicio 7. Muestre que el paso por texto puede tener diferentes resultados que el paso por nombre. no ocurrirá un error de ejecución.5. Vuelva a cscrihir el procedimiento malloc ik la sección 7. justo como en el paso por nombre. esto ocasionará un error de ejecucih En otros.4. o evaluación retardada.21 y puede ser realilatl~ mediante malloc si una petici6n de memoria falla debido a la c. en el cual los argumentos son evaluados en modo retardado. 7.20 a Como se describió en la sección 7. Explique c6mo puede ocurrir esto. el cual es Ilamado cada vez que el parámetro aparece en el código.4. pero si la subrutina se llama de nuevo con un 1 como SU argumento.

.CAP. junto con extensiones para manipular herencia múltiple. Un panorama general de la colección de basura se presenta en Wilson 119921 o Co 119811. Budd [1987] describe un ambiente completamente dinámico para un pequeño sistema Smalltalk. NOTAS Y REFERENCIAS EI ambiente completamente estático de FORTRAN77 (y versiones más anti FORTRAN) representa un enfoque directo y natural para el diseño de ambientes y lar a los ambientes de ensamblador. incluyendo el uso de la grafica de herencia. y vuelva a su código del inciso a para incluirla. El uso de un despliegue en I cadenas de acceso (ejercicio 7. Más ejemplos de técnicas de paso de parámetros pueden hallarse en Louden [1993]. Las técnicas de implementación piira la evaluación diferida pueden estudiarse en Peyton Jones [1987]. El diseño de una estructura de apilami para su uso en la compilación se comenta en Fraser y Hanson 119951. y un recolector de basura con conteos de referencia. La compresión requiere que la ubicaci6n del espacio previamente asignado wmb' significa que un programa debe informarse acerca del cambio.4. El uso de una tabla de función virtual en C++ se describe en Ellis y Stroustrup [1990]. al código dado en la se 7. 1 1 AMBIENTES Df EJECUCI~N b. Describa cómo uti tabla de apuntadores a bloques de memoria para resolver ese problema. R Russell 119641 describen un temprano ambiente basado en pila de Algo160 con La organización del registro de activación y la secuencia de llamada para algun piladores de C se describe en Johnson y Ritchie 119811.10) se describe con detalle en Fischer y LeBlanc incluyendo los problemas al utilizarlo en un lenguaje con parámetros de procedimi La administración de memoria dinümiea se analiza en muchos libros acerca de turas de datos.3. donde también se puede encontrar una descripción de la evaluación diferida. Un colector de basura generacional y ambiente de tiempo de corrida para el ML del 1 guaje funcional se describe en Appel[1992]. El código para implementación de ma f ree que es semejante. aparece en Kernighan y Ritchie 119881. tal como Aho. Los ambientes basados en pila se hicieron popula la inclusi6n de la recursividad en lenguajes tales como Algo160 ((Naur [1963]). Una útil perspectiva ge ciente se ofrece en Drozdek y Simon [1995]. El compilador de lenguaje funcional Gofer (Jon 119841) contiene tanto el colector de basun de marcar y barrer como el de dos espacios. aunque ligeramente menos sofisticado. Hopcroft y Ullman 119831.

la velocidad y10 el tamaño del código objetivo recolectando más información acerca del programa fuente y adecuando el código generado para sacar ventaja de las características especiales de la máquina objetivo. o mejorar. modos de direccionamiento. la estructura del ambiente de ejecución y el sistema operativo que esté corriendo en la máquina objetivo. distribución y memoria caché. Un compilador también puede detener en breve la generación de código ejecutable real pero. en vez de esto genera alguna forma de código ensamblador que debe ser procesado adicionalmente por un ensamblador. tales como registros. la de generar código ejecutable para una máquina objetivo que sea una fiel representación de la semántica del código fuente. Debido a la complejidad de la generación del código. un ligador y un cargador.Generación de código Código intermedio y estructuras de datos para generación de código Técnicas básicas de generación de código Generación de código de referencias de estructuras de datos Generación de código de sentencias de control y expresiones lógicas Generación de código de llamadas de procedimientos y funciones Generación de código en compiladores comerciales: dos casos de estudio TM: una máquina objetivo simple Un generador de código para el lenguaje TlNY Una visión general de las técnicas de optimización de código Optimizaciones simples para el generador de código de TlNY En este capitulo volveremos a la tarea final de un compilador. La generación de código es la fase más compleja de un compilador. los cuales pueden ser proporcionados por el sistema operativo o compactados con el compilador. un compilador por lo regular divide esta fase en varios pasos. Ignoraremos el problema del procesamiento adicional del código ensamblador . los cuales tienen muchas características en común. En este capítulo nos concentraremos únicamente en los fundamentos de la generación del código intermedio y el código ensamblador. La generación de código por lo regular implica también algún intento por optimizar. y a menudo incluyen alguna forma de código abstracto denominada código intermedio. los cuales involucran varias estructuras de datos intermedias. puesto que no sólo depende de las características del lenguaje fuente sino también de contar con información detallada acerca de la arquitectura objetivo.

incluso para la generación de código (como veremos en una sección posterior). no se parece n i remotamente al código objetivo. E l código intermedio puede tomar muchas formas: existen casi tantos estilos de código intermedio como compiladores. E l &digo intermedio puede ser de muy alto nivel. En secciones posteriores se analizarán téc de generación de código para varias características del lenguaje. 4 CÓDIGO INTERMEDIO Y ESTRUCTURAS DE DATOS PARA GENERACIÓN DE CÓDIGO < ': Una estructura de datos que representa el programa fuente durante la traducción se denomina representación intermedia. pri analizaremos una arquitectura objetivo simple y un simulador de máquina denominado TM. En la primera sección de este capítulo analizaremos dos formas populares de có intermedio. Puede i)no incorporar toda la inforrnaciiín contenida en la tnbl. tales como sentencias if y s cias while y llamadas a procedimientolfunción. donde el código objetivo. y entonces genere código objetivo de esta nueva representación. daremos visión general de las técnicas para el mejoramiento u optimización del código estánd y describiremos cómo algunas de las técnicas recién adquiridas pueden incorporarse el generador de código de TINY. Una representación intermedia de esta naturaleza que se parece a l c6digo objetivo se denomina código intermedio. la principal estructura de datos utilizada durante la traducción es la tabla de símbolos. o parecerse niucho ti1 código objetivo. corno 10s tamaños de los tipos de datos. y comentaremos algunas de propiedades. para el cual se proporciona un listado fuente en el aphdice C. En la segunda sección describiremos los algoritmos básicos que permit generar código intermedio o ensamblador. En este texto hasta ahora hemos usado un árbol sintáctico abstracto como el IR principal. o IR (por las siglas del término en inglés) para abreviar. representar todas las operaciones de ruanera casi tan abstracta como un árbol sintjctico. A continuac' describiremos el generador de código complejo para TINY. Aunque un árbol sintktico abstracto es una representación adecuada del código fuente.CAP. Como generación de código en este nivel de detalle requiere una máquina objetivo real. la cual se estudió en el capítulo 6. las ubicaciones de Lis variables y la disponibilidad de los registros. es decir.i . A estas secciones les seguirán estu de caso del código producido para estas características mediante dos compiladores merciales: el compilador C de Borland para la arquitectura 80x86 y el compilador C Sun para la arquitectura RlSC de Sparc. un escritor de compiladores puede desear generar una nueva forma de representaciición intermedia del árbol sintiictico que se parezca más al código objetivo o reemplace del todo al árbol sintáctico mediante una representación intermedia de esa clase. el cual puede ser controlado más adecuadamente mediante un lenguaje ensamblador o sistemas de texto de programación. Finalmente. En una sección posterior aplicaremos las técnicas que permiten desarrollar un nerador de código ensamblador para el lenguaje TINY estudiadas hasta aquí. todos representan alguna forma de linealización del árbol sintáctico. incluyendo expresi sentencias de asignación y sentencias de flujo de control. en particular en su representación de constnicciones de flujo de control. como el código de máquina o código ensamblador. 8 1 GENERACI~N DE CÓDIGO en cbdigo ejecutable. Puede o no utilizar informiición detallada acerca de la máquina objetivo y el ambiente de cjccusicíri. el código de tres direcciones y el código P. Por lo tanto. una representación del árbol sintáctico en forma secueircial. Además del IR. como las sentencias if y while. emplean saltos mas que consírucciones de alto nivel. Sin embargo.

aunque no es iiiiposible hacerlo directamente desde el &bol sintáctico. entonces la generación de código objetivo puede basarse sólo en el código intermedio. el compilador debe retener la tabla de símbolos para la generación del código objetivo. Ambos se presentan en muchas formas diferentes. En particular. tül como los ámbitos. 8. las estructuras de datos adicionales que incorporan información de un detallado análisis posterior al análisis sintáctico se pueden generar fácilmente a partir del código intermedio. Sin embargo. El nombre "código de tres direcciones" viene de esta forma de instrucción.1 Codigo de tres direcciones La instrucción básica del código de tres direcciones está diseñada para representar la evaluación de expresiones aritméticas y tiene la siguiente forma general: Esta instrucción expreía la aplicación del operador op a los valores de y y z. y por lo regular esto es más fácil que vdver a escribir todo un generador de código. Aquí r. Para ver cómo las secuencias de código de tres direcciones de esta forma pueden representar el cálculo de una expresión. observe que el uso de la dirección de x difiere del uso de las direcciones de y y z. entonces generar código para una máquina objetivo diferente sólo requiere volver a escribir el traductor de código intermedio a código objetivo. y nuestro estudio aquí se enfocará sólo en las características generales.Código intermedio y estructuras de datos para generación de codigo 399 de sínibolos. Ei código intermedio también puede ser útil al hacer que un conipilador sea más fácilmente redirigible: si el cóúigo intermedio es hasta cierto punto independiente de la niáquina objetivo. los niveles de anidación y los desplazamientos de las variables.y la asignación de este valor para que sea el nuevo valor de x. y y z representan una dimcción de la memoria. Tales descripciones se pueden encontrar en la literatura que se describe en la sección de notas y referencias al final del capítulo. El código intermedio es particularmente útil cuando el objetivo del compilador es producir código muy eficiente. ya que por lo general cada uno de los nombres x.p puede Ter un operador aritméticó como f o .1. ya que para hacerlo así se requiere una cantidad importante del análisis de las propiedades del código objetivo. y esto se facilita mediante el uso del código intermedio. Si io hace. si no. En esta sección estudiaremos dos formas populares de código intermedio: el código de tres direcciones y el código P.o algún otro operador que pueda actuar 5ohre los valore^ de y y z.y que tanto y como z (pero no x) pueden representar constantes o valores de literales sin direcciones de ejecución. considere la expresión aritmética con árbol sintáctico . en lugar de presentar una descripción detallada de una versión de cada uno.

a saber (con un significado dife para los elementos temporales). ser6 necesado variar la forma del código de tres direcciones en cada constmcci Si un lenguaje contiene características poco habituales. a los que en este ejemplo hemos llamado tl. El código de tres direcciones de muestra para este programa se ofrece en la figura 8. a m o ocurre aquí. si e van a inezclar los uonrhres de ccidigo fuente.1. Es posible que un compilador desee un orden diferente en ciertas circunstancias.2. Ésta es una de las razt por las que no existe una forma estríndar para el código de tres direcciones (del mismo modo que no existe una forma estándar para árboles ~intácticos). los nonibres temporales en código de tres direcciories &ben ser (listintos de cualquier notnbre que pudiera ser utilizaJo en cl ccidigo fuente wal. En las siguientes secciones de este capítulo trataremos algunas construcciones comune de lenguaje de programación de manera individual y mostraremos cómo estas construc ciones son traducidas por lo común como código de tres direcciones.Estos elementos te rales corresponden a los nodos interiores del árbol sintáctico y representan sus v calculados. como la negacidn. t2. requieren de una variación ~ del citdigo de tres direcciones que contenga bolo dos direcciones. El código de tres direcciones que se acaba de dar representa una linealizaci6n de i da a derecha del árbol sinttIetico. l o operadores unitarios. Este código contiene 1.C P 8 1 GENERACI~N DE C ~ D I G O A. puede ser necesario incluso inve nuevas formas del código de tres direcciones para expresarlas. De hccho. Considere el programa de muestra TINY de la sección 1. tal como t2 5 - tl Si se desea tener capacidad para todas las construcciones de un lenguaje de programaci estándar. debido a que el código correspondiente a la evaluact subárbol izquierdo de la raíz se lista primero. la forma del código de ires direcciones que hemos mostrado no basta para representar todas las características ni siquiera del lenguaje de programación más pequeño. el cual repetimos en la Figura 8. Por ejemplo. con el último elemento temporal (t3. El código de tres direcciones correspondiente es t l = 2 * a t 2 = b .y así sucesivamente están scilo destinados U ser representativos del estilo neneral de tal código. t2 y t3.3 t 3 = tl + t 2 El código de tres direcciones requiere que el conipilador genere nombres para elem temporales. p ~costumbrantos lo que nos espera presentamos aquí un ejemplo completo en el que se utia liza el lenguaje TINY presentado anteriormente.7 (capítulo 1) que calcula el máximo común divisor de dos enteros. .' La manera en que estos elementos temporales finalmente son asignados en rnoria no es especificada por este código. Las nomhres tl. Evidentemente. Aquí simplemente advertimos que pue ber otro orden para este código de tres direcciones. por lo regular serán asignados a registr también se pueden conservar en registros de activación (véase el análisis de la pila te ral en el capítulo anterior). este ejemplo) representando el v en l a raíz. Sin embargo.

Código intermedio y estrutturas de datos para generati911 de código ( Programa de muestra en lenguaje TINY-calcula el factorial ) read x. write fact ( salida del factorial de x end ) read x t l = x > O if-falsa tl goto L1 fact = 1 label L2 t2 = fact * x fact = t2 t 3 = x .1 x t3 t4 = x == O if-false t4 goto L2 write fact label L1 halt varias formas diferentes de código de tres direcciones. observamos que las asignaciones en el código fuente producen la generación de instrucciones de copia de la forma Por ejemplo. En tercer lugar. las operaciones integradas de entrada y salida read y write se tradujeron directamente en instrucciones de una dirección. dependiendo de las estructuras de datos empleadas para implementar el código de tres direcciones. existe una instrucción de salto condicional if-falae que se utiliza para traducir tanto sentencias if como sentencias repeat y que contiene dos direcciones: el valor condicional que se probará y la dirección de código a la que se saltará. Finalmente. En primer lugar.Estas instrucciones label pueden ser innecesarias. la sentencia de programa de muestra fact := fact * x. x:*x-1 until x = O. . introducir un entero ) if O < x then ( no calcular si x <= O f fact := 1. una instrucción halt (sin direcciones) sirve para marcar el final del código. En segundo lugar. repeat fact := fact * x. Las posiciones de las direcciones de salto también se indican mediante instrucciones (de una dirección) label.

La implementación más común consiste en implementar el código de tres dire fundamentalmente como se muestra.2 se proporciona en la figura 8. Por cjeniplo. Una implementación diferente del código de tres direcciones consiste en utilizar las i trucciones mismas para representar los elementos temporales. la dirección objetivo es siempre un elemento temporal. Como son nec tro campos. En estas definiciones permitimos que una di d o una constante entera o una cadena (que representa el nombre de un elemento tempo o una variable). Esto evita tenecque reaiizar búsquedas e&particularmente ventajoso en un lenguaje con ámbitos anidados.2. la cual se puede conservar en la memoria o escrita para (y leída desde) arch porales cuando sea necesario. una representación de código de tres direcciones de esta naturaie~a den se iia cuádruple. éstos deben introducirse en un tabla de símbolos.2 (véase también la figura 8 5 . donde se necesit información de ámbito que aólo el nombre para realizar una búsqueda. También. Si también troducen constantes en la tabla de símbolos. donde T eicribieron los cuádrnples en nota e matemática de "tuplas". entonces no es necesaria una unión en de datos Address. Esto sucede por razones cas que se explicarán en la sección 8. uno o más de los campos de dirección proporcionan un v "vacío".3. Una posible implementación cuádruple del código de tres direcciones d figura 8.tntc la iinplementnciún. es veriladero en el c6digo de la figura 8. cada instrucción de tres direcciones .z Una implementación del código de tres direcciones de esta clase se denomina triple y requiere que cada instrucción de tres direcciones sea referenciable. Para las instrucciones que necesitan de tres direcciones.3 se ofrecen en la figura 8. . Los typedef posibles de C para implementar los cuádruples mostrados ra 8. 8 1 G E N E M C D~ CÓDIGO ~I N se traduce en las dos instrucciones del código de tres direcciones t 2 = fact fact = t2 * x aunque sería suficiente una instrucción de tres direcciones. Esto reduce la ne campos de dirección de tres a dos. y re necesitnrá realizar búsquedas durante procesamient Una alternativa para conservar los nombres en los cuádruples es mantener npunt cia entradas de la tabla de símbolos. lo que significa que son necesarios cuatro camp para la operación y tres para las direcciones. 8. la selección de los canipos depende de la implementación. Esto tia es una verdad inherente acerca del cúdigo de tres direcciones. está implenientada como una estructura de registro que contiene varibs campos. ya sea como 2. puesto que en una instrucción de tres direcciones que contiene la totalidad de las tres direcciones.4. puesto que se utilizan nombres. pero pttcde asegurarse nietli. En vez de eso.12 Estructuras de datos para la irnpiernentación del código de tres direcciones El código de tres direcciones por lo regular no está implementado en forma textual como lo hemos escrito (aunque podría estarlo).) .CAP. y la s cia completa de instnicciones de tres direcciones está implementada como un arreg ligada.

asn. . Sgura 8.(Sdigo intermedio y estructuras de datas pata generauan de código figura 8.lab. En esa figura utilizamos un sistema de numeraciGu que correspondería a índices de arreglo para representar los triples.3 AddrKind kind. union { int val. tygedef struct ( ) un índice en un arreglo o como un apuntador en una lista ligada. Los triples son una manera eficiente para representar el código de tres direcciones. Sin embargo. tygedef struct { . y el código C apropiado para la rlcfinici6n <le10s mismos. Quad. OpKind og.halt. Adicionalmente. > OgKind.4 COdigo C que define estructuras de datos posibles para los cuádruples de la t~edef enum (rd.5 eliminamos las instrucciones label y las reemplazamos con referencias a los índices de triple mibmos. una representación de lista ligada no sufre de esta deficiencia.addr2. en la figura 8. Problemas adicionales que involucrcin triples. ).. .5 (página 404).mul. entonces cualquier movimiento ríe sus posiciones se vuelve difícil. los triples tienen una importante desvenlaja. tina representación abstracta de una implementación del código de tres direcciones de la figura 8. si se los representa mediante índices de arreglo.String) AddrKind. if-f .2 como triples se proporciona en la figura 8. se dejan coino ejercicios.IntConst. ya que la cantidad de espacio se reduce y el compilador no necesita generar nombres para elementos temporales. Address. sub.gt. char * name. Las referencias de triple también se distinguen de las constantes poniéndolas entre paréntesis en los mismos triples.addr3. Por otra pnrte. y 6sta consiste en que. ) contents. tygedef enum Wmpty.eg.wri. Address addrl. Por ejemplo.

l. (l). que se debe conocer si se desea que programa de código P sea comprensible.5 Una representacion del código de tres direcciones de l figura 8. .1. Código P 1 3 El código P comenzó como un código ensamblador objetivo estándar producido por v compiladores Pascal en la década de 1970 y principios de la de 1980.(11)) (asn. 8 1 GENEMCI~N DE CÓDIGO (0) (1) (2 ) Firiura 8. y se han utilizado varias extensiones y modificaciones del mismo en di sos compiladores de código nativo. Nuestra versión de código P para esta expresión es la que se muestra en seguida: ldc 2 lod a m ~ i lod b ldc 3 sbi adi carga la constante 2 . un memoria de datos no especificada para variables nombradas y una pila para datos temporales. carga la constante 3 sustracción o resta entera adición de enteros . carga el valor de la variable a . Como el código P fue disefiado para ser directamente ejecutable. la mayor parte para lenguajes tipo Pascal. considere la expresión 2*a+(b-3) empleada en la sección 8. adem de mucha información específica para la máquina P.x. multiplicación entera . Las descripciones diversas versiones de código P real podrán encontrarse en varias referencias enumeradas final del capítulo. Fue diseñado par el código real de una máquina de pila hipotética. . Para evitar este detalle describiremos aquí una v sión simplificada y abstracta de código P apropiada para la exposición. denominada máquina P. Como un primer ejemplo de código P. cuyo árbol sintáctico aparece en la página 399. junto con cualquier registro que sea necesario para mantener la pila y apoyar la ejecución.2 como triples a (rd.404 CAP. carga el valor de la variable b . El código P también ha probado ser útil com digo intermedio.1. para la fue escrito un intérprete en varias máquinas reales. Para nuestros propósitos la máquina P está compuesta por una memoria de código. contiene una descn ción implícita de un ambiente de ejecución particular que incluye tamaños de datos.-) (gt.x.O) (if-f. La idea era hacer que los compilado de Pascal se transportaran fkilmente requiriendo sólo que se volviera a escribir el intérpre la máquina P para una nueva plataforma.fact) (3) 8. .

los multiplica (en orden inverso) e inserta el resultado en la pila. En primer lugar. Posteriormente. De este modo el código P hace una distinción entre direcciones de carga (Ida)y valores ordinarios (loa). carga constante 1 suma almacena tope a dirección debajo del tope y extrae ambas Advierta cómo este código calcula primero la dirección de x. que requieren dos valores enteros en el tope de la pila (los cuales son extraídos). y esta dirección es extraída como parte de la instrucción. corresponden a la diferencia entre el uso de x en el lado izquierdo y el uso de y que en el lado derecho de la asignación x: =y+l. . La instrucción mgi extrae estos dos valores de la pila. El código finaliza con un solo valor en la pila. e inserta el resultado. la dirección en la memoria variable en la cual se almacenará. La instrucción de código P rdi requiere que la dirección de la variable cuyo valor va a leerse se encuentre en la parte superior de la pila. ldc 2 irisertia el valor 2 en la pila temporal. Otras instrucciones de código P que aparecen en la figura 8. .que define la posición de un nombre de etiqueta. Como un segundo ejemplo introductorio. resta el primero del segundo. . el cual requiere que se encuentren dos valores en la parte superior de la pila temporal: el valor que será almacenado y. El código P de la tigura 8. debajo de éste. considere la sentencia de asignación Esto corresponde a las siguientes instrucciones en código P: Ida x lod y ldc 1 adi ato r carga dirección de x . y que insertan sus resultados booieanos.6 (página 406) contiene varias instrucciones nuevas de código P. y finalmente ejecuta un comando ato. Finalmente. Las siguientes dos instrucciones (lod b y ldc 3) insertan el valor de b y la constante 3 en la pila (ahora tenemos tres valores en la pila). Luego. la instmcción f jp ("false jump"). . los suma e inserta el resultado. que representa el resultado del cálculo. junto con comentarios que describen cada operación. La instrucción wri requiere que el valor que será escrito esté en la pate superior de la pila. y este valor se extrae como parte de la instrucción. la instmcción stg ("stop") correqmndiente a la instmcción halt del anterior c6digo de tres direcciones. lod a inserta el valor de la variable a en la pila.6 para el programa TINY de la figura 8. En primer lugar. luego el valor de la expresión que será asignada a x.1. que requiere un valor booleano en el tope o parte superior de la pila (el cual es extraído). y las operaciones de comparación grt (de "greater than") y equ ("equal to"). Como último ejemplo de código P en esta sección damos una traducción de código P en la figura 8. carga valor de y .6 que aún no se han comentado son: la instrucción lab. las instrucciones rdi y wri (sin parámetros) implementan las sentencias enteras read y write construidas en TINY. Finalmente. la instrucción sbi extrae los dos valores superiores de la pila. la instmcción sbi (de "sustracción entera". La instrucción sto también extrae estos dos valores (dejando la pila vacía en este ejemplo). la instmcción adi extrae los dos valores restantes de la pila. en inglés). cuya operación es semejante a la de otras instrucciones aritméticas.COdigo intermedio y estmcturas de datos para generación de código 405 Estas instrucciones se ven como si representaran las siguientes operaciones en una miquina P.

carga valor de fact . . y e1 código P no está "autocontenido" en el sentido que Las instrucciones funcionen implícitamente en una pila (y las localidades de pila implícitas son de hecho las direcciones "perdidas"). el código P es menos compacto que el código de tres direcciones en términos de números de iristrucciones. carga valor de x .6 Código P p n el programa a TlNY de la figura 8. salto a L2 si es falso . definición de etiqueta L2 . definición de etiqueta L1 Comparación del código P con el código de tres direcciones El código P en muchos aspectos está más cercano al código de miquina real que al código de tres direcciones. carga constante O . almacena (como antes) . 8 1 GENERACIÓN DE C~DIGO Figura 8. carga dirección de fact . La ventaja respecto a la pila es que contiene todos los valores temporales necesarios en cada punto del código. multiplica . prueba de igualdad . salta a L1 si es falso . almacena el tope a dirección del segundo y extrae . conio en el código de tres direcciones. Las instrucciones en código P también requieren menos direcciones: todas las instrucciones que hemos visto son instrucciones de "una dirección" o "cero direcciones". . carga dirección de fact . resta . almacenando el primero en la . .1 Ida x rdi lod x ldc O grt fjp L1 Ida fact ldc 1 Sto lab Ida lod lod mpi Sto Ida lod ldc sbi Sto lod ldc L2 fact fact x . extrae dos valores. carga valor de x . . escribe tope de pila y extrae . carga dirección de x lee un entero. y el compilacior no necesita asignar noinbres a ninguno de ellos. carga valor de x . carga dirección de x . carga valor de fact .CAP. . carga la constante 1 . . almacena a la dirección en el tope de la pila (y la extrae) carga el valor de x carga la constante O extrae y compara dos valores del tope x x 1 x O equ fjp L2 lod fact wri lab L1 StP inserta resultado Booleano . Por otra parte. extrae resultado Booleano. . carga constante 1 . dirección representada por el segundo .

mientras que descarta la direcciítn. en el cual no existen las asignaciones incrustadas.. una gramática con atributos para un atnbuto de cadena de código 3. la existencia de asignaciones incrustadas es un factor que implica complicaciones... .. "42" para un num o "xtemp" para un id). mientras que en secciones posteriores abordaremos la generación de código para construcciones de lenguaje individuales por separado. ya que la gramática con atributos es más simple debido a que no es necesario generar nombres para elementos temporales. junto de expresiones en C: exp +id = exp / aexp aexp -+ aexp +factor /factor factor -+ ( exp ) 1 num 1 id Esta grwitica sólo contiene dos operaciones.Técnicas básicas de generación de cbdigo 407 /mplementación del código P Históricamente. o lexema. y el token num simboli. Sin embargo. la que como la instrucción ato. En realidad. (Con esto el código P muestra sus orígenes de Pascal. s t n almacena el valor en la direccibn pero deja el valor en el tope de la pila. si el código generado se ve como un atributo de cadena (con instrucciones separadas por caracteres de retorno de línea). . . .. y generado directamente durante el análisis sintáctico o mediante un recorrido postorden del árbol sintáctico. Código P Consideraremos el caso de la generación del código P en primer lugar. Se supone que ambos tokens tienen un atributo strval previamente calculado. ...' El token i d representa un identificador simple. que es el valor de la cadena. supone que se encuentra un valor en la parte superior o tope de la pila y una dirección debajo de la misma. el código P se pueden definir como considere la siguiente gramática que representa un pequeño subcotiun atributo sinteti~ado.. entonces este código se convierte en un atributo sintetizado que se puede definir utilizando una gramática con atributos. Con esta nueva instrucción. ya que la instrucción de código P eitándar a t o es destructiva.. . En esta situación deseamos mantener el valor almacenado como el valor resultante de una expresión de asignación. i<. .1 Código intermedio o código objetivo como un atributo sintetizado La generación de código intermedio (o generación de código objetivo directa sin código intermedio) se puede ver como un cálculo de atributo similar a muchos de los problemas de atributo estudiados en el capítulo 6. L3 asignación en este ejemplo tiene la sciriántica siguiente: x = e almacena el valor de e en x 4 tiene el mismo valor resultante que e. del token (por ejemplo. Para ver cómo el código de tres direcciones. la asignación (con el símbolo =) y la adición (con el símbolo +l. En esta sección comentaremos los enfoques básicos para la generación de código en general.. pues el valor asignado se piede.) Resolvemos este problema introduciendo una instrucción de almacenamiento no destructiva s t n en nuestro código P. . .2.. 0' . o bien. el código P ha sido en su mayor parte generado como un archivo del texto. pero las descripciones anteriores de las implementaciones de cstructura de datos internas para el código de tres direcciones (cuádruples y triples) también funcionarán con una modificación apropiada para el código P. 8. . za una secuencia simple de dígitos que rcpresenta un entero..

~rtrrnr.pode =factor .1 Gramática con atributos de código P tomo un atributo de cadena sintetizado Regla gramatical Reglas semánticas exp. Observe también que.. Tambith empleamos dos notacione.pode fucror . la operación de asignación simplemente utiliza el nombre de la expresión en el lado derecho. .rpi + factor aexp +factor factor -+ ( exp factor + num factor -+ id ) exp. en e"Icaso de Los riodos interiores del operador. y como en la tabla 8.pCodr ++factor . .1.r(l y aexp -+ fnctor. En esa figura utilizamos el nombre de atributo pcod la cadena de código P. En este ejemplo simple sólo los nodos correspondientes al operador + necesitan nuevos nombres temporales.pcode = exp .rpz ern + amn aexp. 8 1 GENEMCI~N DE C~DIGO P se proporciona en la tiabla 8.pcode = "lda"// id . + ue.rctor -+ i d . t3.2 queoen el caso de las producciones unitarias exp -+ (re. (se devuelve uno nuevo cada vez que se llama a newtemp( )). además del atributo tacode.rlnxzl - Código de trer direcciones Una gramática con atributos para código de tres direcciones de 1 gramática de expresión simple anterior se proporciona en la tabla 8. por ejemplo.pccnie "ldc"11 num . . pero para asignar nombres temporales recsn genera a nodos interiores utilizamos una función newremp( ) que se supone genera una secuencia de nombres temporales tl.rp2 .pcode jizctor .p.pcode = "lod"// id . se generan nuevos atributos nnttte antes del tc~cocle ~isociado. En esa tabla utilizamos el atributo de código que se llama tacode (para codigo de tres direcciones)..pcode = ue.. la expresión (x=x+3) +4 tiene ese atributo Ida x lod x ldc 3 adi stn ldc 4 adi Tabla 8.. Advierta en la tabla 8. cl valor de cadena del tokcn sc utiliza c«nto&~cror.stWal ++ expz p o d e ++ "stn" ex^ acode = aexn mode uexp.1.~trval factor . + id = e. el código de tres direcciones requiere que se generen nombres temporales para resultados intermedios en las expresiones.2.CAP. A diferencia del código P. + para concatenación de cadena con un retorno de Iínea y // para concatenación de cadena con un espacio. el atributo turne. Dejamos al lector describir el cálculo del atributo pcode en ejemplos individua mostrar que.pcode ++ "adi" u a p . en las producciones cle Iiojaj¿~c~or num y í. y esto requiere que la gramática con atributos incluya un nueva atributo name para cada n Este atributo también es sintetizado.~ diferentes para la concatenac de cadena: + + cuando las instrucciones van a ser concatenadas con retornos de línea i tados entre ellas y // cuando se está construyendo una instrucción simple y se va a ins un espacio. t2.que (a diferenci:~ y + . se transportan tle hijos a padres y que.

2 . .tacode = " factor . el uso de la concatenación de cadena causa que se desperdicie una cantidad desmesurada de copiado de cadenas y memoria a menos que los operadores de la concatenación sean muy complejos.rp -+ uexp ue. Visualizar la generación de código como el cálculo de un atributo de cadena sintetízado es útil para niostrar claramente las relaciones entre las secuencias de código de las diferentes partes del árbol sintáctico y para comparar los diferentes m6todos de generación de código.. aunque es útil visualizar cl código corno puramente sintetizado.nume twwtenipf ) aexpl . . . Esto es una consecuencia del hecho de que la evaluación de atributos siempre crea un elemento temporal para &da subexpresión.tac»de = " " * id del código Pj no se genera ningún código de tres direcciones en tales nodos (utilizamos " " para representar la cadena vacía). Es por .. .xpl -+ <iexp2+ factor exp . . .nome aexp . dejamos al lector mostrar que. :-:: ~'matica con atributos p n a .. por lo regular es mucho niás deseable generar pequeños segmentos de código a medida que continúa la generación de cótligo y escribir estos segmentos en un archivo o hien insertarlos en una estructura de datos (tal como un arreglo de cuádruples). incluyendo los lados derechos de las asignaciones.iacode factor .nutne = num ..TPcnicas básicas de generación de código .tacode = exp . la generacidn de c6tligo en general depende mucho de . t~icude uexp.srn>ul factor ..nnme =factor . 8.tacode fuctor . . En segundo.tacode = aexp2 .:f&la . la expresión (x=x+3) tiene el atributo tacode +4 (Esto supone que newtemp( j es llamado en postorden y genera nombres temporales comenzando con tl. En primer lugar.nume = aexp . Finalmente..narnc exp Sacode = uexp .. y csto complica cn gran medida las grarn5tic. dadas Ias ecuaciones de atributo de la tabla 8..ndme // "=" // aexp2 ..nome /irctor .tacode + factor. .~ .) Advierta cómo la asignación x=x+3 genera dos instrucciones de tres direcciones utilizando un elemento temporal. por varias razones.atributos hcrcdados. pero es poco practico como técnica para generación de código real.2..name = exp . Regla gramatical Reglas semánticas e.turme = i d ....is con atributos..sfrval flrctor . lo que requiere acciones semáriticas que no se iitlhieren a la-síntesis postorden estándar de atributos. De nueva cuenta.natne - ++ + uexp -+factor fucfar -+ f exp 1 factor + ntm factor uecp .name / j "+" /lfactor .tacode ae.tucocla =factor ..rp.

Observe que este procedimiento recursivo no sólo tiene un componente postorden (qu genera código parti implementar la acción de T ) sino también un componente de preorde y un componente enorden (que genera el código de preparación para los hijos izquierdo derecho de Tf. genCode(1aijo izquierdo de T) . end. En general. acciones equival rante un análisis sintáctico. int val. char / * usado con ConstKind * / y números strval.. .ConstKind. begin if T no es nil then genere co'digo para preparar en el caso del código del hijo izquierdo de T ..Aasignf üptype.. pero ficilment extensibles a más): procedure genCode ( T: treenode ). .*rchild. . */ typedef STreeNode *SyntaxTree. typedef enum {OpKind. genere cddigo para implernentar la acción de T . en la siguiente subsección regresaremos a técnicas de generació código más directas. . 8 1 GENEMCI~N ~ D I G O DE E . cada acción que representa T requerirá una versión un poc diferente del código de preparación preorden y enorden.CAP. Para ver de manera detallada cómo se puede construir el procedimiento gencode en u ejemplo especifico considere la gramitica para expresiones aritméticas simples que hem estado utili~ando esta sección (véase la gramática en la página 407). . Con estas definiciones se puede dar un árbol sintictic« para la expresión (x=x+3 +4 1 como se ve en seguida: . typedef s t m c t streenode t NodeKind kinds üptype og. .. El algoritmo básico puede ser descrito com o el siguient cedimiento recursivo (para nodos de árbol con dos hijos como máximo.IaKindf NodeKind. esto que no nos preocupamos aquí por escribir ningún código (incluso pseudocódigo ra implementar las gramáticas con atributos de los ejemplos anteriores (pero vea los ej cicios).. / * usado con OpKind * / struct streenode *lchild. / * usado para identificadores 1 STreeNode. Las definiciones e en C para un árbol sintáctico abstracto de esta gramática se pueden proporcionar de L inaner a siguiente (compare con las de la página 111 del capítulo 3): typedef enum tPlus. genCode(h¿jo derecho de T) . 822 Generación de código práctica Las técnicas estándar de generación de código involucran modificaciones de los recomd postorden del irbol sintáctico implicado por las gramáticas con atributos de los ejem cedentes o.. En vez de eso. si no se genera un árbol sintáctico de manera explícita. genere código para preparar en el caso del cídigo del hija derecho de T ..

incluso con la variación necesaria en orden de recorrido. el código utiliza la función estándar de C s p r i n t f para concatenar las cadenas en el elemento temporal local codestr. Finalmente. así que un nodo designación tiene solamente un hijo (la expresión que se a~igna). . - Figura 8. emitcode ( "adi"). mientras que A s a i g n requiere cierto procesamiento tanto preorden como postorden. (Observe cómo el procesamiento combinado preorden y postorden de las asignaciones se traduce en secciones de acción dividida en la especificación Yacc. genCode(t->rchild). En segundo lugar se llama el procedimiento a m i tcode para generar una línea simple de código P.) Dejamos al lector escribir un procedimiento genCode y especificación Yacc para generar el código de tres direcciones como se especificó en la gramática con atributos de la tabla 8.Tkcnicas bkicas de generación de código Advierta que el nodo designación contiene el identificador que se está asignando (en ei campo s t r v a l ) .7.8 que corresponde directamente al código de la figura 8. ya sea en una estructura de datos o en un archivo de salida.1 la gram6- tica c n atributos de la o CODESIZE longitud máxima de 1 línea de código P if (t 1. las llamadas recursivas pueden no escribirse del mismo modo para todos los casos. sus detalles no se muestran. En esa figura haremos los comentarios siguientes acerca del código. todavía se puede realizar durante un análisis sintáctico (sin la generación de un árbol sintáctico). break.: NULL) f switch (t->kind) { case GpKind: switch (t->op) f case Plus: */ genCode(t->lchild). De este modo.7. J. Para mostrar que la generación de código. mostramos un archivo de especificación Yacc en la figura 8. En primer Lugar.2. También almacenmos números como cadenas en el campo strval en este ejemplo.1 lmplementación un prode tedimiento de generacien void gencode( SyntaxTree t) f char codestrtCODESIZE1 i /* de código pan código P cornspondiente a tabla 8.~ Basados en esta estructura para un árbol sintáctico podemos escribir un procedimiento genCode para generar código P como se da en la figura 8. los dos casos de operador ( P l u s y A s a i g n ) requieren dos órdenes diferentes de recorridos: P l u s necesita sólo procesamiento de postorden.

break. "ldc". emitCode(codestr).CAP. break.t->strval).. ) 1 1 generación de código P de acuerdo ton la gramática ton atnbutor de la / * hace que Yacc utilice cadenas como valores * / /* %) otro código de inclusión . ernitCode("stnm).1 .. default: emitCode ("Error") . 8 1 GENERACI~N DE C~DIGO case Assign: sprintf (codestr. 1 break. break.t->strval). genCode(t->lchild). break. break."%a %a". "lod"."%S %S". case Constkind: sgrintf(codestr. "Ida". case Idñind: sprintf(codestr. emitCode(code8tr). emitCode(code8tr). default : emitCode ( ''Error" ) ."%S %S".t->strval). * / tabla 8.

y puede variar de formas muy simples de seguimiento cntplcadas cn conjunto con la expansión de macro.$1)."8s %S". Esto requiere que el compilador se mantenga al tanto de las decisiones acerca de ubicaciones e idiomas de código en estructuras de datos separadas y que los procedimientos del macro varíen la secuencia de código como se requiera mediante las clases particulares de datos in~olucradas la en instrucción de código intermedio. Por lo regular la generación de código a partir del código intermedio involucra alguna o ambas de las dos técnicas estándar: expansión de macro y simulación estática. entonces debe efectuarse otro paso en el código intermedio para generar el código objetivo final (por lo regular después de algún procesamiento adicional del código intermedio). Una cuestión particularmente importante es la asignación apropiada de los registros y el mantenimiento de la información . Esto tltinbih requiere de más estructuras de datos.*obreel uso de los mismos (es decir.. emitCode(codestr). en particiilar si el código intermedio es muy simbólico y contiene poca o ninguna información acerca de la máquina objetivo o el ambiente de ejecución. 1 %% / * funciones de utileria . En este caso. cada paso puede ser mucho más complejo que las formas simples de expansión de macro disponibles del preprocesador de C o cnsanibladores de macro. 1 I ID .2.'S I m %S". Este paso puede ser bastante complejo por sí mismo. "ldc". Aplazaremos un análisis detallado de tales cuestiones de asignación hasta más adelante en este capítulo.. De este modo. factor : ' ( ' exp ') ' % f sprintf (codestr.Técnicas bkicar de generación de código aexp : aexp ' + ' factor íemitcode ( "adi") . * / 8. el paso de la generación de código final debe suministrar todas las ubicaciones reales de variables y temporales. Por ahora comentaremos sólo técnicas generales para este proceso. í sprint£(codestr.3 Generación de código objetivo a partir del código intermedio Si un compilador genera código intermedio. cuáles registros se encuentran disponibles y cuáles contienen valores conocidos).1 I factor . La simulación estatiea involucra una simulación en línea recta de los efectos del código intermedio y generación tle código objetivo para igualar estos efectos. más el código necesario para mantener el ambiente de ejecución. emitCode(c0destr). La expansión de macro involucra el reemplazo de cada clase de instrucción del código intermedio con una secuencia equivalente de instrucciones de código objetivo. ya sea directamente durante un análisis sintáctico o desde un &bol sintáctico. hasta la nltarncrite . "lod".$1).

se genera la instrucción de tres direcciones t l = x + 3 y la pila se cambia a +--tope de la pila t1 La instmcción s t n provoca entonces que se genere la instrucción de tres direcciones x 3 tl . cuando se procesa fa operación adi. esta sección.08 y 309. Consideremos primero la traducción del código P para esta expresión: Ida x lod x ldc 3 adi etn 1dc 4 adi en su correspondiente código de tres direcciones: Esto requiere que realicemos una simulación estatica de la pila de m iina P para encontrar equivalentes de tres direcciones del código dado. y la pila tiene el aspecto siguiente: +--tope de la pila Ahora. y consideremos la expresión (x=x+3) cuyas traducciones en cbdigo P código de tres direcciones se proporcionaron en las páginas 4. Después de las primeras tres instrucciones de código P. respectivamente. Consideremos la pequeña gramática de expresión que hemos estado utilizando como ejemplo de ejecución en +4. no se han generado todavía instrucciones de tres direcciones.sofisticada interpretación abstracta (que mantiene los valores algebraicamente a medida que son calculados). pero la pila de máquina P se ha modificado para reflejar las cargas. Podemos obtener alguna idea de los detalles de estas técnicas con~iderando problema el de traducir desde el código P al código de tres direcciones y viceversa. Hacemos esto con una estructura de datos de pila real durante la traducción.

Ahora consideraremos el caso de traducir del código de tres direcciones al código P. Por consiguiente. la instrucción adi provoca que se genere la instrucción de tres direcciones y la pila se cambie a /-tope de la pila Esto completa la simulación estática y la traducción. una instrucción de tres direcciones siempre se puede traducir en la secuencia de código P Ida a lod b lod c adi Sto . o ldc b si b es una constante o ldc c si c ea una constante Esto resulta en la siguiente traducción (algo insatisfactoria) del anterior código de tres direcciones en código P: Ida lod ldc adi Sto Ida lod Sto Ida el x 3 x tl t2 (continúu) . esto puede hacerse mediante expansión simple de macro. . Si ignoramos la complicación agregada de los nombres temporales.Técnic