Capítulo 1: Presentación del Trabajo

Tamaño: px
Comenzar la demostración a partir de la página:

Download "Capítulo 1: Presentación del Trabajo"

Transcripción

1 Presentación del Trabajo Capítulo 1: Presentación del Trabajo 1.1 Introducción La aparición de los traductores en la computación marcaron un hito haciendo que la programación se simplificara y que la computación se pudiera aplicar a más áreas de la ciencia y la tecnología, ya que la obtención de programas se haría más fácil a medida que aparecían nuevos y mejores lenguajes de programación. Se puede definir a un traductor como un programa que convierte o traduce un texto de entrada escrito en un lenguaje A a otro texto de salida escrito en un lenguaje B. Gráficamente: 7H[WRHQ /HQJXDMH$ 7UDGXFWRU 7H[WRHQ /HQJXDMH% Fig. 1 Función de un traductor. Al proceso de traducción se lo divide, por conveniencia, en varias fases, entre las que se destacan: el análisis lexicográfico, el análisis sintáctico, el análisis semántico y la generación de código. La escritura del código fuente para las fases de análisis sintáctico, lexicográfico y semántico son en general demasiado complicadas, y la gran cantidad de errores que se cometen desalientan al programador a usar la teoría de los lenguajes formales en sus aplicaciones finales. Por ejemplo, no es común ver un programa que valide la entrada de datos por medio del uso de un lenguaje artificial creado a tal efecto (la carga de un sistema de ecuaciones e inecuaciones puede ser realizada más fácilmente usando un lenguaje artificial para la especificación de las mismas). Este trabajo presenta un generador de analizadores sintácticos y un generador de analizadores lexicográficos, que intentarán ser una buena solución al problema expuesto previamente, para que el programador sólo tenga que concentrar sus esfuerzos en su objetivo sin la necesidad de investigar en profundidad la teoría subyacente en la construcción de los analizadores. La técnica de análisis lexicográfico que utilizarán los analizadores lexicográficos que se obtengan con el generador presentado en este trabajo es 1

2 Presentación del Trabajo por medio de autómatas finitos determinísticos que eventualmente se construyan para los componentes léxicos a buscar dentro del texto de entrada. Los componentes léxicos podrán ser especificados por medio de expresiones regulares. Los analizadores sintácticos que se obtengan con el generador presentado en este trabajo usarán la técnica LR(1) simple. La salida de los generadores es un módulo fuente en C++ con el respectivo analizador (sintáctico o lexicográfico). La implementación realizada incluye la posibilidad de la generación y uso del analizador en tiempo de ejecución, lo que permite cambiar dinámicamente de analizador, durante la ejecución del programa. La tendencia actual en la Ingeniería de Software es el uso de generadores de programas para desarrollar las partes del sistema que no requieran de la reiterada intervención de la inteligencia humana. Por ejemplo, hoy en día se usan generadores de programas para el desarrollo de la interfase en pantalla para programas que trabajarán en entornos gráficos tipo Windows, Presentation Manager de OS/2, Macintosh y X Window. Se sabe que el aprendizaje de la programación de una interfase gráfica es un poco complicado, pero es repetitivo y rutinario, por lo tanto se implantaron generadores que hagan ese trabajo. Lo mismo ocurre con la Teoría de los Lenguajes Formales: la teoría que se maneja es compleja, pero la tarea de la construcción de un analizador sintáctico es repetitivo y rutinario, y puede ser automatizado. Los generadores presentados en este trabajo intentarán ayudar al programador en el desarrollo de traductores (de un lenguaje a otro, sean intérpretes o compiladores), de rutinas para la recuperación inteligente de la información, y en la utilización de la Teoría de los Lenguajes Formales en otras áreas, como por ejemplo, el reconocimiento de rasgos distintivos en circuitos electrónicos impresos por medio de la concordancia de patrones (Jarvis [1976]). En nuestro ambiente informático existen herramientas similares a las que se presentan en este trabajo: LEX es similar al generador de analizadores lexicográficos y YACC es similar al generador de analizadores sintácticos. Pero hay ciertas diferencias fundamentales con ellos, algunas de las cuales son importantes mejoras introducidas en este trabajo. Las mismas se detallan a continuación. 2

3 Presentación del Trabajo 1. Uso del C++ como lenguaje objeto. Se hace uso del Paradigma Orientado a Objeto. En particular, se hace uso de templates (familias de clases y familias de funciones, sólo presente en C++, Stroustrup [1994]) para la implementación de los analizadores. Se organiza adecuadamente el código generado y se encapsula la complejidad del funcionamiento de los analizadores. 2. La modificación manual de los analizadores generados es mucho más fácil puesto que se separa el código que es igual para todos los analizadores del código que cambia entre ellos, y se los da en módulos separados. 3. Posibilidad de mostrar las tablas generadas en formato texto para su posterior lectura por parte del programador. 4. El generador de autómatas finitos determinísticos (AFDs) a partir de expresiones regulares usa un lenguaje para la especificación de la expresión regular fuente similar al usado por LEX, pero con sintaxis más simple. De esta manera es más fácil y directa la lectura de una expresión regular. 5. Es posible reusar los generadores en programas del usuario. De esta manera, es posible cambiar el analizador en tiempo de ejecución. Por ejemplo, para la recuperación de la información en bases de datos se pueden usar expresiones regulares; el Paradox y el dbase V usan un lenguaje simplificado de expresiones regulares, en el que no es posible el uso de paréntesis ni el operador ' '. 6. Se documenta la estructura de las tablas, y las mismas se implementan en estructuras simples, a los efectos de poder retocarlas en caso de ser necesario. En LEX y en YACC esto no es posible. 7. Posibilidad de reusar trabajos hechos y compilados previamente, sin la necesidad de retocar nada, debido a la independencia de los módulos generados. Los módulos generados por YACC y por LEX son muy interdependientes, y es imposible encadenarlos con otros trabajos hechos anteriormente debido a la colisión de nombres. 8. Debido a que se separan las acciones semánticas en un módulo aparte de donde se realiza el análisis sintáctico, es más fácil la depuración de las mismas. En YACC es demasiado complicado depurar las acciones semánticas. Los analizadores sintácticos ni los lexicográficos no se 3

4 Presentación del Trabajo depuran, el código que los implementa ya fue depurado por el autor de este trabajo. 9. La generación de códigos fuentes en otro lenguaje distinto al C++ es posible escribiendo solamente el código que imprime las tablas y rescribiendo el código que implementa los analizadores (la biblioteca de soporte). 10.A los dispositivos de E/S los elige el usuario de los generadores. No se hace la imposición de usar la entrada y la salida estándar del sistema operativo, como ocurre en YACC y en LEX. En el punto 5 se habla de la posibilidad de reusar los generadores en programas del usuario. De hecho, los generadores han sido concebidos como bibliotecas de código C++. Para poder usarlas hace falta escribir un programa que haga uso de esas bibliotecas. A ese programa que se escribe se lo denomina usuario de esas bibliotecas. Luego, los programas generadores aquí presentados son en realidad programas que usan las bibliotecas de soporte. Por lo tanto, la reusabilidad de los generadores es más directa y simple, y son los dos primeros ejemplos de uso que se dan en el trabajo. Como ejemplos de aplicación, los cuales podrán ser de mucha utilidad para los estudiantes de la cátedra de Compiladores, se incluyen los siguientes: El programa generador de analizadores sintácticos y analizadores lexicográficos a partir de una gramática, que da como salida un texto en C++. Secciones 7.3, 7.4, 20.1 y El programa generador de autómatas finitos determinísticos a partir de expresiones regulares, que da como salida un texto en C++ con el AFD. Secciones 6.7, 22.1 y Un programa para validar esquemas deductivos por medio de la Teoría Semántica del Cálculo Proposicional. Sección 11.9, 11.10, y Se muestra el uso del generador de analizadores sintácticos y del generador de analizadores lexicográficos. Una calculadora simple. Capítulo 14. Se muestra generador de analizadores sintácticos y del generador de analizadores lexicográficos. Consultas a archivos bases de datos BCE. Capítulo 15. Se muestra el uso del generador de AFDs usado por el generador de analizadores lexicográficos. El generador de AFDs es encadenado en el programa final. 4

5 Presentación del Trabajo Un traductor sencillo para un lenguaje simple, y un intérprete para el código objeto generado por el traductor. Capítulo 16. Se muestra el uso del generador de analizadores sintácticos y del generador de analizadores lexicográficos. Este ejemplo puede servir de mucha utilidad a estudiantes de la cátedra de Compiladores. La interfase en pantalla de los ejemplos de aplicación fueron todos desarrollados para Windows 3.1 o posterior. La parte más importante de ellos, la que hace uso de los fuentes generados por los generadores de este trabajo, es portable a cualquier sistema operativo. Los códigos fuentes son también compilables con Borland C para OS/2, sin ninguna modificación; funcionarán usando el Presentation Manager. Los fuentes y ejecutables de los ejemplos se incluye en el disco que acompaña a este trabajo, a efectos de que puedan ser mejorados por algún interesado. Para los programas generadores, en el disco que acompaña este trabajo se incluye una versión que funciona bajo Windows y otra bajo DOS. El código fuente de las versiones Windows de los generadores son compilables sin ninguna modificación con Borland C para OS/2, se podrá obtener una nueva versión que funcionará bajo Presentation Manager de OS/2 Warp. El código fuente de las versiones modo caracter de los generadores se podrán compilar sin ninguna modificación con Borland C para OS/2, y obtener así una nueva versión que funcionará en OS/2 modo caracter. La versión modo caracter del ejecutable de los generadores funcionarán sin problemas bajo Windows 95 en modo caracter, así como también las versiones gráficas. Debido a que en la bibliografía de computación no existe un documento que explique cómo nacen las gramáticas generativas, en el capítulo 17 se da una introducción a las mismas, tratando de explicar cómo fueron concebidas por Chomsky. El presente trabajo intenta servir de bibliografía para los interesados en escribir analizadores sintácticos y/o lexicográficos, así como también para usar la Teoría de los Lenguajes Formales en otras áreas no mencionadas aquí. No es intención de este trabajo el de servir como bibliografía para la escritura de compiladores ni de intérpretes, ya que se deben tratar otros temas como por ejemplo la implementación de los espacios de nombres, lo que está fuera del ámbito del tema tratado en este trabajo. 5

6 Presentación del Trabajo 1.2 Organización del trabajo Este trabajo está organizado en 4 partes a saber: Parte 1 - Marco teórico: en esta parte se explicarán los algoritmos de generación usados por los generadores. Parte 2 - Implementación de los generadores: en esta parte se documentará la implementación realizada para los generadores. Parte 3 - Documentación para el usuario: en esta parte se incluye la documentación que necesitará el usuario de los generadores. Cada capítulo debe ser tomado como un libro aparte. Parte 4 - Ejemplos de aplicación: en esta parte se presentan y documentan los ejemplos de uso de los generadores. Parte 5 - Anexos, Códigos Fuentes y Conclusión: en esta parte se encontrará información adicional relativa a este trabajo, el código fuente de la implementación documentada en la parte 2 y la conclusión. 1.3 Consideraciones acerca de la Implementación El software de la implementación de los algoritmos se realizó en C++ debido a 3 razones fundamentales: 1. El alto nivel de reusabilidad del lenguaje. 2. Es un lenguaje que ofrece todo lo que un Ingeniero en Software puede pedirle a un lenguaje de programación. Hay disponibles muchas herramientas para trabajar con C Está disponible a muy buen precio para muchos sistemas operativos. Este trabajo no pretende ser un curso de programación. Por esta razón, en la documentación de las implementaciones realizadas se trata en detalle los fundamentos teóricos y en resumen las implementaciones en C++. 6

7 Parte 1. Marco Teórico 7

8 Introducción a la Teoría de los Lenguajes Formales Capítulo 2: Introducción a la Teoría de los Lenguajes Formales 2.1 Introducción Previo a la lectura de los capítulos que se encuentran en la Parte II se aconseja leer el Capítulo 17 en la Parte V en donde se da una correcta introducción a las gramáticas generativas y la lingüística matemática. Puede consultar además la bibliografía que trata la teoría de compiladores. En los puntos siguientes se hace una breve introducción a los conceptos fundamentales usados por los 2 generadores y en los capítulos siguientes se explican detalladamente cada uno de los algoritmos de análisis y de generación. Puede ser que el lector que se esté iniciando en la teoría de los lenguajes formales necesite ver algunos ejemplos; en este capítulo se minimiza la inclusión de ejemplos puesto que los conceptos que introduce son muy básicos, por lo que se aconseja leer la bibliografía sobre compiladores e intérpretes. 2.2 Lenguajes y Gramáticas Alfabeto Se denomina Alfabeto de un lenguaje a los caracteres que pueden formar parte de las oraciones del mismo. Para el Castellano tenemos como Alfabeto a todas las letras y los signos de puntuación. Se especifica como un conjunto de elementos Cadena o Tira de Caracteres La noción más básica de la Teoría de los Lenguajes es la tira o cadena de caracteres, que está formada por la concatenación de caracteres. La cadena mínima o nula se denomina λ (lambda). 8

9 Introducción a la Teoría de los Lenguajes Formales Lenguaje Un lenguaje es, en general, un conjunto de cadenas de caracteres. A cada cadena de caracteres la denominaremos oraciones. Está formado por los dos elementos siguientes 1. Un diccionario, que indica los significados de las palabras. 2. Un conjunto de reglas para describir las oraciones válidas del lenguaje. Este conjunto de reglas forma la gramática del lenguaje. Se denomina semántica al estudio del significado de las oraciones y frases de un lenguaje, y su interpretación. Se denomina sintaxis al estudio de la estructura del lenguaje. La sintaxis de las oraciones del lenguaje (la estructura del lenguaje) se la explicita a través de reglas de producciones. Se denomina análisis sintáctico a la operación que se ejecuta para verificar la gramaticalidad de una oración. Es un proceso de validación del texto fuente de entrada. En cualquier situación donde se requiera validar la entrada de datos se puede usar el análisis sintáctico. Se denomina análisis semántico al proceso que se ejecuta para verificar la coherencia en el significado de una oración. Se denomina análisis lexicográfico a un caso especial de análisis sintáctico en donde se usan técnicas especiales y más rápidas para tratar con la entrada de datos (texto fuente). Lea también el punto Noción de Gramática La noción de gramática que aquí se presenta es debida a Chomsky en el año 59. Una gramática generativa transformacional está caracterizada por una cuádrupla ordenada como la siguiente: donde: G = ( N, T, R, O ) 9

10 Introducción a la Teoría de los Lenguajes Formales N: es el conjunto de símbolos no terminales usados para construir las reglas de la gramática y no figuran en las oraciones del lenguaje. A veces se los denomina como no terminales o metanociones. T: es el conjunto de símbolos terminales. Cualquier oración del lenguaje debe ser una cadena de símbolos de T, según la estructura que especifiquen las reglas. A veces pueden ser llamados directamente terminales o nociones. R: es el conjunto de las reglas de rescritura de la gramática. Tienen la forma cadena1 --> Cadena2. O: es el símbolo más importante del conjunto N, se denomina símbolo inicial, axioma, símbolo distinguido o cabeza de lenguaje. Se usa para comenzar las derivaciones en el análisis sintáctico de las oraciones. Al lenguaje L generado por la gramática G está formado por todas las oraciones tales que se puede encontrar a partir de O, por medio de aplicaciones sucesivas de las reglas de la gramática. Se denota por L(G) = { α / O -*-> α El conjunto de Reglas y la Clasificación de Chomsky La clasificación publicada por Chomsky en el año 1959 introduce 4 tipos de gramáticas a base de cambiar solamente el tipo de las reglas de derivación que se encuentran en el conjunto R. A las gramáticas del Tipo 0 y del Tipo 1 se las suele denominar gramáticas generativas trasnformacionales. Lo de transformacional hace referencia al hecho de que transforman una secuencia de caracteres en otra secuencia de caracteres en un solo paso. Gramáticas de Tipo 0 o gramática con estructura de frase Las reglas en este tipo de gramática tienen la forma: α ---> β donde α pertenece a (N U T)+ y β a (N U T)* Por la definición dada, la regla que deriva en λ pertenece al lenguaje. 10

11 Introducción a la Teoría de los Lenguajes Formales Este tipo engloba a todos los otros 3 tipos de gramática. Actualmente no se publicó un trabajo en el que se haya presentado un analizador sintáctico que trabaje con una gramática de éste tipo. Se dice que la Máquina de Turing puede tratar este tipo de gramática. Las máquinas de Turing son bidireccionales (pueden ir hacia adelante o hacia atrás en la examinación de los caracteres de entrada). Gramáticas de Tipo 1 o gramática sensible al contexto Las reglas tienen la forma: α A β ---> α γ β donde A está en Ny, α y β en (N U T) * y γ en (N U T)+ Se llama sensible al contexto por que cambia A por gamma sólo en el contexto formado por alfa... beta. Aquí se puede observar que el problema de número que tenemos al trabajar con lenguajes naturales (los que hablamos los humanos) puede ser tratado con gramáticas de este tipo (por ejemplo, la concordancia del número del verbo con el del sujeto). Se puede construir un autómata bidireccional para tratar esta gramática. Gramáticas de Tipo 2 o gramáticas de contexto libre La forma de las reglas es: A --> α donde A está en N y α en (N U T)* Se llama de contexto libre porque se puede cambiar A por α independientemente del contexto en el que aparezca A. Este tipo de gramáticas son las que se usan para los lenguajes de computación. Cuando se utilice el término gramática en capítulos siguientes, se quiere decir gramáticas de Tipo 2 o 3. Para tratar estas gramáticas es necesario un autómata de pila. 11

12 Introducción a la Teoría de los Lenguajes Formales Gramáticas de Tipo 3 o gramáticas regulares Las reglas pueden tener una de las dos formas siguientes: A ---> a B o bien A ---> a donde A y B son de N y a de T. El no terminal B puede aparecer a la izquierda de a en la regla. Tres años antes de la presentación de esta clasificación, Kleene estudió por primera vez a las expresiones regulares. Resultó ser que las expresiones regulares es otra forma muy compacta de escribir una gramática del Tipo 4. El tratamiento de este tipo de gramáticas es a través de autómatas finitos. 2.3 Gramáticas de Contexto Libre En esta sección estudiaremos conceptos fundamentales acerca de este tipo de gramáticas, para pasar al capítulo siguiente en donde se utilizarán las operaciones fundamentales con ellas, que serán usados más adelantes Recursividad La recursividad es un mecanismo amplificador que permite definir formas complicadas de un lenguaje con muy pocas reglas. Veamos un ejemplo sencillo: sea el lenguaje cuyas oraciones son números enteros según la siguiente descripción dada por extensión: L = { números sin signos formados a partir de la combinación de los dígitos comprendidos entre el 0 y el 9, por ejemplo el 457 Para construir las reglas de la gramática que den cuenta de la estructura de este lenguaje se podrían dar las siguientes reglas: N ---> D N ---> D D 12

13 Introducción a la Teoría de los Lenguajes Formales N ---> D D D D ---> Se observa que la gramática construida no explica la estructura de los números de 4 o más dígitos. Aún más, si consideramos la posibilidad de que hay números con gran cantidad de dígitos tendríamos una gramática con una gran cantidad de reglas, si es que utilizamos esta forma de escribirla. Entonces, como solución aparece la recursividad, y a la gramática la rescribiríamos de la siguiente manera: N ---> N D N ---> D D ---> Arbol de Análisis Sintáctico Es una representación gráfica del resultado obtenido al aplicar las reglas de la gramática partiendo de O (el símbolo inicial) hasta llegar a la oración que se esté analizando sintácticamente. Al proceso de análisis sintáctico se lo menciona muchas veces como el proceso de construir el árbol de análisis sintáctico de la oración Arbol sintáctico Es una versión simplificada de un árbol de análisis sintáctico. Los nodos de un árbol sintáctico son símbolos terminales y representan operaciones entre ellos y el orden en que deben ser ejecutadas. Un árbol sintáctico puede construirse a partir del análisis sintáctico del texto de entrada. La definición de árbol sintáctico dada aquí es la que sirve a los efectos de este trabajo y puede no coincidir con alguna definición que se encuentre en la bibliografía sobre compiladores. En términos de esta definición, cualquier árbol que se diga ser árbol sintáctico y que tenga nodos con no terminales será considerado simplemente árbol. En el capítulo 3 se utilizan árboles sintácticos para expresiones regulares. 13

14 Introducción a la Teoría de los Lenguajes Formales Ambigüedad de una Gramática Una gramática es ambigua si el lenguaje definido por la misma tiene alguna oración que tenga más de un árbol de análisis sintáctico. Se llama ambigua a la gramática y no al lenguaje que define, puesto que frecuentemente es posible modificar la gramática para que deje de ser ambigua sin tocar para nada el lenguaje implicado. Pero hay lenguajes para los que no existen más que gramáticas ambiguas: a estos lenguajes se les llama "ambiguos intrínsecos". La ambigüedad de una gramática es una propiedad indecidible, lo que significa que no existe ningún algoritmo que acepte una gramática y determine con certeza y en un tiempo finito si la gramática es ambigua o no. Una gramática que es tratable con la técnica LR(k) o con la técnica LL(k) no es ambigua, por lo que resulta un buen método de determinar la ambigüedad Gramáticas y el Análisis Sintáctico La construcción de un analizador sintáctico consiste en el desarrollo de subrutinas que revisen el código fuente de entrada y verificar que el mismo cumple con las reglas gramaticales. Si cumple diremos que es gramaticalmente correcta, lo cual no significa que sea entendible. Existen varias técnicas de análisis sintácticos entre las que se destacan 2 grupos principales: la técnica LR y la LL. La técnica LR(k) (Left to right scanning - Rightmost parsing, examen de izquierda a derecha - análisis a derechas), es un método de análisis ascendente, esto es, se parte de la oración para llegar al símbolo inicial O. Ha sido presentado al público por primera vez por Knuth [1965]. Reconocen la gran mayoría de los lenguajes de programación que se puedan generar con gramáticas de contexto libre. En general su uso no tiene restricciones, pero la construcción del analizador sintáctico es demasiado complicada por lo que se hace necesario disponer de generadores de analizadores sintácticos para simplificar la tarea. La técnica LL(k) (Left to right scanning - Leftmost parsing, examen de izquierda a derecha - análisis a izquierdas) es un método descendente, esto es, parte desde el símbolo inicial O para llegar a la oración. Las técnicas de análisis predictivos fueron estudiadas a fondo por Knuth [1971]. El método es 14

15 Introducción a la Teoría de los Lenguajes Formales mucho más restrictivo que el LR, ya que el número de gramáticas que se puede tratar es mucho menor. Gráficamente: *UDPiWLFDV/5 *UDPiWLFDV// 5HVXPHQJUiILFRH[WUDtGRGH6DQFKLV/ORUFD*DOiQ3DVFXDO>@ Fig. 2 Comparación del número de gramáticas tratables con la técnica LR y el número de gramáticas tratables con la técnica LL. El generador de analizadores presentado en este trabajo genera analizadores que trabajan con la técnica LR(1) Simple, a la que se le suele llamar SLR(1) o simplemente SLR El Análisis Lexicográfico La función principal del análisis lexicográfico es la de convertir secuencias de caracteres de entradas en símbolos léxicos. Hay varias razones para dividir la fase de análisis de la traducción en análisis lexicográfico y análisis sintáctico: 1. Un diseño sencillo es la consideración más importante. El uso de un analizador lexicográfico simplifica el analizador sintáctico. Imagine sino un analizador sintáctico que incluya las convenciones de comentarios y espacios en blanco. 2. Se mejora la eficiencia del compilador. Un analizador léxico independiente permite construir un procesador especializado y potencialmente más eficiente para esta función. Gran parte de tiempo del análisis se consume en leer el programa fuente y separarlo en símbolos léxicos. 15

16 Introducción a la Teoría de los Lenguajes Formales 3. Se mejora la transportabilidad del compilador. Las peculiaridades del alfabeto de entrada y otras anomalías propias de los dispositivos de entrada pueden limitarse al analizador lexicográfico. La forma más simple de especificar símbolos léxicos y la que más se usa actualmente es mediante el uso de expresiones regulares. La construcción de un analizador lexicográfico a partir de estas expresiones regulares es una tarea complicada, para lo cual es necesario disponer de un generador de analizadores lexicográficos. Muchas veces se escuchará decir que la construcción manual del analizador lexicográfico resulta en uno que funciona mucho más rápido que el que se obtendría a través del uso de un generador. El generador que se presenta en este trabajo es un intento de solución a este problema. La inclusión de un analizador lexicográfico en el proyecto de construcción de un traductor implica la construcción de otro analizador sintáctico, sólo que para una gramática del Tipo 3 de la clasificación de Chomsky la cual es tratada con técnicas distintas que las del Tipo 2. Las oraciones encontradas por ese analizador son pasadas al analizador sintáctico del lenguaje fuente, y como consecuencia directa se tiene la simplificación de la gramática del lenguaje fuente. Muchas veces este concepto es confundido. donde Esquemas de Traducción Se define un esquema de traducción como un sistema formal del tipo: EDT = { N, T e, T s, R, O N: es el conjunto finito de símbolos no terminales. T e : es el conjunto finito de símbolos terminales del lenguaje de entrada o fuente. T s : es el conjunto finito de símbolos terminales del lenguaje de salida u objeto. R: es el conjunto finito de reglas de traducción que poseen la estructura: A ---> α, β donde A pertenece a N 16

17 Introducción a la Teoría de los Lenguajes Formales α pertenece a ( N U T e )* β pertenece a ( N U T s )* En cualquier caso se cumple que N intersección (T e U T s ) = { O: es el símbolo inicial. La estructura del lenguaje de salida se puede explicar con la gramática siguiente: donde G s = { N, T s, P, O N: es el conjunto de símbolos no terminales del lenguaje fuente. O es un elemento de este conjunto. T s : es el mismo conjunto de símbolos terminales del esquema de traducción. P: es el conjunto de reglas de la gramática, cuyos elementos son las reglas que: P = { A ---> β / (A ---> α, β) era una regla de traducción del esquema Acciones Semánticas / Rutinas Semánticas En la bibliografía utilizada se encontrarán definiciones distintas de lo que son las acciones semánticas y las rutinas semánticas. En Sanchis Llorca-Galán Pascual [1986], los términos acciones semánticas se aplican a los autómatas finitos, y los términos rutinas semánticas a los analizadores sintácticos (que en definitiva son también autómatas). En Aho-Sethi-Ullman [1990], los términos acciones semánticas se aplican a los analizadores sintácticos. Los términos rutinas semánticas no son utilizados en ese libro. A los efectos de unificar conceptos, en este trabajo se tomarán como sinónimos a los términos acciones semánticas y rutinas semánticas, 17

18 Introducción a la Teoría de los Lenguajes Formales fundamentado en que existe una analogía entre un analizador sintáctico y un autómata finito. La definición utilizada será la siguiente: se denomina acciones semánticas a los fragmentos de oraciones de un lenguaje intercalados en la parte derecha de una regla de una gramática libre de contexto. En la definición dada en el punto anterior se habló de esquemas de traducción, introduciendo un nuevo tipo de reglas en las que en la parte derecha se encuentran cadenas de símbolos terminales del lenguaje objeto (o de salida). Se transcribe la forma general de las reglas de traducción: A ---> α, β Aquí, β es la cadena de símbolos terminales del lenguaje objeto. Luego, se denomina acciones semánticas a las acciones que se ejecutan para producir esa cadena de símbolos terminales del lenguaje objeto. En general, las acciones semánticas se escriben con fragmentos de oraciones de un lenguaje distinto al fuente y al objeto. En este trabajo se utiliza el lenguaje C++ para escribir acciones semánticas. 18

19 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos Capítulo 3: Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos 3.1 Introducción En este capítulo se presentan los algoritmos usados por el generador de autómatas finitos determinísticos que sirve como base principal para la construcción de un analizador lexicográfico. A diferencia del capítulo 2, en este capítulo se documenta en detalle cada aspecto de los algoritmos, y se incluyen ejemplos que clarificarán su funcionamiento. Durante su lectura no se debe olvidar que el tratamiento de lenguajes con expresiones regulares es un caso especial de análisis sintáctico (implícitamente se está trabajando con una gramática del Tipo 3), al que comúnmente se lo denomina análisis lexicográfico. La teoría aquí presentada se puede aplicar a muchas áreas de la ciencia, y no sólo al análisis lexicográfico. 3.2 Arbol Sintáctico para una Expresión Regular Descripción Se construirá un árbol sintáctico para la expresión regular a partir del análisis sintáctico de la misma. Como ya se sabe, en un árbol hay 2 tipos de nodos: nodos hojas y nodos que no son hojas que aquí representarán operaciones. En un árbol sintáctico de una expresión regular los nodos hojas representarán un caracter (o símbolo terminal) que aparece en el texto fuente, el resto de los nodos representarán operaciones con ellos. El lenguaje para expresiones regulares con el que trabajaremos es el que se presenta en la sección En la misma se definen 5 operaciones, lo que nos da 6 tipos de nodos posibles a saber: 1. Nodo hoja: se representa con un círculo y dentro del mismo el caracter que hay en esa posición. 19

20 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos 2. Nodo concatenación: se representa con un círculo con un punto adentro. 3. Nodo disyunción: se representa con un círculo con un adentro. 4. Nodo asterisco: se representa con un círculo y un * adentro. 5. Nodo opcional: se representa con un círculo y un? adentro. 6. Nodo uno o más: se representa con un círculo y un + adentro. El árbol puede ser construido trabajando con cualquier técnica de análisis sintáctico que permita construir el árbol sintáctico respetando el orden de examen de los terminales de izquierda a derecha (puede ser LL(k) o LR(k)). Ejemplo: sea la expresión regular la siguiente: ( '+' '-' )? d + El árbol sintáctico sería:.? + d '+' '-' Fig. 3 Arbol sintáctico para ( '+' '-' )? d + A los efectos de poder implementar los algoritmos de generación se trabaja con la expresión regular aumentada "ExpReg #" donde el numeral (#) significa fin de la expresión regular. Al construir el árbol sintáctico para la expresión regular aumentada, el nodo raíz del árbol será un nodo concatenación el cual tendrá como subárbol izquierdo el árbol sintáctico construido y como subárbol derecho la hoja etiquetada con #. Para el ejemplo 20

21 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos dado se tendrá:.. #? + 4 d 3 '+' '-' 1 2 Fig. 4 Arbol sintáctico para ( '+' '-' )? d + # En el árbol sintáctico presentado en la figura 4 se muestran las hojas numeradas de 1 a N. La numeración de las hojas se realizará por el orden de aparición de las mismas en la expresión regular Funciones aplicables a nodos de un árbol sintáctico Definiremos 4 funciones que operan sobre nodos de un árbol sintáctico construido para una expresión regular dada: Anulable, PrimeraPos, UltimaPos y SiguientePos. El sufijo Pos significa posición del árbol. Las funciones devuelven algún valor referente a esa posición del árbol. La función Anulable devuelve como resultado Verdadero o Falso. Es recursiva por definición y se la caracteriza por medio de la siguiente tabla: 21

22 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos Tipo de nodo Anulable(Nodo) Hoja con número i Falso si. sd Anulable(si) y Anulable(sd) si sd Anulable(si) o Anulable(sd) * s Verdadero + s Anulable(s)? s Verdadero Fig. 5 Función Anulable Las funciones PrimeraPos y UltimaPos son también recursivas. Devuelven como resultado un conjunto cuyos elementos son los números de las hojas siguientes a la posición actual. Las funciones quedan caracterizadas mediante la siguiente tabla: 22

23 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos Tipo de nodo PrimeraPos(Nodo) UltimaPos(Nodo) Hoja con número i si. sd { i { i Si Anulable(si) Entonces PrimeraPos(si) U PrimeraPos(sd) Sino PrimeraPos(si) Si Anulable(sd) Entonces UltimaPos(sd) U UltimaPos(si) Sino UltimaPos(sd) si sd PrimeraPos(si) U PrimeraPos(sd) UltimaPos(sd) U UltimaPos(si) * s PrimeraPos(s) UltimaPos(s) + s PrimeraPos(s) UltimaPos(s)? s PrimeraPos(s) UltimaPos(s) Fig. 6 Funciones PrimeraPos y UltimaPos La función SiguientePos se calcula únicamente para los nodos hoja, pero su cálculo requiere el completo recorrido del árbol sintáctico. Se define por las dos reglas siguientes: 1. Si n es un nodo concatenación con subárbol izquierdo si y subárbol derecho sd, e i es una posición dentro de UltimaPos(si), entonces todas las posiciones de PrimeraPos(sd) están en SiguientePos(i). 2. Si n es un nodo asterisco e i es una posición dentro de UltimaPos(n), entonces todas las posiciones de PrimeraPos(n) están en SiguientePos(i). Ejemplo: para el ejemplo dado (ver figura 4) tenemos: Funciones PrimeraPos y UltimaPos: Para el nodo disyunción: PrimeraPos( ) = { 1, 2 UltimaPos( ) = { 1, 2 23

24 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos Para el nodo opcional: PrimeraPos(? ) = { 1, 2 UltimaPos(? ) = { 3 Para el nodo concatenación de más a la izquierda: PrimeraPos(. ) = { 1, 2, 3 UltimaPos(. ) = { 3 Para el nodo concatenación raíz: PrimeraPos(. ) = { 1, 2, 3 UltimaPos(. ) = { 4 Las funciones SiguientePos(i) para cada hoja son: SiguientePos(1) = { 3 SiguientePos(2) = { 3 SiguientePos(3) = { 3, 4 SiguientePos(4) = { 3.3 Construcción de un AFD a partir de una expresión regular Construcción del AFD La construcción de un autómata finito determinístico (AFD) a partir de una expresión regular se realiza mediante el siguiente procedimiento: 1. Construir el árbol sintáctico para la expresión regular aumentada ExpReg #, donde # es un marcador de final que se añade a ExpReg y que difiere de los caracteres que pueden aparecer en ExpReg (no debe estar dentro del conjunto de terminales). 2. Calcular las funciones Anulable, PrimeraPos, UltimaPos y SiguientePos haciendo recorridos en profundidad del árbol. 3. Construir EstadosD (la letra D por Determinístico), el conjunto de estados del AFD, por medio del procedimiento descripto en el punto siguiente. Los estados dentro de EstadosD son conjuntos de posiciones; al principio, cada estado está "no marcado", y un estado se convierte en 24

25 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos "marcado" justo antes de considerar sus transiciones de salida. El estado inicial del AFD es PrimeraPos(raíz), y los estados de aceptación son todos los que contienen la posición asociada con el marcador # Cálculo del conjunto de estados y de la tabla de transiciones El procedimiento de cálculo del conjunto de estados y de la tabla de transiciones del autómata finito determinístico para la expresión regular es el que se muestra en la figura siguiente: Al principio, el único estado no marcado en EstadosD es PrimeraPos(raíz), donde raíz es la raíz del árbol sintáctico construido para ExpReg #. Mientras haya un estado E sin marcar en EstadosD hacer Marcar E Para cada símbolo de entrada a hacer FinPara FinMientras sea U el conjunto de posiciones que están en SiguientePos(p) para alguna posición p en E, tal que el símbolo en la posición p es a. Si U no está vacío y no está en EstadosD entonces Añadir U como estado no marcado a EstadosD. Transición[E, a] = U. Sino Transición[E, a] = no definida Fig. 7 Algoritmo para la construcción del conjunto de estados y la tabla de transiciones para el AFD de la expresión regular Ejemplo de construcción del AFD a partir de la expresión regular Para la expresión regular utilizada en los puntos precedentes tenemos los siguientes pasos: 1. Inicialmente EstadosD = { {1, 2, 3. El único estado del conjunto está sin marcar. 25

26 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos 2. Sea E = { 1, 2, 3 el estado actual, al que luego se le dará el número 0: U '+' = { 3 Transición[ {1, 2, 3, '+' ] = { 3 U '-' = { 3 Transición[ {1, 2, 3, '-' ] = { 3 U d = { 3, 4 Transición[ {1, 2, 3, d ] = { 4, 3 EstadosD = { {1, 2, 30, {3, {3, 4 3. Sea E = { 3 el estado actual, al que luego se le dará el número 1: U '+' = { Transición[ {3, '+' ] = no definido U '-' = { Transición[ {3, '-' ] = no definido U d = { 3, 4 Transición[ {3, d ] = { 3, 4 EstadosD = { {1, 2, 30, {30, {3, 4 4. Sea E = { 3, 4 el estado actual, al que luego se le dará el número 2: U '+' = { Transición[ {3, 4, '+' ] = no definido U '-' = { Transición[ {3, 4, '-' ] = no definido U d = { 3, 4 Transición[ {3, 4, d ] = { 3, 4 EstadosD = { {1, 2, 30, {30, {3, No hay más estados sin marcar, por lo tanto termina la iteración. Los estados (conjunto de posiciones siguientes) fueron numerados de 0 a n-1, donde n es el cardinal del conjunto EstadosD. Hay un solo estado final y es el {3, 4 (al que se le dio el número 2), puesto que es el que contiene la posición que corresponde al #, que fue numerada con 4. El conjunto de estados finales queda así: Estados Finales = { {3, 4 La tabla de transiciones de estados para el autómata finito determinístico que reconoce cadena de caracteres con la estructura " ( '+' '-' )? d +" es la siguiente: Estado '+' '-' d no definido no definido 2 2 no definido no definido 2 La gráfica del autómata finito determinístico para el ejemplo dado es: 26

27 Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos '+' / '-' d d d Fig. 8 Autómata finito determinístico construido para la expresión regular ( '+' '-' )? d Funcionamiento de un autómata finito determinístico El siguiente procedimiento ilustra el funcionamiento de un autómata finito determinístico: EstadoActual = 0; Buscar un caracter de entrada y guardarlo en c Hacer Si c no es FinDeArchivo entonces FinSi FinSi EstadoNuevo = Transición[EstadoActual, c]; Buscar un caracter de entrada y guardarlo en c Si EstadoNuevo es un estado del autómata entonces EstadoActual = EstadoNuevo; Mientras EstadoActual sea igual a EstadoNuevo; Si EstadoActual es un elemento del conjunto de estados finales entonces FinSi La cadena de entrada leída hasta este momento cumple con la expresión regular. Fig. 9 Algoritmo para el funcionamiento de un Autómata Finito Determinístico. 27

28 Algoritmos usados por el Generador de Analizadores Sintácticos Capítulo 4: Algoritmos usados por el Generador de Analizadores Sintácticos 4.1 Introducción En este capítulo se presentan los algoritmos usados por el Generador de Analizadores Sintácticos SLR. Se tratará de clarificar bien los conceptos teóricos utilizados, lo que no hace la bibliografía que se utilizó (luego de la lectura de la bibliografía, en el mejor de los casos quedan muchos conceptos flotando y sin relación alguna, y lo más común es que no se los entienda). Los analizadores sintácticos generados trabajan con la técnica SLR(1) o SLR para simplificar, la que es un caso especial de LR(k). A diferencia de la técnica LR(1) canónica y de la técnica LALR, esta técnica genera tablas mucho más pequeñas (se minimiza el número de estados del autómata de pila). Se la puede aplicar a casi todas las gramáticas de contexto libre que pueda necesitar un programador (el desarrollo de lenguajes de programación con gramáticas complejas puede requerir trabajar con la técnica LALR, pero si la técnica SLR es aplicable entonces el analizador será más eficiente). La técnica LR ha sido descripta por Knuth [1965], pero no resultó práctica porque el tamaño de las tablas que se debían construir eran demasiado grandes (recordar que en ese tiempo 16 kbytes era mucha memoria). En 1969 Korenjak mostró que mediante esa técnica se podían producir analizadores sintácticos de tamaño razonable para las gramáticas de los lenguajes de programación. En 1969 y 1971, DeRemer inventó los métodos "LR simples" (SLR por Simple LR) y "LR con símbolo de anticipación" (LALR por lookahead-lr) que son más simples que los de Korenjak. La utilización de la técnica SLR (y del resto de las LR) simplifica mucho el trabajo del desarrollo de un traductor, siempre y cuando se disponga de un programa generador de las tablas de análisis sintáctico. Si no se dispone de un generador de las tablas, la construcción de las mismas puede llegar a desalentar la construcción del traductor puesto que se requiere un dominio profundo de los algoritmos de construcción y de la teoría en la que se basan. Una persona puede entender bien los algoritmos de construcción de las tablas, y aún así cometer muchos errores. 28

29 Algoritmos usados por el Generador de Analizadores Sintácticos El analizador SLR es uno solo para todas las posibles gramáticas. Es un autómata de pila determinístico, y su funcionamiento es análogo al autómata finito determinístico. El algoritmo se lo ilustra en la sección siguiente. La tabla construida para un analizador SLR se llama tabla SLR, la gramática para la cual se pueda construir una tabla SLR se llamará gramática SLR. Una gramática para la cual la construcción de la tabla SLR genere conflictos no es SLR, aunque se puedan resolver a mano los conflictos. 4.2 Análisis sintáctico por la técnica SLR (LR Simple) Antes de explicar cómo se construyen las tablas es necesario ver primero cómo se las usa Algoritmo de análisis sintáctico SLR Se dispone de una pila en la que se pueden almacenar estados (enteros no negativos) o símbolos de la gramáticas (terminales o no terminales). También se dispone de un par de tablas a las que se les da el nombre de acción e ir_a. La tabla acción contiene las acciones que puede ejecutar el autómata. Las acciones se especifican con una letra que especifica la acción y el número de estado al que debe ir el autómata luego de ejecutarla. Las mismas pueden ser: 1. Desplazamiento: se simboliza con la letra d y un número que indica el estado al que debe pasar el autómata. 2. Reducción: se simboliza con la letra r y un número que indica el número de regla por la cual se reduce. 3. Aceptar: se simboliza con la letra a. No tiene un número de sufijo puesto que no hace falta; el autómata se detiene automáticamente al encontrar esa orden. 4. Nada: si una entrada en esta tabla no contiene alguna de las tres alternativas anteriores entonces en la posición actual hay error de sintaxis. 29

30 Algoritmos usados por el Generador de Analizadores Sintácticos La tabla ir_a contiene números enteros no negativos que son los estados a los que tiene que ir el autómata luego de ejecutar una reducción. La figura siguiente muestra el algoritmo de análisis sintáctico LR simple: Se dispone de una pila vacía en la cual se coloca 0 como estado inicial. Sea pt el puntero al primer terminal de la cadena de entrada. Salir = No Repetir Sea E el estado en el tope de la pila y t el terminal apuntado por pt. Si acción[e, t] = dy entonces Apilar primero t y luego Y. Avanzar pt al siguiente terminal. Sino, si acción[e, t] = rx entonces X es el número de regla por la que hay que reducir, entonces desapilar 2 veces el número de símbolos de la parte derecha de la regla número X. Sea Y el estado que ahora está en el tope de la pila y A el no terminal de la parte derecha de la regla número X. Apilar primero A y luego ir_a[x, A]. Sino, si acción[e, t] = a entonces Sino FinSi Se ha llegado al estado de aceptación. Salir = Si. Aceptar = Si. Salir = Si. Hasta que Salir = Si. Aceptar = No; hay error de sintaxis. Si Aceptar = Si entonces La oración de entrada cumple con la estructura definida por la gramática, esto es, es gramatical. Sino La oración de entrada no es gramatical. 30

31 Algoritmos usados por el Generador de Analizadores Sintácticos Fig. 10 Algoritmo de análisis sintáctico SLR Ejemplo del funcionamiento del analizador sintáctico SLR Para ejemplificar el funcionamiento del analizador sintáctico trabajaremos con la gramática siguiente (las reglas han sido numeradas): 1: P --> D I 2: D --> 'Var' L ':' 'Tipo' ';' 3: D --> 4: L --> L ',' 'Id' 5: L --> 'Id' 6: I --> I 'Instr' ';' 7: I --> Las tablas de análisis sintáctico SLR para la gramática dada son las siguientes: acción(estado, símbolo terminal) ir_a(estado, símbolo no terminal) Est 'Var' ':' 'Tipo' ';' ',' 'Id' 'Instr' FA P D L I d1 r3 r d4 5 2 a 3 r7 r7 6 4 r5 r5 5 d7 d8 6 d9 r1 7 d10 8 d11 9 d12 10 d13 11 r4 r4 12 r6 r6 13 r2 r2 Fig. 11 Tabla de análisis sintáctico SLR para la gramática de un lenguaje simple. En la tabla acción el símbolo FA significa Fin de Archivo. Aunque no forma parte del texto de entrada, su inclusión fue realizada durante la construcción de las tablas; el símbolo Fin de Archivo es un símbolo terminal de la gramática ampliada (ver más adelante qué es una gramática ampliada). Sea el texto de entrada el siguiente: Var Id : Tipo ; Instr ; 31

32 Algoritmos usados por el Generador de Analizadores Sintácticos El análisis sintáctico se ilustra en el cuadro siguiente. Se utiliza un espacio como separador de elementos de la pila. Si se ha llegado al fin de archivo (FA) en la columna Entrada no habrán terminales. Paso Pila Entrada Acción 1 0 Var Id : Tipo ; Instr ; d1 2 0 Var 1 Id : Tipo ; Instr ; d4 3 0 Var 1 Id 4 : Tipo ; Instr ; r5 4 0 Var 1 L : Tipo ; Instr ; Ir a Var 1 L 5 : Tipo ; Instr ; d7 6 0 Var 1 L 5 : 7 Tipo ; Instr ; d Var 1 L 5 : 7 Tipo 10 ; Instr ; d Var 1 L 5 : 7 Tipo 10 ; 13 Instr ; r2 9 0 D Instr ; Ir a D 3 Instr ; r D 3 I Instr ; Ir a D 3 I 6 Instr ; d D 3 I 6 Instr 9 ; d D 3 I 6 Instr 9 ; 12 r D 3 I Ir a D 3 I 6 r P Ir a P 2 Acepta r Fig. 12 Ejemplo de análisis sintáctico con la técnica SLR. La pila del analizador puede quedar con muchos elementos y aún así haber llegado al estado de aceptación Clasificación de los errores que se pueden producir durante el análisis sintáctico El algoritmo de análisis sintáctico SLR presentado en la figura 10 no especifica qué tipos de errores pueden aparecer ni cómo tratarlos. En esta 32

33 Algoritmos usados por el Generador de Analizadores Sintácticos sección se dará una definición de los tipos de errores que pueden aparecer ya que la bibliografía no da una definición rigurosa de los mismos. Error lexicográfico: se dará este nombre al error que se produce al intentar buscar un símbolo terminal siguiente al actual y se encontró un símbolo que no es parte del conjunto de símbolos terminales de la gramática. El Fin de Archivo (FA) no es error lexicográfico, sino que simplemente no hay más símbolos terminales de entrada. Error semántico: si la implementación del analizador sintáctico incluye la posibilidad de ejecutar acciones semánticas (ver sección y ), durante la ejecución de esas acciones se pueden producir errores. A esos errores se les dará el nombre de errores semánticos. Error sintáctico: si no es error lexicográfico ni error semántico entonces es sintáctico. El autómata en este caso se queda sin saber qué acción ejecutar (la entrada de la tabla acción no está definida para el estado actual y el terminal de entrada). Las definiciones precedentes son aplicables a todas las técnicas de análisis que hay esta el momento de la escritura de este trabajo. En el contexto de las técnicas LR(k), un error semántico puede ser ignorado sin que se produzcan efectos indeseados. No ocurre lo mismo con los errores lexicográficos y sintácticos (nadie sabe con certeza lo que quiso escribir el autor del texto fuente), aunque los efectos colaterales pueden reducirse previo estudio y modificación "a mano" de las tablas. 4.3 Fundamentos de la construcción de las tablas de análisis SLR Antes de explicar la construcción de las tablas se darán primero algunas definiciones, se explicarán algoritmos usados por el algoritmo de construcción de las tablas de análisis sintáctico SLR y se explicarán los fundamentos de la construcción de las tablas del analizador SLR Elemento del análisis sintáctico LR(0) Se denominará elemento del análisis sintáctico LR(0) (elemento para abreviar) de una gramática G a una regla de G con un punto en alguna posición de la parte derecha. 33

34 Algoritmos usados por el Generador de Analizadores Sintácticos Una regla que derive en la cadena nula (A --> ) genera un solo elemento que es "A -->. ". Intuitivamente, un elemento indica hasta dónde se ha visto una producción en un momento dado del proceso de análisis sintáctico. Por ejemplo, la regla A --> B C D genera cuatro elementos: A --->. B C D A ---> B. C D A ---> B C. D A ---> B C D. El primer elemento indica que a continuación se espera ver en la entrada una cadena derivable de "B C D". El segundo elemento indica que se acaba de ver en la entrada una cadena derivable de B, y que a continuación se espera ver una cadena derivable de "C D" Operación Cerradura Sea I un conjunto de elementos para una gramática G, Cerradura(I) es el conjunto de elementos construidos a partir de I y de G mediante las dos siguientes reglas: 1. Inicialmente, todo elemento de I se agrega a Cerradura(I). 2. Si A --> α. B β está en Cerradura(I) y B --> γ es una regla, entonces se agrega B -->. γ a Cerradura(I), si todavía no fue agregado. Se aplica esta regla hasta que no se puedan agregar más elementos a Cerradura(I). Intuitivamente, si A --> α. B β está en Cerradura(I) es porque en algún momento del proceso de análisis sintáctico se cree que es posible ver a continuación una cadena derivable de B como entrada. Si B --> γ es una regla, también se espera ver una subcadena derivable de B --> γ en éste punto, y por esta razón se incluye B -->. γ en Cerradura(I). Ejemplo: para la gramática utilizada en el punto 4.2.2, sea I = { (P --> D I) entonces Cerradura(I) contiene los siguientes elementos: P ---> D I D ---> 'Var' L ':' 'Tipo' ';' D ---> 34

35 Algoritmos usados por el Generador de Analizadores Sintácticos Otro ejemplo: para la gramática utilizada en el punto 4.2.2, sea I = { ( D ---> 'Var' L ':' 'Tipo' ';' ), entonces Cerradura(I) contiene los siguientes elementos: D ---> 'Var' L ':' 'Tipo' ';' L ---> L ',' 'Id' L ---> 'Id' Mangos Informalmente, un mango de una cadena es una subcadena que concuerda con el lado derecho de una regla y cuya reducción al no terminal del lado izquierdo de la regla representa un paso a lo largo de la inversa de una derivación por la derecha. Formalmente, un mango de una forma de frase derecha γ es una regla A-->β y una posición de γ donde la cadena β podría encontrarse y sustituirse por A para producir la forma de frase derecha previa en una derivación por la derecha de γ. Es decir, si O ==>* αaω ==>* αβω, entonces A-->β si la posición que sigue de α es un mango de αβω. La cadena ω a la derecha del mango contiene sólo símbolos terminales. Si la gramática no es ambigua entonces toda forma de frase derecha de la gramática tiene exactamente un mango. Para la gramática del ejemplo dado en la sección 4.2.2, obsérvesen la figura 12, la fila correspondiente al paso 14: la forma de frase derecha es D I Instr ; la operación que se va a ejecutar en ese paso es r6 y el mango es "I Instr ;", que concuerda con la parte derecha de la regla 6 ( I --> I 'Instr' ';' ). En este caso, a la derecha del mango no hay símbolos terminales Prefijo Viable Los prefijos de las formas de frase derecha que pueden aparecer en la pila de un analizador sintáctico por desplazamiento y reducción se denominan prefijos viables. Una definición equivalente de un prefijo viable es la de que es un prefijo de una forma de frase derecha que no continúa más allá del extremo derecho del mango situado más a la derecha de esta forma de frase. Con esta 35

36 Algoritmos usados por el Generador de Analizadores Sintácticos definición, siempre es posible añadir símbolos terminales al final de un prefijo viable para obtener una forma de frase derecha. Por lo tanto, aparentemente no hay error siempre que la porción examinada de la entrada hasta un punto dado pueda reducirse a un prefijo viable. Por ejemplo, para la gramática del ejemplo dado en la sección 4.2.2, obsérvese en la figura 12, la fila correspondiente al paso 8, la forma de frase derecha es: Var L : Tipo ; Instr ; y se reduce por regla 2 el mango "Var L : Tipo ;" para obtener la forma de frase derecha siguiente: D Instr ; Aquí, D es un prefijo viable Operación ir_a La función ir_a(i, X) donde I es un conjunto de elementos y X es un símbolo de la gramática (terminal o no terminal) se define como la cerradura del conjunto de todos los elementos A --> α X. β tales que A --> α. X β esté en I. Intuitivamente, si I es el conjunto de elementos válidos para algún prefijo viable, entonces ir_a(i, X) es el conjunto de elementos válidos para el prefijo viable γx. Por ejemplo, para la gramática del ejemplo dado en la sección 4.2.2, para el conjunto de elementos I igual a: P ---> D I D ---> 'Var' L ':' 'Tipo' ';' D ---> entonces ir_a(i, D) consta de: P ---> D I I ---> I 'Instr' ';' I ---> 36

37 Algoritmos usados por el Generador de Analizadores Sintácticos Colección Canónica de Conjuntos de Elementos del Análisis Sintáctico LR(0) La construcción de la colección canónica de conjunto de elementos LR(0) se aplica a una gramática aumentada G' obtenida a partir de G por el agregado de la siguiente regla: O' --> O donde O es el símbolo inicial de la gramática G. Ahora O' es el símbolo inicial de la gramática G', a la que se llamará gramática aumentada. El objetivo de esta regla es la de indicar al analizador cuándo debe detener el análisis sintáctico y anunciar la aceptación de la cadena. La aceptación se produce únicamente cuando el analizador está por reducir O' --> O. El algoritmo se muestra en la siguiente figura, en donde C es conjunto de conjuntos de elementos (la colección canónica LR(0) ): Sea C = { Cerradura( { O' -->. O ) Repetir Para cada conjunto de elementos I en C y cada símbolo gramatical X tal que ir_a(i, X) no esté vacío y no esté en C hacer FinPara Agregar ir_a(i, X) a C. Hasta que no se puedan agregar más conjuntos de elementos a C. Fig. 13 Construcción de la Colección Canónica LR(0) En adelante, a cada I se lo llamará indistintamente conjunto de elementos LR(0) o estado. Ejemplo: para la gramática dada en la sección 4.2.2, la gramática aumentada es: P' --> P P ---> D I D ---> 'Var' L ':' 'Tipo' ';' D ---> L ---> L ',' 'Id' L ---> 'Id' I ---> I 'Instr' ';' I ---> 37

38 Algoritmos usados por el Generador de Analizadores Sintácticos y la colección canónica es: Estado 0 (I 0 ): P' ---> P P ---> D I D ---> 'Var' L ':' 'Tipo' ';' D ---> Estado 1 (I 1 ): D ---> 'Var' L ':' 'Tipo' ';' L ---> L ',' 'Id' L ---> 'Id' Estado 2 (I 2 ): P' ---> P Estado 3: P ---> D I I ---> I 'Instr' ';' I ---> Estado 4: L ---> 'Id' Estado 5: D ---> 'Var' L ':' 'Tipo' ';' L ---> L ',' 'Id' Estado 6: P ---> D I I ---> I 'Instr' ';' Estado 7: D ---> 'Var' L ':' 'Tipo' ';' Estado 8: L ---> L ',' 'Id' Estado 9: I ---> I 'Instr' ';' Estado 10: D ---> 'Var' L ':' 'Tipo' ';' Estado 11: L ---> L ',' 'Id' Estado 12: I ---> I 'Instr' ';' Estado 13: D ---> 'Var' L ':' 'Tipo' ';' Idea Central del Método SLR Los elementos del análisis sintáctico LR(0) pueden considerarse como los estados del autómata finito no determinista que reconoce los prefijos viables. Para poder convertir el autómata en determinista hace falta agrupar los 38

39 Algoritmos usados por el Generador de Analizadores Sintácticos elementos en conjuntos. Los conjuntos serían entonces los estados del autómata finito determinista que reconoce los prefijos viables. La idea central del método SLR es construir primero a partir de la gramática un AFD que reconozca los prefijos viables. Se construye la colección canónica de elementos LR(0). Cada conjunto de elementos dentro de la colección canónica pasan a ser los estados del analizador sintáctico SLR (del AFD que el mismo usa). Para el ejemplo dado en el punto anterior, el AFD que reconoce prefijos viables se muestra en la figura siguiente: 'Id' 4 'Var' 1 L ':' 7 'Tipo' 10 ';' 13 0 P D 2 5 ',' 8 'Id' 11 3 I 6 'Instr' ';' 9 12 Fig. 14 AFD que reconoce prefijos viables de la gramática del ejemplo. El AFD de la figura precedente tiene dos problemas: no tiene estados finales y tiene transiciones con símbolos no terminales. El hecho de tener transiciones con no terminales hace necesario disponer de una pila para la implementación del autómata. El autómata resultante se pasa a llamar autómata de pila, que como ya se mencionó en el punto 2.2.5, son los que implementan analizadores sintácticos para gramáticas del Tipo 2 de la clasificación de Chomsky Función Anulable La función Anulable se aplica a un símbolo no terminal de una gramática y devuelve Verdadero si ese símbolo es anulable. Un símbolo no terminal A es anulable si de alguna forma, mediante derivaciones sucesivas con las reglas de la gramática, se transforma A en la 39

40 Algoritmos usados por el Generador de Analizadores Sintácticos cadena nula ( A ==>* λ ). (λ representa a la cadena nula. En este trabajo cuando en una regla se quiere denotar la presencia de la cadena nula no se pone nada puesto que es más claro.) Definiremos primero una versión simple de Anulable a la que se le llamará AnulableSimple. Esta función devolverá Verdadero si hay una regla que derive el no terminal directamente en la cadena nula ( A --> ). A partir de la función de AnulableSimple se define la función Anulable de la siguiente manera: un símbolo no terminal X de una gramática G es anulable si: 1. X es Anulable Simple ( X --> ). 2. Si existe alguna regla X --> Y Z, con Y y Z anulables. El punto 2 hace que la definición sea recursiva, por lo tanto un algoritmo que la implemente podrá ser recursivo, lo que no es aconsejable debido a la cantidad de veces que se puede repetir el cálculo de Anulable para algunos símbolos no terminales. Ejemplo: para la gramática siguiente O ---> A B C A ---> a B A ---> B ---> b C B ---> C C ---> c O C ---> El cálculo de AnulableSimple para cada uno de los símbolos daría el siguiente resultado: O: no es anulable simple. A: si es anulable simple. B: no es anulable simple. C: si es anulable simple. El cálculo de Anulable arroja el siguiente resultado: O: si es anulable porque A, B y C son anulables. A: si es anulable porque es anulable simple. B: si es anulable porque hay una regla B --> C, y C es anulable. C: si es anulable porque es anulable simple La operación Primero Si α es una cadena de símbolos gramaticales, se considera Primero(α) como el conjunto de terminales que inician las cadenas derivadas de α. 40

41 Algoritmos usados por el Generador de Analizadores Sintácticos Si α ==>* λ, entonces λ también está en Primero(α). El resultado de la función Primero es un conjunto de símbolos terminales, que obviamente es un subconjunto de T (el conjunto de símbolos terminales de la gramática). Para calcular Primero(X) para todos los símbolos gramaticales X, se proponen las siguientes reglas: 1. Si X es terminal, entonces Primero(X) es { X. 2. Si X -- > λ es una regla, entonces agregar λ a Primero(X). 3. Si X es no terminal y X --> Y 1 Y 2... Y k entonces se agrega b a Primero(X) si, para alguna i, b está en Primero(Y i ) y λ está en todos los conjuntos Primero(Y 1 ),..., Primero(Y i - 1 ); esto es, la cadena Y 1...Y i - 1 ==>* λ. Si λ está en Primero(Y j ) para toda j = 1, 2,..., k, entonces se agrega λ a Primero(X); esto es, la cadena Y 1...Y k ==>* λ. Por ejemplo, todo lo que está en Primero(Y 1 ) sin duda estará en Primero(X). Si Y 1 no deriva en λ, entonces no se agrega nada más a Primero(X), pero si Y 1 ==>* λ, entonces se agrega Primero(Y 2 ), y así sucesivamente. 4. Se repetirán las tres primeras reglas hasta que no se puedan agregar Fig. 15 Cálculo de Primero(X). Observando la regla 1, se concluye que el cálculo de Primero(X) para X terminal no tiene sentido, puesto que es un axioma. A los efectos de la utilización de la función Primero en la generación de las tablas de análisis sintáctico SLR, el agregado de λ no es necesario. En los ejemplos siguientes no se incluye λ en donde se debiera. Ejemplo 1: para la gramática dada en la sección anterior (4.3.8) los conjuntos Primero sin Lambda (λ) son: Primero(O) = { a b c Primero(A) = { a Primero(B) = { b c Primero(C) = { c 41

42 Algoritmos usados por el Generador de Analizadores Sintácticos Ejemplo 2: para la gramática dada en la sección (4.2.2) los conjuntos Primero sin Lambda (λ) son: Primero(P) = { 'Var' 'Instr' Primero(D) = { 'Var' Primero(L) = { 'Id' Primero(I) = { 'Instr' La operación Siguiente La función Siguiente se calcula para los símbolos no terminales de la gramática, únicamente. Para calcular Siguiente(X) para todos los símbolos no terminales X, se proponen las siguientes reglas: 1. Agregar FA (Fin de Archivo) a Siguiente(O), donde O es el símbolo inicial y FA es el delimitador derecho de la entrada. 2. Si hay una regla A ---> α B β, entonces todo lo que esté en Primero(β) excepto λ se agrega a Siguiente(B). 3. Si hay una regla A ---> α B o una regla A ---> α B β, donde Primero(β) contenga λ (es decir, β ==>* λ), entonces todo lo que esté en Siguiente(A) se agrega a Siguiente(B). Fig. 16 Cálculo de Siguiente(X). Obsérvese las reglas 2 y 3, al decir Primero(β) se está hablando en realidad de la unión de varios conjuntos Primero(X i ) para i = 1,..., k y todos los Primero(X i ) excepto Primero(X k ) contiene a λ. Por lo tanto, se deberá tener esto en cuenta al implementar el algoritmo. Obsérvese también que en la regla 2, al agregar todo lo que esté en Primero(β) excepto λ a Siguiente(B) justifica el hecho de que en este trabajo se calcula Primero(X) sin λ. Ejemplo 1: para la gramática dada en la sección anterior (4.3.8) los conjuntos Siguiente(X) son los siguientes: Siguiente(O) = { c b Siguiente(A) = { b c 42

43 Algoritmos usados por el Generador de Analizadores Sintácticos Siguiente(B) = { c b Siguiente(C) = { c b Ejemplo 2: para la gramática dada en la sección (4.2.2) los conjuntos Siguiente(X) son: Siguiente(P) = { Siguiente(D) = { 'Instr' Siguiente(L) = { ':' ',' Siguiente(I) = { 'Instr' 4.4 Construcción de las tablas de análisis SLR La construcción de las tablas de análisis sintáctico SLR (similar a la mostrada en la figura 11 de la sección 4.2.2) se resume en el algoritmo mostrado en la figura siguiente. Si los 3 primeros pasos generan acciones contradictorias, se dice que la gramática no es SLR(1) porque el algoritmo no consigue producir las tablas para el análisis sintáctico. En este caso no se ejecutan los pasos siguientes. 43

44 Algoritmos usados por el Generador de Analizadores Sintácticos 1. Construir G', la gramática aumentada, a partir de G, por el agregado de la regla O' --> O, donde O era el símbolo inicial de G. O' pasa a ser el símbolo inicial en G'. 2. Construir C = { I 0, I 1,..., I n, la colección canónica de conjuntos de elementos del análisis sintáctico LR(0) para la gramática aumentada G'. 3. El estado i se construye a partir de I i. Las acciones de análisis sintáctico para el estado i se determinan como sigue: a. Si A --> α. x β está en I i con x terminal e ir_a(i i, x) = I j, entonces asignar "desplazar j" a accion[i, x]. b. Si A --> α. está en I i, con A distinto de O' entonces asignar "reducir por A --> α" a accion[i, x] para todo x perteneciente a Siguiente(A). c. Si O' ---> O. está en I i, entonces asignar "aceptar" a accion[i, FA] (FA es el Fin de Archivo). 4. La tabla ir_a(i, X) para el estado i y el no terminal X se construyen utilizando la regla: si ir_a[i i, X] = I j entonces ir_a[i, X] = j 5. Todas las entradas de las tablas no definidas por las reglas 3 y 4 son consideradas "error". 6. El estado inicial del analizador es el que se construye a partir del conjunto de elementos que contiene a S' --->. S Fig. 17 Construcción de las tablas de análisis sintáctico SLR. Ejemplo 1: para la gramática dada en la sección 4.2.2, las tablas se muestran en la figura 12 que se encuentra en la misma sección. La colección canónica de elementos LR(0) los encontrará en la sección Los conjuntos Siguiente(X) los encontrará en la sección Ejemplo 2: para la gramática dada en la sección 4.3.8, los conjuntos Siguiente(X) los encontrará en la sección La colección canónica de 44

45 Algoritmos usados por el Generador de Analizadores Sintácticos elementos LR(0) es la siguiente (aquí a los terminales se los encierra entre comillas simples para que no queden dudas): Estado 0: O' ---> O O ---> A B C A ---> 'a' B A ---> Estado 1: A ---> 'a' B B ---> 'b' C B ---> C C ---> 'c' O C ---> Estado 2: O' ---> O Estado 3: O ---> A B C B ---> 'b' C B ---> C C ---> 'c' O C ---> Estado 4: B ---> 'b' C C ---> 'c' O C ---> Estado 5: C ---> 'c' O O ---> A B C A ---> 'a' B A ---> Estado 6: A ---> 'a' B Estado 7: B ---> C Estado 8: O ---> A B C C ---> 'c' O C ---> Estado 9: B ---> 'b' C Estado 10: C ---> 'c' O Estado 11: O ---> A B C Al construir la tabla acción obtenemos lo siguiente, en la que se detectará 6 conflictos de desplazamiento-reducción: acción(e, x) 'a' 'b' 'c' FA ir_a(e, X): O A B C 45

46 Algoritmos usados por el Generador de Analizadores Sintácticos 0 d1 r3 r3 r d4 d5 r a 3 d4 d5 r r7 d5 r7 9 5 d1 r3 r3 r r2 r2 r2 7 r5 r5 r5 8 r7 d5 r r4 r4 r4 10 r6 r6 r6 11 r1 r1 r1 Los conflictos los generó el paso 3.b del algoritmo y son los siguientes: 1. En el estado 1, con el terminal 'c' hay que elegir entre d5 o r7. 2. En el estado 1, con el terminal 'b' hay que elegir entre d4 o r7. 3. En el estado 3, con el terminal 'c' hay que elegir entre d5 o r7. 4. En el estado 3, con el terminal 'b' hay que elegir entre d4 o r7. 5. En el estado 4, con el terminal 'c' hay que elegir entre d5 o r7. 6. En el estado 8, con el terminal 'c' hay que elegir entre d5 o r7. Debido a que esta gramática genera conflictos en la construcción de las tablas SLR se dirá que la misma no es SLR. Una definición equivalente es decir que la gramática es ambigua para la técnica de análisis SLR, lo que no significa que sea ambigua a secas (la construcción unívoca de un árbol de análisis sintáctico no es posible con la técnica SLR, esto es, el algoritmo de construcción de las tablas se comporta en este caso como una función no inyectiva porque puede generar varias tablas accion alternativas para la misma gramática). Más ejemplos de construcción de tablas las puede encontrar en el capítulo 11 y 12 en donde se muestran ejemplos de ejecución de SLR1. 46

47 El Generador de Analizadores Lexicográficos Capítulo 5: El Generador de Analizadores Lexicográficos Previo a la lectura de este capítulo se aconseja leer la sección donde se explica qué es un analizador lexicográfico (es muy importante que lo lea para que no se confundan conceptos). En este capítulo, al lenguaje reconocido por el analizador lexicográfico se lo denominará lenguaje léxico. A las oraciones del lenguaje léxico se los denominará símbolos. A los símbolos terminales de la gramática subyacente (la que explica la estructura del lenguaje léxico) se los denominará terminales léxicos, los que usualmente son caracteres ASCII según la especificación OEM (para DOS y OS/2) o ANSI (para Windows). La definición y el funcionamiento de un analizador lexicográfico se explica en el punto 5.1, y la construcción de un analizador lexicográfico en Funcionamiento propuesto de un Analizador Lexicográfico En esta sección se propondrá una definición de analizador lexicográfico y un algoritmo para el funcionamiento del mismo Reconocedor de símbolo Se denominará reconocedor de símbolo al objeto que describe cómo se debe realizar la búsqueda de un símbolo de un lenguaje dado. El lenguaje dado puede ser expresado por una expresión regular, y en este caso se usaría un autómata finito determinístico para reconocer el símbolo. La otra forma posible es la de usar una simple comparación literal del símbolo, como la que se usaría para reconocer palabras claves u operadores de un lenguaje (una simple comparación de cadena). Entonces, el objeto reconocedor podrá ser un autómata finito determinístico o una cadena de terminales léxicos. 47

48 El Generador de Analizadores Lexicográficos Componentes de un Analizador Lexicográfico Cualquier analizador lexicográfico construido usando esta teoría constaría de 2 partes: 1. Un conjunto de reconocedores de símbolos. 2. Un algoritmo que especifique cómo reconocer un símbolo de entrada que incluya reglas para la resolución de posibles conflictos. Se produce un conflicto cuando 2 o más reconocedores de símbolos reconocen a una cadena de terminales del lenguaje léxico que comienza a partir de la posición actual. El lenguaje reconocido por el analizador lexicográfico es la unión de los lenguajes reconocidos por cada reconocedor de símbolos, resolviendo conflictos en donde haya intersección de acuerdo a ciertas reglas dadas de antemano (ver sección siguiente) Algoritmo de Examinación de un Analizador Lexicográfico Para examinar la cadena de terminales léxicos se propone el siguiente algoritmo: 1. Recorrer el conjunto de reconocedores de símbolos para ver si alguno de ellos reconoce una cadena de terminales léxicos a partir de la posición actual. Para cada reconocedor, si es autómata finito determinístico entonces usar el algoritmo estándar de funcionamiento, y si es cadena usar el algoritmo estándar de comparación de cadenas. 2. Si al ejecutar el paso 1 se determina que hay 2 o más reconocedores de símbolos que reconocen una cadena de terminales léxicos a partir de la posición actual, el conflicto se resuelve por medio de las siguientes reglas: a. Si hay un reconocedor de símbolo que reconoce una secuencia de terminales léxicos más larga que el resto de los reconocedores, entonces se elige ése. Si hay empate entre dos o más reconocedores se procede con las reglas siguientes. b. Si el grupo de reconocedores para los que se produjo el empate son AFDs (posiblemente construidos a partir de expresiones regulares), se 48

49 El Generador de Analizadores Lexicográficos elige el que se encontró primero en el conjunto de reconocedores de símbolos. c. Si en el grupo hay una cadena de terminales léxicos se elige éste. Si hay varias se elige la primera que se encontró en el conjunto de reconocedores de símbolos. 5.2 Construcción de un Analizador Lexicográfico La construcción de un analizador lexicográfico se reduce entonces a la construcción del conjunto de reconocedores de símbolos y a la implementación de un algoritmo que utilice ese conjunto y que tenga el comportamiento definido en Si un símbolo está definido por una expresión regular entonces se propone utilizar el algoritmo de construcción de un AFD a partir de una expresión regular dado en la sección 3.3 (utilícese un generador) y agregar el AFD resultante al conjunto de reconocedores de símbolos. En cambio, si el símbolo es una cadena literal, simplemente se la incluirá dentro del conjunto de reconocedores de símbolos. El algoritmo que utilice el conjunto de reconocedores de símbolos se escribirá y depurará una sola vez. Lo que cambiará para cada analizador lexicográfico será el conjunto de reconocedores de símbolos. De esta manera, la complejidad de la construcción de un analizador lexicográfico se reduce a la complejidad de la construcción de un AFD a partir de una expresión regular. En este trabajo se separa la generación del analizador lexicográfico de la generación de AFDs a partir de expresiones regulares. De esta manera, si es necesario se puede construir manualmente el AFD para algún reconocedor de símbolo. 49

50 Parte 2. Implementación de los Generadores 50

51 Implementación del Generador de Autómatas Finitos Determinísticos Capítulo 6: Implementación del Generador de Autómatas Finitos Determinísticos A los efectos de que el generador de autómatas finitos determinísticos pueda ser incluido en programas del usuario, se creará una biblioteca C++ que ofrecerá todas las funciones necesarias. La biblioteca recibió el nombre de ExpReg, por Expresión Regular. El generador de autómatas finitos determinísticos debe ser visto como un programa de algún usuario que hace uso de la biblioteca de soporte ExpReg. El generador recibió el nombre de AFD por Autómata Finito Determinístico. La E/S se realizó utilizando la biblioteca BCE, cuyo autor es el mismo de este trabajo. El listado de los códigos fuentes los encontrará en la parte 5. Más documentación de la implementación la puede encontrar en el capítulo 10 y 13 en donde se da una guía del usuario para AFD y ExpReg, respectivamente. Cuando se quiera decir la función Func de la clase Clase se resumirá diciendo Clase::Func, que en el lenguaje C++ significa eso. 6.1 Estructuras de Datos Las estructuras de datos de la implementación se hallan repartidas en 2 archivos cabeceras (.H) a saber: 1. En expreg.h se encuentra toda la interfase visible por el usuario de ExpReg. 2. En genafd.h están las estructuras de datos usadas para la generación del AFD a partir de una expresión regular de entrada. 3. En lse.h (de la Biblioteca BCE) está la implementación de Listas Simples Encadenadas (para cualquier tipo de dato como elemento de la lista), la que se implementa mediante templates. 51

52 Implementación del Generador de Autómatas Finitos Determinísticos Estructura de un Arbol Sintáctico para una Expresión Regular El árbol sintáctico para una expresión regular ha sido implementado por medio de la clase NodoArbol que se encuentra declarado en genafd.h. Los campos dato de la clase son: Tipo: indica el tipo de nodo. Para las hojas, este campo tiene el número de hoja (no puede haber 2 hojas con el mismo número), valor que est entre 0 y N_HOJA ambos inclusive. IdCar: identificador del caracter, es un índice para acceder a un vector donde está la descripción de la expresión monocaracter. PrimUltCalc: tiene 0 si PrimeraPos (Prim) y UltimaPos (Ult) no han sido calculados, distinto a 0 si ya se calcularon. pizq: subárbol de la izquierda del actual. Para los nodos '*', '+' y '?' el subárbol se engancha en este campo. Para las hojas igual a 0. pder: subárbol de la derecha del actual. Se usa solamente para nodos del tipo '.' y ' '. Para los nodos '*', '+', '?' y hojas vale 0. Prim y Ult: son listas de identificadores de las hojas (valor del campo Tipo de las mismas) que son PrimeraPos y UltimaPos respectivamente, del nodo actual. Las operaciones PrimeraPos, UltimaPos y SiguientePos quedan a cargo de funciones miembro de esta clase Estructura de un Estado del AFD durante su Construcción La estructura de un estado del AFD durante su construcción se implementa con la clase Estado que se encuentra declarada en genafd.h. Tiene los siguientes campos: Id: número de estado (0 a n - 1 si hay n estados) Trans: es un vector en el que se almacenan los estados siguientes. Es una fila de la tabla de transición de estados del AFD en construcción, la que se indexa por columna con caracteres. Tiene tantos elementos como caracteres distintos aparecen en el texto fuente. 52

53 Implementación del Generador de Autómatas Finitos Determinísticos Pos: es una lista de las hojas del árbol sintáctico que se agrupan en este estado Estructura de un Autómata Finito Determinístico Los autómatas finitos determinísticos se implementan con la clase AutomFinDet, la que se documenta ampliamente en la sección 10.5 y en la sección Estructura de las Tablas usadas para implementar un Autómata Finito Determinístico Puesto que se prevé la construcción a mano de un autómata finito determinístico, la documentación en detalle de las tablas se incluye en las secciones 10.5 y Estructura de una Expresión Regular Una expresión regular se implementa con la clase ExpReg, que agrupa y todas las funciones necesarias para trabajar con expresiones regulares, encapsulando la complejidad inherente a la construcción de un AFD a partir de una expresión regular. Se documenta en detalle en la sección Estructura de un Reconocedor de Símbolos Los reconocedores de símbolos se implementan con la clase RecSimb, la que se documenta en detalle en la sección Estructura de un Analizador Lexicográfico Un analizador lexicográfico es implementado con la clase AnaLex, que se documenta en detalle en la sección Construcción del Arbol Sintáctico para una Expresión Regular La construcción de un árbol sintáctico para una expresión regular se implementó usando la técnica de análisis sintáctico SLR. La versión actual utiliza tablas generadas con SLR1 versión 2 y la versión 2 de la biblioteca de 53

54 Implementación del Generador de Autómatas Finitos Determinísticos soporte de SLR1. La primera implementación usaba tablas generadas por SLR1 versión 1, con un analizador lexicográfico hecho a mano. Una vez obtenido una versión del generador de AFDs se implementó otro con la clase AnaLex de la biblioteca ExpReg versión 1; hasta el momento de la presentación de este trabajo, el analizador lexicográfico continúa siendo el mismo. El lenguaje de las expresiones regulares se documenta en el capítulo 10. La función de acciones semánticas que son las que actualmente construyen el árbol se llama AccSem, es conocida solamente en el módulo narbol01.cmm. 6.3 Cálculo de Anulable, PrimeraPos, UltimaPos y SiguientePos El cálculo de las funciones PrimeraPos, UltimaPos y SiguientePos quedan a cargo de la función NodoArbol::CalcPrimUltSig. La función NodoArbol::Anulable realiza el cálculo de la función Anulable. La función NodoArbol::CalcPrimUltSig ha sido definida en forma recursiva siguiendo las siguientes reglas: 1. Si para un nodo dado PrimeraPos, UltimaPos y SiguientePos ya han sido calculados entonces no se hace nada. 2. Para cualquier tipo de nodo, primero se calcula PrimeraPos, UltimaPos y SiguientePos de los subárboles que pudiera tener. Luego se procede según las reglas dadas en la sección y en la figura 6. La función NodoArbol::Anulable es también recursiva, y procede de acuerdo a lo especificado por la figura Construcción de la tabla de transición de estados del AFD La función ExpReg::Reasignar es la que construye un AFD a partir de una expresión regular de entrada, la que se da en formato texto. Primero se arma el árbol sintáctico, luego se calcula PrimeraPos, UltimaPos y SiguientePos para los nodos, y finalmente se procede a calcular 54

55 Implementación del Generador de Autómatas Finitos Determinísticos las transiciones mediante el procedimiento dado en la figura 7, en la sección Funcionamiento de un AFD El funcionamiento de un AFD se implementa con la función ExpReg::Examinar, que se documenta en la sección con mayor detalle. 6.6 Funcionamiento de un analizador lexicográfico El funcionamiento de un analizador lexicográfico se encuentra encapsulado en la función AnaLex::Examinar, que se documenta en la sección con más detalle. 6.7 Funcionamiento del Generador de Autómatas Finitos Determinísticos La implementación de un generador de autómatas finitos determinísticos se reduce sólo al uso de las rutinas de la biblioteca ExpReg. Es trabajo del usuario el obtener la expresión regular en formato texto en memoria. La implementación usada para AFD v 3.x se resume con los siguientes pasos: 1. Primero se debe obtener la expresión regular en memoria, de cualquier fuente (archivo, pantalla, etc.). 2. Mediante un objeto ExpReg se llama a ExpReg::Reasignar mandando como parámetro el texto con la expresión regular de entrada. Si esto no da error, entonces se prosigue con los pasos siguientes. Sino, había un error en la expresión regular de entrada. 3. El fuente C++ del AFD (definición del objeto AutomFinDet) se realiza llamando a la función ExpReg::ImprFuente, que recibe como parámetro el nombre del archivo donde se debe imprimir y las cadenas postfijos que se deben usar (ver sección para más detalle). 4. Una versión en formato texto del AFD se imprime llamando a la función ExpReg::Imprimir pasando como parámetro el archivo donde se deberá imprimir. 55

56 Implementación del Generador de Autómatas Finitos Determinísticos Los 4 pasos precedentes pueden ser fácilmente implementados en una sola función y en cualquier sistema operativo para el que se disponga de un compilador de C++. El objetivo de la reusabilidad fácil del generador de AFD se ha cumplido. Para la implementación, se puede seguir la siguiente, la cual es independiente del sistema operativo: // Sea cad una cadena Ascii terminada por un 0 (una cadena C++) Ascii0 cad; // Paso 1: aquí cargar cad con el texto con la expresión regular // Paso 2: probar generar el AFD a partir del texto fuente: ExpReg er; Ascii0 CadPosErr; if (! er.reasignar(cad, CadPosErr)) return ERROR; // Paso 3: guardar fuente C++ if (! er.imprfuente(nomarchcmm, 0, 0)) return ERROR; // Paso 4: guardar archivo de texto con autómata ArchivoSO arch; if (arch.abrir(nomarchtxt, MA_E, MD_TXT)) return ERROR; er.imprimir(arch); arch.cerrar(); La implementación que se realizó para AFD v 3.x está basada en el código precedente, y se la puede ver en las secciones 22.1 y 22.2, donde se muestra el código fuente. 56

57 Capítulo 7: Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos A los efectos de que el generador de analizadores sintácticos SLR pueda ser incluido en programas del usuario, se creará una biblioteca C++ que ofrecerá todas las funciones necesarias. La biblioteca recibió el nombre de Biblioteca de Soporte del Generador de Analizadores Sintácticos SLR. El generador debe ser visto como un programa de algún usuario que hace uso de la biblioteca de soporte. El generador recibió el nombre de SLR1 por SLR(1). La E/S se realizó utilizando la biblioteca BCE. Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos El listado de los códigos fuentes los encontrará en la parte 5. Más documentación de la implementación la puede encontrar en el capítulo 11 y 12 en donde se da una guía del usuario para la biblioteca de soporte y SLR1. Este capítulo documenta solamente la implementación usada en la versión 2.x del generador de analizadores sintácticos. La implementación usada por la versión 1 se documenta brevemente en el capítulo que presenta la guía del usuario del mismo. 7.1 Estructuras de Datos Las declaraciones de las estructuras de datos se hallan repartidos en varios módulos cabeceras a saber: 1. En gramlc.h se encuentran declaradas todas las clases y funciones para trabajar con gramáticas libre de contexto. 2. En genslr.h se encuentran declaradas todas las clases y funciones utilizadas por el generador de analizadores sintácticos SLR. 3. En slr1.h se encuentran declaradas todas las clases y funciones para trabajar con analizadores sintácticos SLR(1). 57

58 7.1.1 Estructura de un Símbolo de una Gramática Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos La estructura de un símbolo de una gramática se implementa con la clase Simbolo. La clase tiene los siguientes campos datos: Cad: es un puntero a una cadena Ascii0 asignada dinámicamente que tiene la versión texto del símbolo, exactamente igual a como lo especificó el usuario. Codigo: es el código del símbolo. Se usa un entero de 15 bits (obsérvese que es un campo tipo bit), por lo que habrá a lo sumo símbolos posibles. NoTerminal: este campo es de 1 bit; contiene 0 si éste símbolo es terminal y 1 si es no terminal. Un símbolo de una gramática puede ser unívocamente identificado usando los campos Codigo y NoTerminal. A la combinación de estos dos campos se lo denominará código compuesto del símbolo Estructura de un Conjunto de Símbolos Un conjunto de símbolos es la base para la definición de la estructura de una regla y de un alfabeto. La clase ConjSimb define la interfase mínima que debe tener un clase que implemente un conjunto de símbolos e implementa las operaciones fundamentales aplicables a un conjunto de símbolos. Esta clase es virtual pura. La clase ConjSimbLSE implementa un conjunto de símbolos usando una lista simple encadenada de símbolos. Se eligió lista simple encadenada debido a que no se sabe de antemano el número de símbolos que tendrá el conjunto, y de esta forma se ahorra memoria. Por otra parte, como siempre habrán pocos símbolos (en promedio 4 o 5), la pérdida de tiempo por usar búsqueda secuencial es mínima. La clase ConjSimbLSE tiene 2 campos datos: lse: es la lista simple encadenada de símbolos. Se usa la instancia LSE<Simbolo> de la familia de clases LSE<X> definida como template en lse.h. El archivo lse.h pertenece a la Biblioteca BCE cuyo autor es el mismo de este trabajo. pult:es el puntero al último elemento de la LSE de símbolos. Se mantiene el puntero al último debido a que siempre se agregan elementos al final de la lista 58

59 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos para mantener el orden de aparición de los mismos, y de esta manera se reduce al mínimo el agregado de un elemento a la lista. La función ConjSimbLSE::AgregarSinCad agrega un símbolo al conjunto pero sin asignar el campo Cad del mismo, esto es, sólo se realiza una copia de su código compuesto Estructura de una Regla de una Gramática Libre de Contexto La estructura de una regla de una gramática libre de contexto se implementa con la clase ReglaLC, la que deriva de ConjSimbLSE. Se agrega un solo campo dato a esta clase: AccSem: contiene un texto con el código de las acciones semánticas a ejecutar cuando se reduzca por esta regla. Puede o no ser usado. En ConjSimbLSE por defecto no se acepta redundancia de símbolos. Se denominará redundancia de símbolo a la repetición de los mismos dentro de un mismo conjunto. En una ReglaLC si se acepta redundancia, y es a los efectos de contemplar la posibilidad de que un símbolo pueda aparecer varias veces en una regla. La flecha (o símbolo separador) no se incluye. El primer elemento de la lista es el no terminal de la izquierda de la regla. El segundo elemento es el primer símbolo de la parte derecha de la regla. El tercer elemento es el segundo símbolo de la parte derecha de la regla; y así sucesivamente. Si la regla es de la forma "A --> " ( A --> λ ) entonces la lista tendrá solamente un elemento Estructura de un Alfabeto La estructura de un alfabeto se implementa con la clase Alfabeto. La misma deriva de la clase ConjSimbLSE, y no agrega nada a la misma Estructura de un Descriptor de un Terminal La estructura de un descriptor de un terminal se implementa con la clase DescrTerminal. Los descriptores de terminales sirven para dar soporte a las sentencias #IGNORAR y #TERMINAL de la especificación 2.0 de la metagramática 59

60 usada por la función Gramatica::Reasignar para la carga de una gramática en memoria (consulte la sección 12.3 donde se documenta la metagramática). Los campos datos de esta clase son: Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos Cadena: es el identificador que aparece en las directivas IGNORAR y TERMINAL. Si la cadena aquí almacenada no es elemento del conjunto de símbolos terminales entonces la directiva usada fue IGNORAR. NomArch: es el nombre del archivo donde se encuentra la expresión regular que define la estructura de éste terminal. Si aquí hay una cadena nula entonces se usará Cadena como nombre de archivo Estructura de un Descriptor de Acción Semántica Un descriptor de acción semántica se usa para el caso de que se haya usado la directiva ACCSEM. Al encontrar la directiva ACCSEM se construye un descriptor de acción semántica que contiene: Ident: el identificador usado en la directiva. TxtAccSem: el texto correspondiente al bloque de código especificado en la directiva. En donde se encuentre Ident en el texto de la gramática fuente se deberá entender que se hace referencia a éste bloque de código Estructura de una Gramática Libre de Contexto Una gramática libre de contexto se implementa con la clase Gramatica. Los campos datos de esta clase son: Term: es el alfabeto de símbolos terminales. Se usa la clase Alfabeto. NoTerm: es el alfabeto de símbolos no terminales. Se usa la clase Alfabeto. Reglas: es el conjunto de reglas que definen la estructura del lenguaje. Se implementa con una lista simple encadenada de objetos ReglaLC. BlqCodInic: contiene el texto del bloque de código que se puso antes de las reglas en el texto fuente de esta gramática. Se usa durante la generación del código C++ de las acciones semánticas. 60

61 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos ClaseElemPila: si el analizador sintáctico usará una clase distinta a la definida por defecto, en este campo estará el nombre de esa clase. TablaAccSem: es el conjunto de las acciones semánticas definidas usando la directiva ACCSEM. Contiene las acciones semánticas que son comunes a 2 o más reglas. Se implementa usando la clase LSE (por medio de lista simple encadenada). TablaTerminal: si hay terminales descriptos por expresiones regulares, en esta tabla están los nombres de archivos donde se encuentran esas expresiones regulares. Esta tabla se construye a partir de las directivas TERMINAL e IGNORAR. Se implementa usando la clase LSE. MayIgualMin: si este campo es igual a 0 entonces las mayúsculas son interpretadas como diferentes a las minúsculas (como en C++). Si es distinto a 0 entonces serán tomadas como iguales (como Pascal). CodError: contendrá el código de error luego de haber ejecutado la operación Gramatica::Reasignar. Esta clase agrupa y encapsula las siguientes funciones importantes: Anulable, Primero, Siguiente, generador del código C++ de las acciones semánticas y al generador de analizadores lexicográficos (Gramatica:: ImprAnaLex) Estructura de un Símbolo para el Analizador Sintáctico SLR La estructura de un símbolo para cualquier analizador sintáctico SLR generado es una versión resumida de la clase Simbolo. Se implementa con la clase SimboloSLR, que contiene los siguientes campos: Id: es el código del símbolo. NoTerminal: si tiene 0 entonces éste símbolo es terminal, caso contrario es no terminal. 61

62 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos Estructura por defecto de los Elementos de la Pila del Analizador Sintáctico SLR La estructura por defecto de los elementos de la pila de cualquier analizador sintáctico SLR generado se implementa con la clase ElemPila. Contiene los siguientes campos: Simb: aunque este campo es de acceso publico, sólo debe ser utilizado por el analizador sintáctico SLR y debe ser usado como de sólo lectura. Contiene el SimboloSLR1 correspondiente a ésta posición de la pila. Estado: Contiene el número de estado correspondiente a ésta posición de la pila. Aunque este campo es de acceso publico, sólo debe ser utilizado por el analizador sintáctico SLR y debe ser usado como de sólo lectura. CadTerm: es la cadena del terminal leído por el analizador lexicográfico y que corresponde a ésta posición de la pila. Si necesita campos adicionales para utilizarlos en las acciones semánticas, se aconseja que la clase que implemente el elemento de la pila sea una derivada de ésta Estructura de un Analizador Sintáctico SLR La estructura de un analizador sintáctico SLR se implementa con la clase AnalSLR1. Esta clase es una clase template, siendo el tipo de dato del elemento de la pila el parámetro template. De esta manera, se escribió una sola vez el código del analizador sintáctico SLR y el mismo servirá para cualquier tipo de elemento de pila que se le pueda ocurrir a cualquier usuario en cualquier tiempo. La clase template AnalSLR1 en realidad define una familia de clases, cada una de las cuales implementa un caso particular de analizador SLR, que sólo difiere del resto en el tipo de elemento de la pila. El funcionamiento del analizador es el mismo para todas las clases (obviamente), lo único que puede cambiar son las tablas y la pila. El tipo de elemento de la pila y las tablas se especificarán durante la creación del objeto AnalSLR1. Al pedir realizar el análisis sintáctico, el analizador le pedirá que le indique quién hará el análisis lexicográfico. De esta forma se contempla la posibilidad que para un analizador sintáctico se puedan usar varios analizadores lexicográficos en un mismo programa. El analizador lexicográfico es el que realmente tratará con el texto fuente de entrada (a 62

63 analizar sintácticamente). Se espera que el analizador lexicográfico haya sido implementado usando la clase AnaLex. Los campos datos de esta clase son protegidos salvo el campo CodError. Ellos son: ntlpd: es una tabla generada por el generador de analizadores sintácticos. Contiene el número de terminales, el número de no terminales, y para todas las reglas de la gramática, el código del no terminal de la regla y la longitud de la parte derecha de la regla. accion: es la tabla acción que se explicó en la sección 4.4. Puede haber sido generada por el generador de analizadores sintáctico SLR. ir_a: es la tabla ir_a cuya construcción de documenta en la sección 4.4. Puede haber sido generada por el generador de analizadores sintáctico SLR. faccsem: es un puntero a la función que ejecuta las acciones semánticas. flimpila: es un puntero a la función que limpia la pila. Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos fver: es un puntero a la función que permite ver el progreso del análisis sintáctico. CodError: aquí se almacena el código de error que pudiera haberse producido durante una sesión de análisis sintáctico. Con limpiar la pila se hace referencia a alguna posible operación especial que a veces es necesario cuando se trabaja con objetos muy complejos como campos de la clase que implementa los elementos de la pila. Se aconseja no usar una función que limpie la pila, en su defecto, se debe hacer que el destructor de la clase que implementa los elementos de la pila sea la que realice la limpieza de ése elemento. Consulte también la sección 12.8 que documenta esta clase desde una perspectiva del usuario Estructura de la Pila del Analizador Sintáctico SLR La pila del analizador sintáctico SLR se implementa usando vectores, cuyo tamaño será indicado por el usuario al llamar a la función AnalSLR1:: Analizar. 63

64 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos Por defecto, el tamaño del vector es 50 elementos, lo que es suficiente para la mayoría de las situaciones. Si la gramática con la que se construyó éste analizador usa mucho la recursividad por derecha entonces puede necesitar un tamaño más grande de la pila. La pila del analizador es local a la función AnalSLR1::Analizar, esto quiere decir que la misma será construida y destruida cada vez que se llame a esa función. De esta manera, mientras no se esté usando el analizador sintáctico la pila no estará usando memoria. Los elementos de la pila son de la clase especificada durante la creación del objeto AnalSLR1. Consulte también el capítulo 12 que documenta a SLR1 v 2.x desde una perspectiva del usuario Estructura de las Tablas del Analizador Sintáctico SLR Las tablas del analizador sintáctico SLR se implementaron en vectores, con los elementos acomodados por fila mayor. Aunque las tablas accion e ir_a pueden ser esparcidas. No se contempla la posibilidad de almacenarlas en una estructura que reduzca el espacio necesario para almacenarlas, porque para la gramática más grande con la que se trabajó, las tablas llegaron a ocupar 4000 bytes aproximadamente. Se sabe que cualquier computadora tiene más de 1 megabyte de RAM, entonces no se tocó la implementación original de las tablas. Por otro lado, posiblemente sea más conveniente perder espacio y ganar velocidad en acceso (se realizan muchos accesos aleatorios a las tablas, durante el análisis sintáctico). ntlpd: viene de no terminal de la izquierda - longitud de la parte derecha de la regla. Esta tabla contiene elementos de 2 bytes de tamaño. El primer byte es el código del no terminal de la izquierda de la flecha de la regla. El segundo byte contiene la cantidad de elementos de la parte derecha de la flecha de la regla, 0 si deriva en λ. El elemento 1 contiene información de la regla 1, el elemento 2 contiene información de la regla 2, y así sucesivamente. 64

65 El elemento 0 contiene información especial, que difiere del resto de los elementos de la tabla: en el primer byte (el byte alto si se está trabajando con una PC sea cual fuere el sistema operativo) contiene el número de símbolos no terminales de la gramática, el segundo byte (el byte bajo si se está usando una PC) contiene el número de símbolos terminales. accion: Cada elemento de esta tabla debe ser visto como un entero no negativo de 2 bytes. Los elementos de la tabla acción se acomodan por fila mayor. La posición correspondiente al símbolo fin de archivo es la columna 0 de la matriz, la columna 1 corresponde al primer terminal, la columna 2 al segundo y así sucesivamente. Los bits 15 y 14 contienen el código de acción, a saber: 00 para desplazar, 01 para aceptar y 10 para reducir. El código 11 no se usa. Los bits restantes se usan para especificar el nuevo estado al que debe pasar el autómata. Si un elemento corresponde a una posición de error de sintaxis, se deberá almacenar un 0. Si un elemento corresponde a una acción de desplazamiento, el número estará en el rango de 0 y 16383, ambos inclusive. Nunca se encontrará la acción d0 puesto que no se encontrará O' a la derecha de ninguna regla (para la construcción de las tablas se partió de una gramática ampliada; consulte la sección 4.4). Para el elemento que tenga la acción aceptar se encontrará el número Habrá únicamente 1 solo elemento con ese valor. Para los elementos que correspondan a reducciones se encontrarán números en el rango de al 49151, ambos inclusive. Si se encuentra un número que no haya sido especificado en los párrafos precedentes entonces hubo problemas durante la construcción de ésta tabla. ir_a: Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos Cada elemento de esta tabla debe ser visto como un entero no negativo de 2 bytes. Los elementos de la tabla acción se acomodan por fila 65

66 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos mayor. La columna 0 corresponde al primer no terminal, la columna 1 al segundo, la columna 2 al tercero, y así sucesivamente. Los valores que contiene cada elemento son directamente el número de estado al que debe ir el autómata. Si la entrada de la tabla no está definida entonces se deberá poner un Estructura de un Elemento del Análisis Sintáctico LR(0) La estructura de un elemento del análisis sintáctico LR(0) se implementa con la clase ElemCanonLR0, cuyos campos son: pr: puntero a la regla a la que corresponde. punto: puntero a la posición dentro de la regla, en donde se encuentra el punto Estructura de un Conjunto de Elementos del Análisis Sintáctico LR(0) El conjunto canónico de elementos se implementa por medio de una lista simple encadenada de elementos ElemCanonLR0. La clase que implementa se llama ConjElem y los campos datos son: lse: es la lista simple encadenada de elementos. numelem: es el número de elementos que hay en lse. Se eligió la estructura de lista simple encadenada para implementar el conjunto porque se desconoce de antemano el número de elementos que resultan luego del cálculo de la función ir_a (ver sección 4.3.5) Estructura de un Estado del Analizador Sintáctico SLR durante la Construcción de las Tablas Un estado se implementa con la clase Estado. Los campos dato de esta clase son: pce: es un puntero al conjunto de elementos de éste estado. n: es el número que le tocó a éste estado. 66

67 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos ves: es el vector de estados siguientes a éste estado. Contiene el resultado de la función ir_a que se documenta en la sección 4.3.5, para cada símbolo gramatical. El número de elementos de este vector es el número de terminales más el número de no terminales más uno (para el fin de archivo) Estructura de la Colección Canónica de Conjuntos de Elementos del Análisis Sintáctico LR(0) La estructura de la colección canónica de conjunto de elementos se implementa con la clase ConjEst. Se eligió la estructura de lista simple encadenada para el conjunto de estado debido a que no se sabe de antemano el número de estados que habrá, ni tampoco se hacen muchos accesos secuenciales como para justificar una estructura de datos rara. Los campos dato de la clase son: lse: la lista simple encadenada de estados. El elemento 0 correspondrá al estado 0, y así sucesivamente. numest: es el número de estados, número de elementos que hay en lse. Esta clase es la que actualmente está encargada de construir las tablas de análisis sintáctico para una gramática dada. La función se llama ConjEst::Armar, y recibe como parámetro una Gramatica. 7.2 Carga de una Gramática en Memoria Se denominará carga de una gramática en memoria al proceso de pasar de formato texto al formato especificado por la clase Gramatica. La conversión de formato la realiza la función Gramatica::Reasignar, que recibe como parámetro un texto en formato especificado por la metagramática documentada en la sección Se construyó, a tal efecto, un analizador lexicográfico y un analizador sintáctico SLR. Se usaron las clases AnaLex y AnalSLR1 respectivamente. Las acciones semánticas escritas se encuentran en la metagramática. 67

68 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos Al finalizar con éxito la ejecución de la función Gramatica::Reasignar, la gramática está lista para ser procesada a efectos de construir un analizador sintáctico y lexicográfico para la misma. Si hay algún problema, el código del error se encontrará en Gramatica::CodError. 7.3 Generación de un Analizador Lexicográfico La generación de un analizador lexicográfico se realiza a partir de una lista de descriptores de terminales y del conjunto de símbolos terminales de una gramática. La generación del analizador lexicográfico está basada en la teoría explicada en el capítulo 5. Se deberá construir un vector de reconocedores de símbolos a partir de los descriptores y del conjunto de terminales usando las siguientes reglas: 1. Si un símbolo terminal se encuentra en la lista de descriptores de terminales entonces se asume que el mismo está descripto por una expresión regular que se encuentra en un archivo de texto cuyo nombre está en el descriptor. Se incluirá entonces el autómata finito determinístico generador por el programa AFD (o cualquier generador que use la biblioteca ExpReg) a partir de ese archivo. 2. Si un símbolo terminal no se encuentra en la lista de descriptores y es un literal (se llamará literal a un símbolo que haya sido especificado entre comillas simples o dobles), entonces se especifica esa cadena de caracteres en la construcción del reconocedor de símbolo respectivo. 3. Si un símbolo terminal no se encuentra en la lista de descriptores y no es un literal entonces en la especificación de la gramática no se dio una descripción correcta del mismo. El descriptor de estos terminales será construido de manera similar a los del punto anterior, pero deberá ser modificado a efectos de que el analizador lexicográfico funcione de la manera esperada. 4. Si hay un descriptor de símbolo para el cual no hay un correspondiente símbolo terminal, entonces se asume que hay un secuencias de caracteres en el texto fuente que deberán ser ignorados, y la estructura de esas secuencias están especificadas por expresiones regulares que se encuentran en los archivos especificados por los correspondientes 68

69 Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos descriptores. Por ejemplo, es común ignorar blancos y comentarios en un texto correspondiente a una oración del lenguaje. La función que implementa los pasos anteriores se llama Gramatica:: ImprAnaLex, que es una versión que genera un fuente C++ para el analizador lexicográfico, que se implementa usando la clase AnaLex. En este trabajo no se contempla la construcción dinámica de analizadores lexicográficos. Un constructor dinámico sería el que tome una gramática y construya directamente el objeto AnaLex, dejándolo listo para ser usado en el mismo programa en donde se encuentra el constructor dinámico. La base para escribir el constructor dinámico la puede encontrar en el código fuente de Gramatica::ImprAnaLex. 7.4 Generación de un Analizador Sintáctico SLR La generación de las tablas para un analizador sintáctico SLR se resume en los siguientes pasos: 1. Primero cargar la gramática en memoria, esto es, construir un objeto Gramatica que contenga la gramática fuente. Se debe utilizar la función Gramatica::Reasignar para convertir de formato texto a formato Gramatica. 2. A partir de esa gramática se debe armar la colección canónica de conjuntos de elementos del análisis sintáctico LR(0). Esta operación construye además las tablas acción e ir_a que definen al analizador sintáctico para la gramática fuente. Se debe utilizar la función ConjEst::Armar. 3. Para generar el código C++ del analizador lexicográfico se debe utilizar la función Gramatica::ImprAnaLex. 4. Para generar el código C++ de las acciones semánticas se debe utilizar Gramatica::ImprAccSem. 5. Para generar el código C++ de las tablas se debe utilizar la función ConjEst::ImprTablaC. 6. Para generar un archivo de texto con los estados y las tablas del analizador generado se debe utilizar la función ConjEst::ImprTabla. 69

70 La implementación de los pasos precedentes se encuentra en el módulo genslr1.cmm. La parte independiente del sistema operativo del mismo se transcribe a continuación: // Implementación de la generación de un analizador sintáctico SLR y de un // analizador lexicográfico a partir de una gramática. // El código siguiente deberá estar dentro de alguna función C++. // Si es necesario se deberá intercalar código que muestre el progreso y // los errores que pudieran ocurrir. // Objetos utilizados: Gramatica G; ConjEst AutomataSLR1; Ascii0 CadPosErr, GramTxt; const char * nom, cmm[] = ".cmm", est[] = ".est", tab[] = ".tab"; ArchivoSO a; // Cargar la gramática en la memoria: if ( a.abrir(nomarch, MA_L) ) return ERROR; // imposible abrir archivo if (! GramTxt.Reasignar(a.Tam()+1)) return ERROR; // falta memoria para cargar el texto if (! a.leer( (char *) GramTxt, a.tam() ) ) return ERROR; // error de e/s al intentar leer el texto desde archivo a.cerrar(); // Realizar análisis sintáctico y reasignar a objeto Gramatica if (! G.Reasignar(GramTxt, CadPosErr)) return ERROR; // error de sintaxis o lexicográfico en gramática G.ArmarAlfabetos(); // terminal y no terminal // Generar el código fuente con las acciones semánticas: nom = NomExt(NomArch, cmm); if (a.abrir(nom, MA_E)) return ERROR; G.ImprAccSem(a, Sufijo); a << "\n"; // Generar código para el analizador léxico: G.ImprAnaLex(a, Sufijo); // Imprimir analizador sintáctico: se genera declaración de la forma // AnalSLR1<ElemPila> assufijo(ntlpdsufijo, accionsufijo, ir_asufijo, // AccSemSufijo); a << "\n// Definición del analizador sintáctico (SLR1 v 2.3).\n" "// Cambiar de lugar la definición si es necesario.\n\n" "# include \"" << NomExtSinDir(NomArch, tab) << "\" // tablas del analizador sintáctico\n\nanalslr1<" << (const char *) G.ClaseElemPila << "> as" << Sufijo << "(ntlpd" << Sufijo << ", accion" << Sufijo << ", ir_a" << Sufijo << ", AccSem" << Sufijo << ");"; a.cerrar(); // Imprimir la gramática en archivo.est nom = NomExt(NomArch, est); Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos 70

71 if ( a.abrir(nom, MA_E, MD_TXT) ) return ERROR; // no se puede abrir el archivo.est para escritura a << G; // Armar las tablas de análisis sintáctico AutomataSLR1.Armar(G, FuncError); // Imprimir las tablas en archivo.est a << "\n\nestados: \n\n" << AutomataSLR1; AutomataSLR1.ImprTabla(a, G); a.cerrar(); // Imprimir las tablas en archivo.tab AutomataSLR1.ImprTablaC(NomExt(NomArch, tab), G, Sufijo); // Fin de la implementación Implementación del Generador de Analizadores Sintácticos SLR y del Generador de Analizadores Lexicográficos 7.5 Implantación del Algoritmo de Análisis Sintáctico SLR El algoritmo de análisis sintáctico SLR que se muestra en la figura 10 de la sección 4.2.1, se implementa con la función AnalSLR1::Analizar, que recibe como entrada el analizador lexicográfico a usar y el tamaño de la pila. Consulte la sección 12.8 que documenta la función AnalSLR1:: Analizar. 71

72 Parte 3. Documentación para el Usuario 72

73 Documentación de AFD versión 1.x Capítulo 8: Documentación de AFD versión 1.x El texto de este capítulo es la documentación de AFD v 1.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. La última versión de AFD importante disponible es la 3.x. Su documentación la encontrará en el capítulo 10. Si se planea usar AFD entonces se debe usar esa versión o alguna posterior. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación. 8.1 Introducción El programa AFD (por autómata finito determinístico) es un generador de autómata finito determinístico (dado en tablas y fuente C++) a partir de una expresión regular fuente. Esta versión ha sido realizada para uso interno del autor solamente, para poder mejorar el analizador lexicográfico de la versión 2. Es muy limitada en cuanto al lenguaje fuente de especificación de una expresión regular. Además la inclusión del AFD generado en un programa es un poco complicado. Esta documentación puede resultar incompleta para el uso de AFD en programación. La razón es que debe utilizar la versión 2 o posterior de AFD. 8.2 Descripción general del funcionamiento El programa AFD v 1 funciona bajo DOS. Utiliza la salida estándar para la comunicación de mensajes. La entrada son los argumentos pasados al arrancar el programa. donde: La sintaxis de llamada es: AFD "expreg" [ PostFijoObj ] [ PostFijoSubobj ] expreg: es la expresión regular. Su sintaxis se explica más adelante. 73

74 Documentación de AFD versión 1.x PostFijoObj: es la cadena postfijo a utilizar para los nombres del objeto AutomFinDet. Es opcional. PostFijoSubobj: es la cadena postfijo a utilizar para los nombres de las tablas. Si éste parámetro no es dado se toma el valor de PostFijoObj. Este parámetro también es opcional. Si utiliza el operador > de redireccionamiento de la salida estándar puede mandar la misma a un archivo de texto, para luego poder consultarlo en detalle y poder usar el fuente C++ generado. Las tablas que se generan se presentan en un formato similar utilizado por la bibliografía que trata sobre AFDs. El fuente se da en C++, las declaraciones importantes las puede encontrar en EXPREG.H, y si usa el fuente C++ debe incluir la biblioteca EXPREG.LIB. El contenido de las tablas y la estructura del fuente C++ se explica más adelante. 8.3 Sintaxis de una expresión regular Una expresión regular reconocido por AFD versión 1 queda perfectamente definido por las siguientes reglas: 1. La cadena nula (lambda) es una expresión regular. 2. Si a no es un caracter reservado (operador), a es una expresión regular. 3. Si r y s son expresiones regulares, entonces las siguientes son expresiones regulares: a. r s (concatenación). b. r s (uno u otro). c. r * (0 o más veces r). d. r + (1 o más veces r). e. r? (0 o 1 vez r). f. ( r ) (los paréntesis se utilizan para asociar). Los operadores ( ) (paréntesis) tienen la mayor precedencia. Los operadores *, +, y? tienen los tres la misma precedencia y son de menor precedencia que los paréntesis. Por último, el junto con la concatenación (que 74

75 Documentación de AFD versión 1.x no tiene carácter que lo represente, simplemente se ponen las expresiones regulares una a la par de la otra) son los que tienen menor precedencia. La gramática usada por AFD v 1 es la siguiente: ExpReg --> ExpReg ExpReg2 ExpReg --> ExpReg ' ' ExpReg2 ExpReg --> ExpReg2 ExpReg2 --> ExpReg3 '?' ExpReg2 --> ExpReg3 '+' ExpReg2 --> ExpReg3 '*' ExpReg2 --> ExpReg3 ExpReg3 --> '(' ExpReg ')' ExpReg3 --> Caracter El analizador lexicográfico utilizado por esta versión es muy simple. Reconoce sólo caracteres y operadores de la siguiente manera: Los caracteres,?, +, *, ( y ) son caracteres reservados. No pueden ser usados como simples caracteres en la expresión regular. Si el caracter actual no es un caracter reservado, entonces se lo toma literalmente. Por ejemplo, "a? b" es una expresión regular formado por un espacio y una b precedido opcionalmente por una a. 8.4 Ejemplos del funcionamiento del programa Ejemplo 1 En este ejemplo se da una expresión regular para reconocer blancos de un archivo de texto. El rango de caracteres considerados blancos se simbolizan con la letra b. A la hora de implementar el fuente, se deberá cambiar éste por su correspondiente rango. Expresión regular: b+ Llamada a AFD: AFD "b+" Blanco 75

76 Documentación de AFD versión 1.x Salida de AFD: Expresión regular: b+ Caracter o rango: (0, "b") Tabla de transiciones: Estado final: 1 En formato fuente: // Fuente del AFD para la expresión regular "b+" // Generado por AFD versión 1 (Ene 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char erblanco[] = "b+"; static const char * vcblanco[] = { "b" ; static const unsigned tblanco[] = { 1, 1 ; static const unsigned efblanco[] = { 1, 1 ; AutomFinDet afdblanco = { erblanco, vcblanco, 1, tblanco, efblanco ; Ejemplo 2 La siguiente expresión regular ha sido extraída del libro "Compiladores - principios, técnicas y herramientas" de Aho, Sethi y Ullman, editorial Addison-Wesley Iberoamericana, página 138 de la versión en Castellano, figura La expresión regular se la da en 3.39.a, y el autómata finito determinístico para esa expresión regular está en 3.39.b; observe que el autómata resultante es exactamente igual al mostrado en la figura (esto es así porque AFD v 1 utiliza el mismo algoritmo usado para generar el de esa figura). Expresión regular: (a b)*abb Llamada a AFD: AFD "(a b)*abb" Salida de AFD: Expresión regular: (a b)*abb Caracteres y/o rangos: (0, "a"), (1, "b") Tabla de transiciones:

77 Documentación de AFD versión 1.x Estado final: Ejemplo 3 La siguiente expresión regular sirve para reconocer caracteres en formato C++. Con la letra d se simboliza la comilla doble (que no puede ser usada en la llamada a AFD por ser caracter reservado de DOS). Con la letra e se simboliza un caracter usado para representar una secuencia de escape. Con el punto se simbolizan todos los demás caracteres. Expresión regular: '((\(\ ' d e)) e.)' Llamada a AFD: AFD "'((\(\ ' d e)) e.)'" Caract Salida de AFD: Expresión regular: '((\(\ ' d e)) e.)' Caracteres y/o rangos: (0, "\'"), (1, "\\"), (2, "d"), (3, "e"), (4, ".") Tabla de transiciones: Estado final: 4 En formato fuente: // Fuente del AFD para la expresión regular "'((\(\ ' d e)) e.)'" // Generado por AFD versión 1 (Ene 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char ercaract[] = "'((\(\ ' d e)) e.)'"; static const char * vccaract[] = { "\'", "\\", "d", "e", "." ; static const unsigned tcaract[] = { 1, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, 2, TNDEF, 3, 3, 3, 3, 3, 3, TNDEF, 4, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF ; static const unsigned efcaract[] = { 1, 4 ; AutomFinDet afdcaract = { ercaract, vccaract, 5, tcaract, efcaract ; 77

78 Documentación de AFD versión 1.x Ejemplo 4 En este ejemplo se da una expresión regular para reconocer rangos de caracteres en el formato de LEX de UNIX. Con la letra d se simboliza la comilla doble, con la letra e se simbolizan los caracteres utilizados en las secuencias de escape, y con el punto se simbolizan el resto de los caracteres. Expresión regular: [((\(\ ' d e)) e.)+] Llamada a AFD: AFD "[((\(\ ' d e)) e.)+]" Rango Salida de AFD: Expresión regular: [((\(\ ' d e)) e.)+] Caracteres y/o rangos: (0, "["), (1, "\\"), (2, "\'"), (3, "d"), (4, "e"), (5, "."), (6, "]") Tabla de transiciones: Estado final: 4 En formato fuente: // Fuente del AFD para la expresión regular "[((\(\ ' d e)) e.)+]" // Generado por AFD versión 1 (Ene 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char errango[] = "[((\(\ ' d e)) e.)+]"; static const char * vcrango[] = { "[", "\\", "\'", "d", "e", ".", "]" ; static const unsigned trango[] = { 1, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, 2, TNDEF, TNDEF, 3, 3, TNDEF, TNDEF, 3, 3, 3, 3, TNDEF, TNDEF, TNDEF, 2, TNDEF, TNDEF, 3, 3, 4, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF, TNDEF ; static const unsigned efrango[] = { 1, 4 ; AutomFinDet afdrango = { errango, vcrango, 7, trango, efrango ; 8.5 Descripción de la salida de AFD La salida de AFD v 1 consta de dos partes según se especifique o no el/los postfijo(s) en la sintaxis de llamada. La primera parte es un texto explicativo del AFD generado a partir de la expresión regular en formato 78

79 Documentación de AFD versión 1.x similar al que se usa en la bibliografía. La segunda parte es una versión C++ de la primera parte, y comienza con el mensaje "En formato fuente:". En ambas partes se muestra primero la expresión regular, en la de C++ se usa el prefijo er por expresión regular. Lo que sigue es la enumeración de los caracteres que aparecen en la expresión regular. El prefijo usado en el fuente C++ es vc. En la primera parte se muestran como una enumeración de pares ordenados en donde la primera componente es el número asignado al caracter y la segunda componente es el caracter. El número asignado al caracter sirve para acceder a la columna correspondiente de la tabla de transición de estados del autómata. Luego se presenta la tabla de transición de estados del autómata finito determinístico generado. Por filas están los estados, comenzando por el 0. Por columnas están los caracteres que participan en la expresión regular. En la parte de formato texto la primera fila de esta tabla muestra los números de caracter, y la primera columna los números de estado. El número que aparece en la primera fila indica el número de caracter que va allí. Por ejemplo, el 0 indica que esa columna corresponde al primer caracter aparecido en la expresión regular, que es el primero que aparece en la enumeración de caracteres "Caracteres y/o rangos:". En el fuente C++ la tabla tiene nombre con prefijo t, se almacena por filas en un vector de enteros sin signo. El tamaño de este vector es: cantidad de estados por cantidad de caracteres. Los estados finales son almacenados en el vector de enteros sin signo que tiene prefijo vf. El primer elemento de éste vector indica el número de estados finales, a continuación están los estados finales. La implementación del autómata finito determinístico es realizada con la clase AutomFinDet declarada en EXPREG.H. La declaración es como sigue: class AutomFinDet { public: const char * ExpRegular; const char * * Caracter; unsigned NumCar; const unsigned * Transicion, * EstFinales; ; en donde: 79

80 Documentación de AFD versión 1.x ExpRegular: es una cadena de caracteres en formato C++ que define la expresión regular reconocida por éste autómata. Caracter: es un vector de cadena de caracteres que contiene los caracteres posibles como caracteres simples o como rangos. NumCar: es el número de caracteres, que coincide con el tamaño del vector Caracter y con el número de columnas de la tabla de transición de estados del autómata. Transicion: es la tabla de transición de estados del autómata finito determinístico que reconoce la expresión regular ExpRegular. Para una transición no definida se debe utilizar TNDEF o el número hexa 0xffff. EstFinales: es el conjunto de estados finales del AFD. El primer elemento de este vector indica el número de estados finales, a continuación, en los elementos siguientes, están los estados finales. El número de estados no se almacena en la estructura debido a que, como la tabla de transición de estados es generada automáticamente, su uso no llevará a transicionar a un estado inexistente. Pero sí se almacena el número de caracteres debido a que los mismos deberán pasar por un filtro de caracteres para poder ser atendidos por el autómata. El filtro funciona como un clasificador de caracteres; la clasificación (agrupación por rangos o grupos de caracteres) sirve para minimizar la cantidad de estados necesarios. Puede ver ejemplos de definición de objetos AutomFinDet en los ejemplos dados previamente. 8.6 Consideraciones finales Esta versión de AFD ha sido realizada a efectos de poder hacer una versión más fácil de usar, en el que la entrada pueda ser dada en un formato más amigable. Los AFD generados pueden ser utilizados para implementar analizadores lexicográficos. Los analizadores lexicográficos constan en general de varios autómatas finitos determinísticos. Más información sobre la implementación de analizadores lexicográficos usando la salida de AFD puede encontrar en la documentación de la biblioteca ExpReg. 80

81 Documentación de AFD versión 1.x Esta versión no ha sido realizada para uso de terceros, sólo para uso del autor. Se debe utilizar alguna de las versiones siguientes. 81

82 Documentación de AFD versión 2.x Capítulo 9: Documentación de AFD versión 2.x El texto de este capítulo es la documentación de AFD v 2.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. La última versión de AFD importante disponible es la 3.x. Su documentación la encontrará en el capítulo 10. Si se planea usar AFD entonces se debe usar esa versión o alguna posterior. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación. La documentación que a continuación se presenta, ha sido hecha a partir de la documentación de AFD versión 1.x. 9.1 Introducción El programa AFD (por autómata finito determinístico) es un generador de autómata finito determinístico (dado en tablas y fuente C++) a partir de una expresión regular fuente. A diferencia de la versión 1 de AFD, esta versión tiene un analizador lexicográfico mejorado que permite el uso de caracteres en formato C++, rangos de caracteres que incluyen secuencias de escape, y la posibilidad de dejar espacio entre símbolos léxicos. Aunque con esta versión se pueden realizar trabajos de programación, se aconseja usar la versión 3, que tiene una gramática de especificación de la entrada más completa y un analizador léxico mejorado, construido a partir de ésta versión. 9.2 Descripción general del funcionamiento El programa AFD v 2 funciona bajo DOS. Utiliza la salida estándar para la comunicación de mensajes. La entrada son los argumentos pasados al arrancar el programa. La sintaxis de llamada es: 82

83 Documentación de AFD versión 2.x donde: AFD "expreg" [ PostFijoObj ] [ PostFijoSubobj ] expreg: es la expresión regular. Su sintaxis se explica más adelante. PostFijoObj: es la cadena postfijo a utilizar para los nombres del objeto AutomFinDet. Es opcional. PostFijoSubobj: es la cadena postfijo a utilizar para los nombres de las tablas. Si éste parámetro no es dado se toma el valor de PostFijoObj. Este parámetro también es opcional. Si utiliza el operador > de redireccionamiento de la salida estándar puede mandar la misma a un archivo de texto, para luego poder consultarlo en detalle y poder usar el fuente C++ generado. Las tablas que se generan se presentan en un formato similar utilizado por la bibliografía que trata sobre AFDs. El fuente se da en C++, las declaraciones importantes las puede encontrar en EXPREG.H, y si usa el fuente C++ debe incluir la biblioteca EXPREG.LIB. El contenido de las tablas y la estructura del fuente C++ se explica más adelante. Puede también consultar la documentación de la biblioteca ExpReg para más información. 9.3 Sintaxis de una expresión regular Una expresión regular reconocida por AFD versión 2 queda perfectamente definida por las siguientes reglas: 1. La cadena nula (lambda) es una expresión regular. 2. Si a no es un caracter reservado (operador), a es una expresión regular. 3. Si r y s son expresiones regulares, entonces las siguientes son expresiones regulares: a. r s (concatenación). b. r s (uno u otro). c. r * (0 o más veces r). 83

84 Documentación de AFD versión 2.x d. r + (1 o más veces r). e. r? (0 o 1 vez r). f. ( r ) (los paréntesis se utilizan para asociar). Los operadores ( ) (paréntesis) tienen la mayor precedencia. Los operadores *, +, y? tienen los tres la misma precedencia y son de menor precedencia que los paréntesis. Por último, el es el que tiene menor precedencia. La gramática usada por AFD v 2 es la siguiente: ExpReg --> ExpReg ExpReg2 ExpReg --> ExpReg ' ' ExpReg2 ExpReg --> ExpReg2 ExpReg2 --> ExpReg3 '?' ExpReg2 --> ExpReg3 '+' ExpReg2 --> ExpReg3 '*' ExpReg2 --> ExpReg3 ExpReg3 --> '(' ExpReg ')' ExpReg3 --> Caracter El analizador lexicográfico de esta versión se realizó con la versión 1 de AFD. Los símbolos léxicos reconocidos son los siguientes: (cuando sea aplicable se usarán expresiones regulares escritas en formato AFD v 2, por cuanto pueden ser tomadas como ejemplo de especificación) Operadores:? + * ( y ) Constante de caracter: ' ( ( \ ( \ ' " [abtnvfr] ) ) [abtnvfr] [^\'\\\"\n] ) ' Rango: [ ( ( \ ( \ ' " [abtnvfr] ) ) [abtnvfr] [^\'\\\"\n] ) + ] Identificador: [a-za-záéíóúññüü] ( [a-za-záéíóúññüü] [0-9] ) + Observar que se usa + porque sino un identificador se confundiría con un caracter que cualquiera que fuera letra o dígito. La longitud resultante de un identificador es de por lo menos 2 caracteres. El identificador no se usa en esta versión debido a que la entrada es la estándar. Se usa recién en la versión 3, aunque el código que maneja 84

85 Documentación de AFD versión 2.x identificadores ya está presente en esta versión. Por lo tanto no debe especificar identificadores en la entrada. Comentarios: / / [^\n] * Los comentarios son iguales a los que se usan en C++, comienzan con un doble // y terminan al final de la línea. No forman parte de la expresión regular y son ignorados por el analizador léxico de AFD v 2. Debido a que la entrada es una cadena de caracteres que se especifica como argumento, se tiene solamente una línea, por lo que el comentario debe estar siempre al final de la expresión regular de entrada, dentro de las comillas. Blancos: [ \n\r\f\t\v] + Los blancos no forman parte de la expresión regular y son ignorados por el analizador lexicográfico de AFD v 2. Otros: si un caracter que aparece en la expresión regular no cumple con alguno de los componentes léxicos dados previamente, entonces es tomado como un simple caracter por el analizador lexicográfico de AFD v 2. Así usted verá que en las expresiones regulares dadas, si hay una comilla simple que abre y no hay otra que cierra dos caracteres más adelante, entonces esa comilla es tomada como literal. Para más detalle ver los ejemplos siguientes. 9.4 Ejemplos de funcionamiento del programa Ejemplo 1 Expresión regular para un identificador reconocido por el analizador léxico de AFD v 2. Expresión regular: [a-za-záéíóúññüü] ( [a-za-záéíóúññüü] [0-9] ) + Llamada a AFD: AFD "[a-za-záéíóúññüü] ( [a-za-záéíóúññüü] [0-9] ) +" Salida de AFD: Expresión regular: [a-za-záéíóúññüü] ( [a-za-záéíóúññüü] [0-9] ) + Caracteres y/o rangos: (0, "a-za-záéíóúññüü"), (1, "0-9") Tabla de transiciones: 85

86 Documentación de AFD versión 2.x Estado final: Ejemplo 2 Expresión regular para un comentario ignorado por el analizador léxico de AFD v 2. Expresión regular: / / [^\n] * Llamada a AFD: AFD "/ / [^\n] * // ejemplo de comentario" Coment Salida de AFD: Expresión regular: / / [^\n] * // ejemplo de comentario Caracteres y/o rangos: (0, "/"), (1, "^\n") Tabla de transiciones: Estado final: 2 En formato fuente: // Fuente del AFD para la expresión regular "/ / [^\n] * // ejemplo de comentario" // Generado por AFD versión 2 (Ene 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char ercoment[] = "/ / [^\n] * // ejemplo de comentario"; static const char * vccoment[] = { "/", "^\n" ; static const unsigned tcoment[] = { 1, TNDEF, 2, TNDEF, TNDEF, 2 ; static const unsigned efcoment[] = { 1, 2 ; AutomFinDet afdcoment = { ercoment, vccoment, 2, tcoment, efcoment ; Ejemplo 3 Expresión regular para los blancos ignorados por el analizador léxico de AFD v 2. 86

87 Documentación de AFD versión 2.x Expresión regular: [ \n\r\f\t\v] + Llamada a AFD: AFD "[ \n\r\f\t\v] +" Salida de AFD: Expresión regular: [ \n\r\f\t\v] + Caracter o rango: (0, " \n\r\f\t\v") Tabla de transiciones: Estado final: Ejemplo 4 Expresión regular para reconocer un caracter de manera similar al analizador léxico de AFD v 2. Expresión regular: ' ( ( \ ( \ ' " [abtnvfr] ) ) [abtnvfr] [^\'\\\"\n] ) ' Debido a que la comilla doble es un caracter reservado del DOS y que la expresión regular se la debe especificar entre comillas dobles en la llamada al archivo, la comilla doble ha sido cambiada por la letra d en la misma. Llamada a AFD: AFD " ' ( ( \ ( \ ' d [abtnvfr] ) ) [abtnvfr] [^\'\\\nd] ) ' " Salida de AFD: Expresión regular: ' ( ( \ ( \ ' d [abtnvfr] ) ) [abtnvfr] [^\'\\\nd] ) ' Caracteres y/o rangos: (0, "\'"), (1, "\\"), (2, "d"), (3, "abtnvfr"), (4, "^\'\\\nd") Tabla de transiciones: Estado final: Descripción de la salida de AFD La salida de AFD v 2 consta de dos partes según se especifique o no el/los postfijo(s) en la sintaxis de llamada. La primera parte es un texto explicativo del AFD generado a partir de la expresión regular en formato 87

88 Documentación de AFD versión 2.x similar al que se usa en la bibliografía. La segunda parte es una versión C++ de la primera parte, y comienza con el mensaje "En formato fuente:". En ambas partes se muestra primero la expresión regular, en la de C++ se usa el prefijo er por expresión regular. Lo que sigue es la enumeración de los caracteres que aparecen en la expresión regular. El prefijo usado en el fuente C++ es vc. En la primera parte se muestran como una enumeración de pares ordenados en donde la primera componente es el número asignado al caracter y la segunda componente es el caracter. El número asignado al caracter sirve para acceder a la columna correspondiente de la tabla de transición de estados del autómata. Luego se presenta la tabla de transición de estados del autómata finito determinístico generado. Por filas están los estados, comenzando por el 0. Por columnas están los caracteres que participan en la expresión regular. En la parte de formato texto la primera fila de esta tabla muestra los números de caracter, y la primera columna los números de estado. El número que aparece en la primera fila indica el número de caracter que va allí. Por ejemplo, el 0 indica que esa columna corresponde al primer caracter aparecido en la expresión regular, que es el primero que aparece en la enumeración de caracteres "Caracteres y/o rangos:". En el fuente C++ la tabla tiene nombre con prefijo t, se almacena por filas en un vector de enteros sin signo. El tamaño de este vector es: cantidad de estados por cantidad de caracteres. Los estados finales son almacenados en el vector de enteros sin signo que tiene prefijo vf. El primer elemento de éste vector indica el número de estados finales, a continuación están los estados finales. La implementación del autómata finito determinístico es realizada con la clase AutomFinDet declarada en EXPREG.H. La declaración es como sigue: class AutomFinDet { public: const char * ExpRegular; const char * * Caracter; unsigned NumCar; const unsigned * Transicion, * EstFinales; ; en donde: ExpRegular: es una cadena de caracteres en formato C++ que define la expresión regular reconocida por éste autómata. 88

89 Documentación de AFD versión 2.x Caracter: es un vector de cadena de caracteres que contiene los caracteres posibles como caracteres simples o como rangos. NumCar: es el número de caracteres, que coincide con el tamaño del vector Caracter y con el número de columnas de la tabla de transición de estados del autómata. Transicion: es la tabla de transición de estados del autómata finito determinístico que reconoce la expresión regular ExpRegular. Para una transición no definida se debe utilizar TNDEF o el número hexa 0xffff. EstFinales: es el conjunto de estados finales del AFD. El primer elemento de este vector indica el número de estados finales, a continuación, en los elementos siguientes, están los estados finales. El número de estados no se almacena en la estructura debido a que, como la tabla de transición de estados es generada automáticamente, su uso no llevará a transicionar a un estado inexistente. Pero si se almacena el número de caracteres debido a que los mismos deberán pasar por un filtro de caracteres para poder ser atendidos por el autómata. El filtro funciona como un clasificador de caracteres; la clasificación (agrupación por rangos o grupos de caracteres) sirve para minimizar la cantidad de estados necesarios. Puede ver ejemplos de definición de objetos AutomFinDet en los ejemplos dados previamente. 9.6 Consideraciones finales Los AFD generados por esta versión pueden ser utilizados para implementar analizadores lexicográficos. Los analizadores lexicográficos constan en general de varios autómatas finitos determinísticos. Más información sobre la implementación de analizadores lexicográficos usando la salida de AFD puede encontrar en la documentación de la biblioteca ExpReg. Luego de usar esta versión de AFD observará la necesidad de tener una sentencia tipo #define del C++ que permita definir macros, para así reducir el esfuerzo de escribir expresiones regulares y hacerlas más legibles. La versión 3 de AFD resuelve este problema, para lo cual se cambió la gramática de expresiones regulares. 89

90 Documentación de AFD versión 2.x Esta versión no ha sido realizada para uso de terceros, sólo para uso del autor. Se debe utilizar la versión 3 o siguiente dado que ofrecen más prestaciones. 90

91 Documentación de AFD versión 3.x Capítulo 10: Documentación de AFD versión 3.x El texto de este capítulo es la documentación de AFD v 3.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación. La documentación que a continuación se presenta, ha sido hecha a partir de la documentación de AFD versión 2.x. Esta versión de AFD trabaja conjuntamente con SLR1, el generador de analizadores sintácticos. Los archivos.cmm generados por AFD son usados por el analizador lexicográfico generado por SLR Introducción El programa AFD (por autómata finito determinístico) es un generador de autómata finito determinístico (dado en tablas y fuente C++) a partir de una expresión regular fuente. A diferencia de la versión 2 de AFD, esta versión incluye la posibilidad de definir macros para simplificar la escritura de expresiones regulares. Se mencionará en adelante como AFD v 3 o AFD v 3.x. Esta documentación describe a las versiones de DOS y Windows del generador. La versión de OS/2 es similar a la de Windows y no es mencionada en este documento Descripción general del funcionamiento Una versión del programa AFD v 3 funciona bajo DOS. La expresión regular se da en un archivo de texto. La salida en fuente C++ del autómata finito determinístico se guarda en un archivo.cmm que tendrá el mismo nombre del archivo de entrada. En la salida estándar se muestra el AFD en formato texto. La sintaxis de llamada al programa es: 91

92 Documentación de AFD versión 3.x AFD ArchExpReg [ PostFijoObj ] [ PostFijoSubobj ] Donde: ArchExpReg: es un archivo de texto con la expresión regular PostFijoObj: es la cadena postfijo a utilizar para los nombres del objeto AutomFinDet. Es opcional, pero si lo especifica el fuente C++ será guardado en ArchExpReg.CMM. PostFijoSubobj: es la cadena postfijo a utilizar para los nombres de las tablas. Si éste parámetro no es dado se toma el valor de PostFijoObj. Este parámetro también es opcional. Si utiliza el operador > de redireccionamiento de la salida estándar puede mandar la misma a un archivo de texto, para luego poder consultarlo en detalle. Otra versión de este utilitario funciona bajo Windows como un editor multitexto (un editor que incluye la posibilidad de editar muchos textos a la vez) con alta funcionalidad, en donde se incluye en el menú Archivo del mismo la posibilidad de "Generar autómata" que ejecuta todo el proceso de traducción para el texto cuya ventana es la actual. Como salida del proceso se generan un archivo.cmm y un archivo.txt con el mismo nombre que el de entrada. El archivo.txt contiene la versión en formato texto del autómata y el archivo.cmm contiene la versión C++ del mismo. Las tablas que se generan se presentan en un formato similar utilizado por la bibliografía que trata sobre AFDs. El fuente se da en C++, las declaraciones importantes las puede encontrar en EXPREG.H, y si usa el fuente C++ debe incluir la biblioteca EXPREG.LIB. El contenido de las tablas y la estructura del fuente C++ se explica más adelante. Puede también consultar la documentación de la biblioteca ExpReg para más información Sintaxis de una expresión regular Una expresión regular reconocida por AFD versión 3 queda perfectamente definida por las siguientes reglas: 1. La cadena nula (lambda) es una expresión regular. 92

93 Documentación de AFD versión 3.x 2. Si a no es un caracter reservado (operador), a es una expresión regular. 3. Si r y s son expresiones regulares, entonces las siguientes son expresiones regulares: a. r s (concatenación). b. r s (uno u otro). c. r * (0 o más veces r). d. r + (1 o más veces r). e. r? (0 o 1 vez r). f. ( r ) (los paréntesis se utilizan para asociar). La gramática usada por AFD v 3 es la siguiente: ExpReg ----> Defs Disy ExpReg ----> Disy Defs > Defs Def Defs > Def Def > '#' Ident CteCar Def > '#' Ident Rango Disy > Disy ' ' Concat Disy > Concat Concat ----> Concat Unario Concat ----> Unario Unario ----> Par_Car '?' Unario ----> Par_Car '+' Unario ----> Par_Car '*' Unario ----> Par_Car Par_Car ---> '(' Disy ')' Par_Car ---> Caracter Caracter --> Ident Caracter --> CteCar Caracter --> Rango De la gramática surge que la precedencia de los operadores es la que se muestra en la tabla siguiente: 93

94 Documentación de AFD versión 3.x Orden Operador Observaciones 1 la más alta 2 Los 3 la misma. ( ) Use para asociar o romper precedencias.? + * 0 o 1 vez. 1 o más veces. 0 o más veces. 4 Concatenación. No tiene caracter que lo represente. 5 Disyunción. Fig. 18 Precedencia de operadores en una expresión regular Símbolos Terminales que pueden aparecer en una Expresión Regular El analizador lexicográfico de esta versión se realizó con la versión 2 de AFD. Los símbolos léxicos (o terminales) reconocidos son los siguientes: (cuando sea aplicable se usarán expresiones regulares escritas en formato AFD v 3.x; las mismas pueden ser tomadas como ejemplo de especificación) Operadores:? + * ( y ) Numeral: # El numeral comienza una definición de macro. Para la definición de macros consulte más adelante, en la sección Ver ejemplos de su uso en la definición de constante de caracter, rango, etc. Constante de caracter: # CarEsp [abtnvfr] # Otros [^\'\\\"\n] ' ( \ ( \ ' " CarEsp ) CarEsp Otros ) ' Se considera constante de caracter a los que están entre comillas. Hay constantes de caracter que pueden especificarse sin comillas; ejemplo de ello son los que aparecen en la expresión regular dada que son la comilla simple y la \. Rango: # CarEsp [abtnvfr] # Otros [^\'\"\\\n] '[' ( \ ( \ ' " CarEsp ) CarEsp Otros ) + ']' 94

95 Documentación de AFD versión 3.x Cualquier caracter menos nueva línea ('\n'):. El punto es un caracter reservado de esta versión de AFD, sirve para identificar cualquier caracter menos el '\n' (nueva línea). Expande al rango [^\n]. Este caracter debe ser usado con mucho cuidado puesto que puede resultar en un autómata finito no determinístico (por la intersección de rangos de caracteres). En general es conveniente que aparezca al final de la expresión regular, y, si hay un caracter después de éste, ese caracter debe haber aparecido antes. Identificador: # Letra [a-za-z_á-ñéüü] # Dígito [0-9] Letra (Letra Dígito)+ Observar que se usa +, y esto es porque sino un identificador se confundiría con un caracter cualquiera que fuera letra o dígito. La longitud resultante de un identificador es de por lo menos 2 caracteres. Los identificadores se usan en AFD para identificar macros. Un identificador debe estar previamente definido antes de su uso. Ejemplos de definiciones de macros son los que se dan en esta expresión regular de identificador, en donde Letra y Dígito son los identificadores que se definen para reemplazar a los rangos. Comentarios: / /.* Los comentarios son iguales a los que se usan en C++, comienzan con un doble // y terminan al final de la línea, el efecto es ignorar desde el // en adelante. Los comentarios no forman parte de la expresión regular y son ignorados por el analizador léxico de AFD. Blancos: [ \n\r\f\t\v] + Los blancos no forman parte de la expresión regular y son ignorados por el analizador lexicográfico de AFD. Otros: si un caracter que aparece en la expresión regular no cumple con alguno de los componentes léxicos dados previamente, entonces es tomado 95

96 Documentación de AFD versión 3.x como un simple caracter por el analizador lexicográfico de AFD. Así usted verá que en las expresiones regulares dadas, si hay una comilla simple que abre y no hay otra que cierra uno o dos caracteres más adelante, entonces esa comilla es tomada como literal. Para más detalle ver los ejemplos siguientes Definición de macros La definición de macros se realiza usando la siguiente sintaxis: '#' Ident CteCar o bien '#' Ident Rango donde: Ident: es el identificador que reemplazará a la constante de caracter o rango de la derecha. CteCar: es una constante de caracter válida. Rango: es un rango de caracteres válido. Durante el análisis léxico, cada vez que aparezca Ident en la expresión regular fuente, el mismo será reemplazado por la constante de caracter o rango. No hay problemas si define dos macros en una misma línea, el analizador sintáctico de AFD se las arregla para entenderle. Por ejemplo, la siguiente son dos definiciones en una misma línea: # Letra [a-za-z] # digito [0-9] 10.4 Ejemplos de funcionamiento del programa Ejemplo 1 AFD. Expresión regular para un rango reconocido por el analizador léxico de 96

97 Documentación de AFD versión 3.x Expresión regular en archivo rango.er: // Expresión regular para especificación de rangos # CarEsp [abtnvfr] // las secuencias \x tienen x igual a algún // CarEsp # Otros [^\'\"\\\n] // Otros caracteres // expresión: '[' ( \ ( \ ' " CarEsp ) CarEsp Otros ) + ']' Llamada a AFD para DOS: AFD rango.er Rango Rng Salida de AFD (en salida estándar del DOS o archivo.txt si bajo Windows): Expresión regular: // Expresión regular para especificación de rangos # CarEsp [abtnvfr] // las secuencias \x tienen x igual a algún CarEsp // expresión: '[' ( \ ( \ ' " CarEsp ) CarEsp [^\'\"\\\n] ) + ']' Caracteres y/o rangos: (0, "abtnvfr"), (1, "["), (2, "\\"), (3, "\'"), (4, "\""), (5, "^\'\"\\\n"), (6, "]") Tabla de transiciones: Estado final: Ejemplo 2 Expresión regular para identificadores válidos del C++. Expresión regular en archivo identcmm.er: # letra [a-za-z_] # digito [0-9] letra ( letra digito ) * Llamada a AFD para DOS: AFD identcmm.er IdentCMM Salida de AFD (en salida estándar si bajo DOS o archivo.txt si bajo Windows): Expresión regular: # letra [a-za-z_] # digito [0-9] letra ( letra digito ) * Caracteres y/o rangos: (0, "a-za-z_"), (1, "0-9") Tabla de transiciones: Estado final: 1 Salida de AFD en archivo.cmm: // AFD generado por la versión 3.4 de ExpReg::Reasignar (Nov 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /

98 Documentación de AFD versión 3.x # ifndef EXPREG_H # include "expreg.h" # endif static const char eridentcmm[] = "# letra [a-za-z_] # digito [0-9]\r\nletra ( letra digito ) *\r\n\r\n"; static const char * vcidentcmm[] = { "a-za-z_", "0-9" ; static const Ent16ns tidentcmm[] = { 1, TNDEF, 1, 1 ; static const Ent16ns efidentcmm[] = { 1, 1 ; AutomFinDet afdidentcmm = { eridentcmm, vcidentcmm, 2, tidentcmm, efidentcmm ; 10.5 Descripción de la salida de AFD La salida de AFD v 3 consta de dos partes. Una parte es un texto explicativo del AFD generado a partir de la expresión regular, que aparece en formato similar al que se usa en la bibliografía. Se mencionará como primera parte. Se da en el archivo.txt si trabaja con la versión de Windows o en la salida estándar si trabaja bajo DOS. La que se muestra en formato C++ en el archivo.cmm es la implementación del AFD para su uso en un programa. Se mencionará en adelante como la segunda parte o fuente C++. En ambas partes se muestra primero la expresión regular, en la de C++ se usa el prefijo er por expresión regular. Lo que sigue es la enumeración de los caracteres que aparecen en la expresión regular. El prefijo usado en el fuente C++ es vc. En la primera parte se muestran como una enumeración de pares ordenados en donde la primera componente es el número asignado al caracter y la segunda componente es el caracter. El número asignado al caracter sirve para acceder a la columna correspondiente de la tabla de transición de estados del autómata. Luego se presenta la tabla de transición de estados del autómata finito determinístico generado. Por filas están los estados, comenzando por el 0. Por columnas están los caracteres que participan en la expresión regular. En la parte de formato texto la primera fila de esta tabla muestra los números de caracter, y la primera columna los números de estado. El número que aparece en la primera fila indica el número de caracter que va allí. Por ejemplo, el 0 98

99 Documentación de AFD versión 3.x indica que esa columna corresponde al primer caracter aparecido en la expresión regular, que es el primero que aparece en la enumeración de caracteres "Caracteres y/o rangos:". En el fuente C++ la tabla tiene nombre con prefijo t, se almacena por filas en un vector de enteros sin signo. El tamaño de este vector es: cantidad de estados por cantidad de caracteres. Los estados finales son almacenados en el vector de enteros sin signo que tiene prefijo vf. El primer elemento de éste vector indica el número de estados finales, a continuación están los estados finales. En la primera parte se muestran a continuación de la frase "Estado(s) final(es): ". La implementación del autómata finito determinístico es realizada con la clase AutomFinDet declarada en EXPREG.H. La declaración es como sigue: class AutomFinDet { public: const char * ExpRegular; const char * * Caracter; Ent16ns NumCar; const Ent16ns * Transicion, * EstFinales; ; en donde: ExpRegular: es una cadena de caracteres en formato C++ que define la expresión regular reconocida por éste autómata. Caracter: es un vector de cadena de caracteres que contiene los caracteres posibles como caracteres simples o como rangos. NumCar: es el número de caracteres, que coincide con el tamaño del vector Caracter y con el número de columnas de la tabla de transición de estados del autómata. Transicion: es la tabla de transición de estados del autómata finito determinístico que reconoce la expresión regular ExpRegular. Para una transición no definida se debe utilizar TNDEF o el número hexa 0xffff. EstFinales: es el conjunto de estados finales del AFD. El primer elemento de este vector indica el número de estados finales, a continuación, en los elementos siguientes, están los estados finales. El número de estados no se almacena en la estructura debido a que, como la tabla de transición de estados es generada automáticamente, su uso no llevará a transicionar a un estado inexistente. Pero si se almacena el número de 99

100 Documentación de AFD versión 3.x caracteres debido a que los mismos deberán pasar por un filtro de caracteres para poder ser atendidos por el autómata. El filtro funciona como un clasificador de caracteres; la clasificación (agrupación por rangos o grupos de caracteres) sirve para minimizar la cantidad de estados necesarios. El tipo Ent16ns es un typedef definido en TIPOS.H para obtener la portabilidad del código escrito usando compiladores de 16 bits a compiladores de 32 bits. La definición se hizo de la siguiente manera: typedef unsigned short int Ent16ns ; El tipo unsigned short int tiene un tamaño de 2 bytes (16 bits) en todas las implementaciones del C++ de la empresa Borland (los usados para el desarrollo de AFD). Si usted proyecta usar un compilador que no es de Borland, revise la implementación de ese tipo de dato para verificar que sea de 2 bytes. Si es de más de 2 bytes ocupará más espacio del necesario; si es menor, busque un tipo entero no signado que ocupe 2 bytes y cambie el typedef en TIPOS.H. Al cambiar de compilador no olvide recompilar la biblioteca ExpReg (expreg.lib). Puede ver ejemplos de definición de objetos AutomFinDet en el ejemplo 2 dado previamente, en la sección archivo.cmm generado Consideraciones finales Los AFD generados por esta versión pueden ser utilizados para implementar analizadores lexicográficos. Los analizadores lexicográficos constan en general de varios autómatas finitos determinísticos. Más información sobre la implementación de analizadores lexicográficos usando la salida de AFD puede encontrar en la documentación de la biblioteca ExpReg. Esta versión de AFD puede ser usada junto con el generador de analizadores sintácticos SLR1 para el desarrollo de herramientas de traducción (compiladores, intérpretes, traductor de un idioma a otro, etc.). La versión 2 de SLR1 genera automáticamente un analizador lexicográfico, dejándole como trabajo al usuario el de procesar todos los archivos.er con las expresiones regulares. Consulte la documentación de SLR1 v 2 o posterior para más detalle. 100

101 Documentación de SLR1 versión 1.x Capítulo 11: Documentación de SLR1 versión 1.x El texto de este capítulo es la documentación de SLR1 v 1.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación. Aunque esta versión del generador está disponible para el uso de terceros, el autor aconseja usar la versión 2.x o posterior (ver el capítulo 12) Introducción SLR1 es un programa para generar tablas de análisis sintáctico LR(1) simples a partir de la especificación de la gramática del lenguaje. Las tablas generadas por este programa serán usadas pasa la realización del análisis sintáctico de oraciones de ese lenguaje Descripción general del funcionamiento SLR1 es un programa que funciona bajo el sistema operativo DOS. No se prevee el desarrollo de una versión con la funcionalidad de esta versión para otro sistema operativo. Pero si se prevee una versión nueva de SLR1 para varios sistemas operativos, cuya sintaxis de entrada es distinta a ésta, y la funcionalidad altamente superior. La entrada de SLR1 v 1 es un archivo de texto plano con la gramática que se desea tratar. La salida del programa son 2 archivos: un archivo.cmm con las tablas que serán utilizadas para el análisis sintáctico en formato de fuente de C++ y un archivo.txt con las tablas de análisis sintáctico en formato similar al que se presenta en la bibliografía, con los estados y con los códigos asignados a los símbolos terminales y no terminales. El archivo.cmm es el que luego se utiliza para realizar el análisis sintáctico, incluyéndolo en la compilación junto con otros módulos fuentes del proyecto en desarrollo. 101

102 Documentación de SLR1 versión 1.x Sintaxis de la especificación de una gramática La metagramática que a continuación se presenta, describe la sintaxis que se debe usar para especificar una gramática que se usará como entrada de SLR1. Gramática ---> Reglas Reglas ---> Reglas Regla Reglas ---> Regla Regla ---> IdentIzq Flecha ListaSímbolos FinLínea ListaSímbolos ---> ListaSímbolos Símbolo ListaSímbolos ---> Las reglas se numeran de arriba a abajo de 1 a N si hay N reglas. Una gramática está formada por un conjunto de reglas (regla 1) no vacío (regla 3), es decir, al menos debe haber una regla. Una regla está formada por un identificador, una flecha y una lista de símbolos (regla 4), y cada regla ocupará una y sólo una línea (regla 4). La lista de símbolos que aparece entre la flecha y el fin de línea puede ser vacía (regla 6). Los identificadores que no aparecen a la izquierda de alguna regla en la gramática previamente dada son símbolos terminales, y serán explicados a continuación, como parte de la lexicografía a utilizar para la especificación de la gramática. La sintaxis que se usará es la de expresiones regulares. Identificador: [_a-za-zá-ñü-ü] [_a-za-zá-ñü-ü0-9] * Flecha: ( - ) + > Uno o más guiones o signos menos seguido de un signo mayor que. Símbolo: [^ \t] + Símbolo es una concatenación no vacía de caracteres distintos al espacio y al tabulado. 102

103 Documentación de SLR1 versión 1.x 11.3 Ejemplos de gramáticas Ejemplo 1 La gramática dada previamente, usada para explicar la estructura de una gramática (i.e. metagramática), es un ejemplo de gramática que se puede usar como entrada para SLR1. La regla 6 de esta gramática es una regla que deriva en lambda, es decir, ListaSímbolos es anulable. Archivo.CMM: unsigned noterm_long_pd[] = { 0x404, 0x101, 0x202, 0x201, 0x304, 0x402, 0x400 ; unsigned accion[] = { 0, 1, 0, 0, 0, 0, 0, 5, 0, 0, 16384, 0, 0, 0, 0, 32769, 1, 0, 0, 0, 32771, 32771, 0, 0, 0, 0, 0, 0, 32774, 32774, 32770, 32770, 0, 0, 0, 0, 0, 0, 8, 9, 32772, 32772, 0, 0, 0, 0, 0, 0, 32773, ; unsigned ir_a[] = { 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; Archivo.TXT: N = { (Gramática, 1), (Reglas, 2), (Regla, 3), (ListaSímbolos, 4) T = { (IdentIzq, 1), (Flecha, 2), (FinLínea, 3), (Símbolo, 4) (Gramática, 1) es el símbolo inicial. Seglas: Gramática' ---> Gramática Gramática ---> Reglas Reglas ---> Reglas Regla Reglas ---> Regla Regla ---> IdentIzq Flecha ListaSímbolos FinLínea ListaSímbolos ---> ListaSímbolos Símbolo ListaSímbolos ---> 103

104 Documentación de SLR1 versión 1.x Estado 0: Gramática' --->. Gramática Gramática --->. Reglas Reglas --->. Reglas Regla Reglas --->. Regla Regla --->. IdentIzq Flecha ListaSímbolos FinLínea Estado 1: Segla ---> IdentIzq. Flecha ListaSímbolos FinLínea Estado 2: Gramática' ---> Gramática. Estado 3: Gramática ---> Reglas. Reglas ---> Reglas. Regla Regla --->. IdentIzq Flecha ListaSímbolos FinLínea Estado 4: Reglas ---> Regla. Estado 5: Regla ---> IdentIzq Flecha. ListaSímbolos FinLínea ListaSímbolos --->. ListaSímbolos Símbolo ListaSímbolos --->. Estado 6: Seglas ---> Reglas Regla. Estado 7: Segla ---> IdentIzq Flecha ListaSímbolos. FinLínea ListaSímbolos ---> ListaSímbolos. Símbolo Estado 8: Segla ---> IdentIzq Flecha ListaSímbolos FinLínea. Estado 9: ListaSímbolos ---> ListaSímbolos Símbolo. Funciones acción(estado, simbolo) e ir_a (estado, símbolo): IdentIzq Flecha FinLínea Símbolo FA Gramática Reglas Regla ListaSímbolos 0 d d a d1... r r3... r r6 r r2... r d8 d r4... r r5 r Ejemplo 2 La gramática que se da a continuación especifica la sintaxis de una expresión aritmética. E --> E '+' T E --> T T --> T '*' F T --> F F --> '(' E ')' 104

105 Documentación de SLR1 versión 1.x F --> id Archivo.CMM: unsigned noterm_long_pd[] = { 0x305, 0x103, 0x101, 0x203, 0x201, 0x303, 0x301 ; unsigned accion[] = { 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 2, 32774, 32774, 32774, 0, 32774, 0, 16384, 7, 0, 0, 0, 0, 32770, 32770, 8, 0, 32770, 0, 32772, 32772, 32772, 0, 32772, 0, 0, 7, 0, 0, 9, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 2, 32773, 32773, 32773, 0, 32773, 0, 32769, 32769, 8, 0, 32769, 0, 32771, 32771, 32771, 0, 32771, 0 ; unsigned ir_a[] = { 3, 4, 5, 6, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; Archivo.TXT: N = { (E, 1), (T, 2), (F, 3) T = { ('+', 1), ('*', 2), ('(', 3), (')', 4), (id, 5) (E, 1) es el símbolo inicial. Reglas: E' ---> E E ---> E '+' T E ---> T T ---> T '*' F T ---> F F ---> '(' E ')' F ---> id Estado 0: E' --->. E E --->. E '+' T E --->. T T --->. T '*' F T --->. F F --->. '(' E ')' F --->. id 105

106 Documentación de SLR1 versión 1.x Estado 1: F ---> '('. E ')' E --->. E '+' T E --->. T T --->. T '*' F T --->. F F --->. '(' E ')' F --->. id Estado 2: F ---> id. Estado 3: E' ---> E. E ---> E. '+' T Estado 4: E ---> T. T ---> T. '*' F Estado 5: T ---> F. Estado 6: F ---> '(' E. ')' E ---> E. '+' T Estado 7: E ---> E '+'. T T --->. T '*' F T --->. F F --->. '(' E ')' F --->. id Estado 8: T ---> T '*'. F F --->. '(' E ')' F --->. id Estado 9: F ---> '(' E ')'. Estado 10: E ---> E '+' T. T ---> T. '*' F Estado 11: T ---> T '*' F. Funciones acción(estado, simbolo) e ir_a(estado, símbolo): '+' '*' '(' ')' id FA E T F 0.. d1. d d1. d r6 r6. r6. r d7.... a... 4 r2 d8. r2. r r4 r4. r4. r d7.. d d1. d d1. d r5 r5. r5. r r1 d8. r1. r r3 r3. r3. r

107 Documentación de SLR1 versión 1.x Ejemplo 3 La gramática siguiente reconoce expresiones aritméticas similares a la dada en el ejemplo 2, pero tiene el problema de que es ambigua. La ambigüedad de una gramática hace que la misma no sea LR(1) simple, por lo tanto no es directamente tratable con SLR1 ni con cualquier programa generador que trabaje con la técnica LR. El programa SLR1, al igual que otros como el YACC, permiten resolver la ambigüedad de la gramática permitiendo al usuario decidir qué es lo que el analizador sintáctico debería hacer en el momento de presentarse un conflicto. A tal efecto, el usuario de esos programas debe tener un conocimiento avanzado del algoritmo de análisis LR, lo que puede consultar en la bibliografía dada al final de este documento. E -> E '+' E E -> E '*' E E -> id Archivo.CMM: unsigned noterm_long_pd[] = { 0x103, 0x103, 0x103, 0x101 ; unsigned accion[] = { 0, 0, 0, 1, 32771, 32771, 32771, 0, 16384, 3, 4, 0, 0, 0, 0, 1, 0, 0, 0, 1, 32769, 32769, 4, 0, 32770, 32770, 32770, 0 ; unsigned ir_a[] = { 2, 0, 0, 5, 6, 0, 0 ; Archivo.TXT: N = { (E, 1) T = { ('+', 1), ('*', 2), (id, 3) (E, 1) es el símbolo inicial. Reglas: E' ---> E 107

108 Documentación de SLR1 versión 1.x E ---> E '+' E E ---> E '*' E E ---> id Estado 0: E' --->. E E --->. E '+' E E --->. E '*' E E --->. id Estado 1: E ---> id. Estado 2: E' ---> E. E ---> E. '+' E E ---> E. '*' E Estado 3: E ---> E '+'. E E --->. E '*' E E --->. id E --->. E '+' E Estado 4: E ---> E '*'. E E --->. E '+' E E --->. E '*' E E --->. id Estado 5: E ---> E '+' E. E ---> E. '*' E E ---> E. '+' E Estado 6: E ---> E '*' E. E ---> E. '+' E E ---> E. '*' E Funciones acción(estado, simbolo) e ir_a(estado, símbolo): '+' '*' id FA E 0.. d r3 r3. r3. 2 d3 d4. a. 3.. d d r1 d4. r1. 6 r2 r2. r2. En este ejemplo, las reglas 1 y 2 plantean un problema de ambigüedad (dos o más posibles análisis sintáctico para una misma oración). El programa SLR1 emitirá 4 mensajes de errores y dos posibles alternativas de solución pasa cada situación de conflicto, a saber: Mensaje 1: "En el estado 5 se debe elegir entre desplazar '+' y pasar a 3 y reducir por regla 1 con ese símbolo." 108

109 Documentación de SLR1 versión 1.x El mensaje significa que en la tabla, en la fila correspondiente al estado 5 (la que tiene el 5 a la izquierda), en la columna correspondiente al '+', se debe elegir entre colocar d5 o r1. De las dos opciones se eligió la segunda. Mensaje 2: "En el estado 5 se debe elegir entre desplazar '*' y pasar a 4 y reducir por regla 1 con ese símbolo." Las alternativas aquí son colocar d4 o r1 en la fila del estado 5, columna del símbolo '*'. En este caso se eligió desplazar y pasar al estado 4. Mensaje 3: "En el estado 6 se debe elegir entre desplazar '+' y pasar a 3 y reducir por regla 2 con ese símbolo." Las opciones son d3 o r2 con '+' en el estado 6. Se eligió reducir por regla 2. Mensaje 4: "En el estado 6 se debe elegir entre desplazar '*' y pasar a 4 y reducir por regla 2 con ese símbolo." Las opciones son d4 o r2 con '*' en el estado 6. Se eligió reducir por regla Consideraciones a tener el cuenta para el uso de gramáticas ambiguas El uso de gramáticas ambiguas puede dar como resultado tablas más pequeñas, lo que reduce el espacio de memoria necesario para almacenarlas. Pero el nivel de conocimiento necesario para poder resolver los conflictos es demasiado elevado. En la automatización de la generación de programas el objetivo es que el usuario (en este caso el programador) no pierda tiempo aprendiendo cosas que no hacen falta, ya que el generador hace ese trabajo y lo hace bien. El uso de gramáticas ambiguas imposibilita cumplir ese objetivo, y por lo general, el resultado no es "mucho mejor" que el de usar gramáticas no ambiguas. Para el ejemplo dado, la diferencia es infinitesimal, dada la disponibilidad de memoria en las computadoras actuales y la velocidad de procesamiento de las mismas. Hay gramáticas que no son ambiguas, pero presenta conflictos pasa la técnica LR(1) simple debido a la forma de trabajo del algoritmo (es decir, es ambigua para la técnica SLR(1) ). La gramática para la sentencia si...entonces...sino... es no ambigua pero presenta un conflicto de 109

110 Documentación de SLR1 versión 1.x desplazamiento-reducción con el símbolo sino y la regla SentSi --> si Expr entonces Sent. (Nota: en este caso se elige desplazar sino). El conflicto de desplazamiento-reducción que provoca esta gramática hace que SLR1 genere un analizador sintáctico para esa gramática que pueda generar 2 posibles árboles de análisis sintáctico para una oración que tenga la parte sino..., y es por eso que decimos que es ambigua para la técnica LR(1) simple Descripción de la estructura de las tablas generadas Las tablas del analizador sintáctico están implementadas en vectores en el archivo fuente.cmm generado. La documentación que aquí se incluye es a los efectos de que cualquier programador pueda hacer las modificaciones necesarias al archivo ANALIZAR.CMM donde se encuentra la función Analizar que realiza el análisis sintáctico. O bien, para que escriba una función nueva de análisis sintáctico, si la dada no satisface sus necesidades. (El archivo ANALIZAR.CMM así como los archivos ELEM.H y SLR1.H se incluyen al final de este documento, junto con los módulos fuentes del ejemplo 4.) Los vectores que se generan son unsigned noterm_long_pd[ ]; Este vector de enteros sin signo de 2 bytes (para compiladores C++ de 16 bits), contiene: En el elemento 0, el byte alto es el número de símbolos no terminales y el byte bajo es el número de terminales. Por ejemplo, en el ejemplo 2 noterm_long_pd[0] tiene el número 0x0305, que significa que hay 3 no terminales y 5 terminales. Estos números son usados para calcular el número de columnas de las tablas accion e ir_a (ver las tablas en archivos.txt, las columnas se indexan por símbolos terminal/no terminal). En los elementos 1 en adelante, el byte alto es el código del no terminal que se halla a la izquierda de la regla cuyo número es el índice del elemento actual, y el byte bajo de éste elemento especifica el número de símbolos que hay a la derecha de la flecha (las reglas se numeran de 1 en adelante, en el orden en que aparecen en el archivo fuente). Por ejemplo, en el ejemplo 1, el 110

111 Documentación de SLR1 versión 1.x elemento noterm_long_pd[6] que corresponde a la regla 6 "ListaSímbolos ---> " tiene el número 0x0400, que significa que el no terminal de la izquierda en la regla 6 es el que tiene el código 4 (ListaSímbolos, ver conjunto N del archivo.txt de salida), y que el número de elementos a retirar de la pila del analizador al hacer una reducción por regla 6 es 0 (ver que la regla deriva en lambda, esto es, ListaSímbolos es anulable). En el momento de ejecutar una reducción, se necesita la información contenida en estos elementos, para desapilar la cantidad adecuada de elementos y poder indexar la columna de la tabla ir_a que requiere el código del no terminal de la izquierda unsigned accion[ ]; Este vector de enteros sin signo de 2 bytes (para compiladores C++ de 16 bits), es la transcripción de la tabla acción que se muestra en el archivo.txt en formato más legible. La tabla acción mostrada en ese archivo comienza en el primer terminal y termina en el símbolo FA, que también es considerado terminal. Por filas se indexa por estado, y por columna se indexa por símbolo terminal. En este vector los elementos se codifican de la siguiente manera, lo que se muestra en formato C++: class Accion { // estructura de una acción public: unsigned n : 14; unsigned cod : 2; ; La clase Accion es una estructura de 16 bits, en los cuales los 14 bits menos significativos son los números que aparecen luego de la letra d o r en la tabla accion, y se acceden usando el campo n de esta estructura. El campo cod contiene el código de acción a ejecutar: 00 para desplazar, 01 para aceptar y 10 para reducir. El 11 no se utiliza. En el vector observará que: el elemento que tiene el es el que corresponde a aceptar, todos los elementos menores que ese número son los de desplazamiento (aparecen con la letra d en la tabla en el archivo.txt) y todos los mayores que ese número son los de reducción (aparecen con la letra r en la tabla en el archivo.txt). 111

112 Documentación de SLR1 versión 1.x unsigned ir_a[ ]; Este vector implementa la tabla ir_a, que en el archivo.txt se muestra a la par de la tabla accion a continuación del símbolo FA. Las columnas de esta tabla se indexan por símbolos no terminales y las filas por estados. La tabla se almacena en el vector por filas (fila mayor). Cada elemento contiene directamente el número que se muestra en la tabla ir_a del archivo.txt Descripción de la función Analizar La función Analizar que se encuentra en el archivo ANALIZAR.CMM y cuyo prototipo se da en el archivo SLR1.H a efectos de ser incluidos en el archivo que realizará el análisis sintáctico. El prototipo de la función es el siguiente: int Analizar( unsigned * ntlpd, unsigned * accion, unsigned * ir_a, unsigned char ( * analex)(), void (* accion_sem)( unsigned NumRegla ), void (* fver)( char * mensaje ) ); La función recibe 6 parámetros. Los tres primeros son las tres tablas que devuelve el programa SLR1 v 1. ntlpd es noterm_long_pd, accion es accion del.cmm e ir_a es ir_a, que ya fueron documentados. Los demás parámetros se documentan a continuación. 1. analex El parámetro analex es una función cuyo prototipo es similar a: unsigned char AnalizadorLexico(); es decir, una función que no recibe parámetros y que devuelve un unsigned char (entre 0 y 255). Esta función debe ser dada ya que es la que realiza el análisis léxico. Los dispositivos de E/S para obtener el fuente a esta función deben ser elegidos por el usuario de SLR1. Los códigos que debe devolver la función son: a. 0 para fin de archivo fuente. 112

113 Documentación de SLR1 versión 1.x b. 1 a N donde N es el número de símbolos terminales de la gramática. Los códigos a devolver deben coincidir con los dados en el conjunto T que se muestra en el archivo.txt de salida, según corresponda. c. Un número mayor que N si el símbolo que aparece a continuación es un símbolo desconocido. 2. accion_sem El parámetro accion_sem es la función que realizará las acciones semánticas para cada reducción de regla. El prototipo de la función es similar a la siguiente: void AccionSemantica( unsigned NumRegla ); En NumRegla viene el número de regla que se está por reducir. La función no devuelve nada. La intercomunicación entre esta función y la que realiza el análisis lexicográfico puede ser realizada usando variables globales, pero la mejor técnica es la del uso de la pila del analizador. En algunos casos es necesario modificar el prototipo de esta función a efectos de poder modificar los elementos de la pila del analizador. En este caso, el prototipo podría ser como sigue: void AccionSemantica( unsigned NumRegla, ElemPila * Pila, unsigned PuntPila ); Debe modificar el prototipo de Analizar en SLR1.H y en ANALIZAR. CMM (agregar los dos parámetros a la declaración del parámetro accion_sem). También debe pasar los parámetros nuevos en la llamada a esa función, dentro del código de Analizar; en el código figura: if (accion_sem!= 0) (*accion_sem)(a.n); y luego de modificarla debe figurar: if (accion_sem!= 0) (*accion_sem)(a.n, pila, pp); en donde pila y pp son variables internas de la función Analizar. 113

114 Documentación de SLR1 versión 1.x Al ser llamada la función accion_sem, los elementos de la regla figuran en la pila de la siguiente manera: sea la regla que se está por reducir (cuyo número viene en el parámetro NumRegla) la siguiente: A --> B C D entonces: A y B se encuentran en pila[pp + 1]. C se encuentra en pila[pp + 2]. D se encuentra en pila[pp + 3]. y así sucesivamente, para reglas de mayor número de símbolos. Observar que el no terminal de la izquierda de la regla y el primer símbolo de la derecha comparten la misma posición de la pila. El parámetro accion_sem puede ser ignorado en caso de no ser necesario. 3. fver El parámetro fver es una función que sirve para depurar analizadores sintácticos generados por medio de SLR1. Permite ver la pila y muestra las acciones que se van realizando en cada paso. El prototipo de la función es similar a la siguiente: void FuncionVer( char * ExplicacionDelPaso ); ExplicacionDelPaso es una cadena de caracteres en donde se explica la acción ejecutada o se muestra el estado de la pila del analizador sintáctico. Dentro de la FuncionVer usted puede incluir código que imprima esa cadena en pantalla, para ir viendo el progreso del análisis sintáctico. El parámetro fver puede ser ignorado. La función Analizar devuelve 0 si hay problemas en las tablas o error de sintaxis, y 1 si la oración analizada es sintácticamente correcta. 114

115 Documentación de SLR1 versión 1.x La función no modifica las tablas, y puede ser llamada las cantidades de veces que desee en el programa que se la incluya, inclusive puede usar varios juegos de tablas, las que podrían ser leídos desde un archivo binario La pila del analizador sintáctico SLR1 La pila del analizador sintáctico SLR1 está implementada en un vector, y se declara dentro de la función Analizar. La estructura de los elementos de esta pila es por defecto la que define la clase ElemPila declarada en ELEM.H, declaración que se transcribe: class ElemPila { public: Simbolo s; unsigned e; ElemPila( ) { s.cod = s.noterminal = 0; e = 0; ; El campo s sirve para identificar el símbolo que se encuentra en ese elemento de la pila. El campo e sirve para identificar el estado correspondiente a esa posición; bajo ningún concepto debe modificar este campo. Esta clase usa la clase Simbolo que también se declara en ELEM.H. No es aconsejable eliminar esos campos de la clase ElemPila, así como tampoco la implementación de la misma (la pila está implementada como vector). Pero si es necesario, puede agregar elementos en la declaración de la clase según necesite para las acciones semánticas. La siguiente es una declaración ejemplo: class ElemPila { // estructura de un elemento de la pila del analizador public: Simbolo s; unsigned e; char Cadena[30]; // a efectos de guardar la cadena de texto que // devuelve el analizador lexicográfico ElemPila( ) { s.cod = s.noterminal = 0; e = 0; ; El acceso al campo Cadena en la función de acciones semánticas sería: pila[pp + X].Cadena, donde X es el número del símbolo de la izquierda de la regla que se desea acceder. 115

116 Documentación de SLR1 versión 1.x 11.8 Uso del programa SLR1 v 1 Para iniciar SLR1 debe ejecutar el programa SLR1.EXE, desde cualquier directorio usando sintaxis de posicionamiento de archivo absoluta o bien desde el directorio donde éste se encuentra. El menú que se muestra luego de cargar el programa tiene dos opciones: cargar gramática y salir. Al elegir cargar gramática, en una ventana de diálogo se pide el nombre del archivo donde se encuentra la gramática. Debe ingresar cualquier nombre de archivo válido para el DOS, y, si no incluye extensión, se asumirá que la extensión es.glc (por gramática libre de contexto). Los archivos de salida.cmm y.txt serán generados en el mismo directorio donde se encuentra el archivo de entrada, para facilitar su localización. En el menú que se muestra luego de aceptar el nombre del archivo de entrada, la opción proceder realiza la traducción, la opción volver atrás cancela la operación sin hacer nada, y las demás opciones son banderas que controlan la salida del programa. La gramática, los estados, las tablas y los códigos de los símbolos se muestran en el archivo.txt, y el código fuente en el archivo.cmm. Si por ejemplo desactiva la impresión del código fuente, no se generará el archivo.cmm. Para que no se genere el archivo.txt debe desactivar las opciones de toda la información que se imprime en ese archivo (debe desactivar todas las opciones menos la de imprimir fuente). En el caso de que se presente un conflicto, la información se presenta en una ventana de diálogo, y el usuario deberá elegir entre dos opciones para resolver el conflicto. Para la resolución de conflictos debe consultar la bibliografía. Al finalizar la generación de las tablas, el programa vuelve al menú principal a efectos de proceder con otro archivo, si es que así lo desea Ejemplo 4: Desarrollo de un proyecto. El proyecto a desarrollar es un sistema para la verificación de la validez de un esquema deductivo del cálculo proposicional. 116

117 Documentación de SLR1 versión 1.x Para que un esquema deductivo sea válido debe primero ser sintácticamente correcto. Luego, la validez puede ser demostrada de dos maneras: utilizando la teoría de la demostración o utilizando la teoría semántica del cálculo proposicional. En este ejemplo se utilizará la teoría semántica del cálculo proposicional. En la teoría semántica del cálculo proposicional, un sistema deductivo es válido cuando la implicación asociada a éste es tautología. Entonces, para verificar la validez de un esquema deductivo se ejecutarán las siguientes operaciones: 1. Primero se realizará el análisis sintáctico del esquema deductivo fuente, a efectos de detectar errores sintácticos. 2. Luego se traducirá el esquema deductivo a su implicación asociada, la que será internamente almacenada en un vector en notación postfijo, a efectos de facilitar su posterior evaluación. Al traducir se generará una tabla con todas las variables que figuran en la fórmula. 3. A continuación se comprueba si la implicación asociada es una tautología, para lo cual se van generando las interpretaciones posibles para las variables que aparecen en la fórmula, y para cada una se comprueba el valor de verdad de la fórmula. Si para alguna interpretación la fórmula da falso, la misma no es tautología. Si ocurre que la fórmula si es tautología, el esquema deductivo es válido. 4. Se informa el resultado del paso 3. Los archivos importantes que componen el proyecto son los que se enumeran a continuación, y son independientes del sistema operativo en el que se implemente la aplicación: ED.GLC: entrada para SLR1 v 1. La salida de SLR1 son los archivos ED.CMM que debe ser compilado y encadenado con el proyecto y el archivo ED.TXT en donde se encuentran los comentarios. ED.CMM: salida de SLR1 al especificar como entrada a ED.GLC. Este archivo contiene la gramática en formato C++. EVALUAR.CMM: en este módulo se dan las funciones para evaluar una fórmula bien formada del cálculo proposicional dada en notación postfijo. 117

118 Documentación de SLR1 versión 1.x ASIGINT.CMM: en este módulo se da una función que traduce una interpretación codificada en binario, cargando en cada variable de la tabla de símbolos dada el valor de verdad correspondiente. TAUTO.CMM: en este módulo se da una función que verifica si una fórmula dada en notación postfijo es o no tautología. VESQDED.CMM: en este módulo se da una función que verifica un esquema deductivo dado como entrada. Se podría considerar a este módulo como el principal. ALED.CMM: en este módulo están el analizador lexicográfico y la función de acciones semánticas que realizan la traducción. ANALIZAR.CMM: este módulo viene provisto junto con SLR1 v 1, y contiene la función Analizar que realiza el análisis sintáctico usando las tablas generadas por SLR1 a partir de la gramática. ESQDED.H: este es un archivo cabecera en donde se declaran las clases y prototipos de las funciones desarrolladas, a los efectos de poder reusar fácilmente los módulos fuentes escritos. SLR1.H: declaración (prototipo) de la función Analizar. Viene provisto junto con SLR1 v 1. ELEM.H: declaración de las clases usadas para manejar las estructuras de datos utilizadas durante el análisis sintáctico. Viene provisto junto con SLR1 v 1. La interfase con el usuario final de la aplicación ha sido generada con el Application Expert (AppExpert) del Turbo C para Windows; la aplicación correrá sin problemas en Windows 3.1 en adelante, incluyendo a Windows NT. El código generado puede ser recompilado sin problemas usando el Borland C para OS/2, y obtener así una versión que correrá en OS/2 2.1 y OS/2 Warp, pero se debe tener cuidado con ese C++ puesto que es de 32 bits (en C++ no se dan especificaciones sobre la implementación de los tipos de datos fundamentales, la misma queda a elección del fabricante del compilador; por ejemplo, en C++ para Win16 el tamaño del tipo int es de 16 bits y en C++ para OS/2 y Win32 es de 32 bits). La aplicación generada es un editor multitexto (se pueden editar varios textos a la vez) con todas las funciones de un editor de textos, y en el menú archivo se agregó una opción para "Verificar validez" del esquema deductivo que se encuentra en la ventana de texto actual (la que se encuentre activa). La generación de la aplicación es en realidad más simple de lo que se pueda 118

119 Documentación de SLR1 versión 1.x entender a partir de la lectura de este documento (fue muy fácil), y es esa la razón por la que se eligió AppExpert para diseñar la interfase. Los archivos generados son: EDAPP.CMM: módulo principal. EDEDITVW.CMM: clase que maneja un objeto editor de texto. A este módulo se lo modificó para procesar la opción "Verificar validez". EDMDICLN.CMM: clase para manejar un objeto cliente de un objeto MDI. EDMDICHL.CMM: clase para manejar un objeto hijo de un objeto MDI. APXPREV.CPP: presentación preliminar de un documento editado. APXPRINT.CPP: objeto para imprimir un documento. EDABTDLG.CPP: ventana Acerca de... de la aplicación. APXPREV.H: cabecera de APXPREV.CPP. APXPRINT.H: cabecera de APXPRINT.CPP. EDABTDLG.H: cabecera de EDABTDLG.CPP EDAPP.H: cabecera de EDAPP.CMM. EDEDITVW.H: cabecera de EDEDITVW.CMM. EDMDICLN.H: cabecera de EDMDICLN.CMM. EDMDICHL.H: cabecera de EDMDICHL.CMM. Además se utilizó la biblioteca de clases BCE, la que se encuentra disponible para DOS, Windows y OS/2 (para modo caracter y Presentation Manager). Los módulos no se enumeran por ser un producto comercial. Se usaron únicamente la clase Ascii0 (en EDEDITVW.CMM para almacenar el texto) y la función SimbSig en el analizador lexicográfico (en ALED.CMM, para simplificar el mismo). La clase EDEditView, cuya definición está en EDEDITVW.CMM, y que sirve para manejar una vista de un documento de texto (el generador AppExpert da soporte al modelo documento/vista introducido por Borland en sus C++ desde la versión 4.0 para Windows y 2.0 para OS/2), fue modificada para manejar el evento CM_VERIFICAR que se genera al seleccionar la opción "Verificar validez". La función agregada es la VerificarValidez de esta 119

120 Documentación de SLR1 versión 1.x clase, cuya declaración y prototipado inicial fue realizado por ClassExpert, el administrador de clases de AppExpert. La función se transcribe a continuación, y el código agregado es el que está después del comentario // INSERT>>... y termina en la llamada a MessageBox final. (Se aclara nuevamente que el resto del código, salvo dos líneas que se encuentran al comienzo de éste módulo, no ha sido escrito por el autor). void EDEditView::VerificarValidez () { // INSERT>> Your code here. // Primero sacar el texto del EditView de Windows Ent16ns tam = GetTextLen() + 1; Ascii0 cad(tam); if (! GetText(cad, tam)) return; // si no hay texto editado // Luego llamar a la función que verifica validez: int r = ValidarEsqDed(cad); // y finalmente se informa el resultado: const char * msj = "CODIGO DESCONOCIDO"; uint icono = MB_OK; switch (r) { case EDVAL: msj = "El esquema deductivo es VALIDO."; icono = MB_ICONINFORMATION; break; case EDINVAL: msj = "El esquema deductivo es INVALIDO."; icono = MB_ICONEXCLAMATION; break; case EDERROR: msj = "Error sintáctico en el fuente."; icono = MB_ICONQUESTION; break; MessageBox(msj, "Resultado del análisis", icono); La única sentencia importante en esta función es la llamada a ValidarEsqDed, que es la función que actualmente ejecuta toda la operación de validación. Todas las sentencias antes de ésta sirven para extraer el texto del objeto ventana editor del Windows (a través de la clase TEdit, clase base de TEditView y EDEditView). El código siguiente a la llamada a ValidarEsqDed es el que presenta el resultado en una ventana de mensajes. El problema de obtener el texto de entrada e informar el resultado de la validación es un problema aparte, y se puede realizar como uno quiera y en el sistema operativo que desee. También se agregaron dos líneas más al comienzo del módulo EDEDITVW.CMM, que son las siguientes: # include <ascii0.h> // de BCE # include "esqded.h" // de esta aplicación 120

121 Documentación de SLR1 versión 1.x Los.h incluidos hacen posible la utilización de la clase Ascii0 (para obtener el texto) y de la función ValidarEsqDed (que no está definida en este módulo). El código restante (enumerado en el primer grupo de módulos fuentes) es el que realiza todo el trabajo de validación. No contempla la forma de obtener el texto ni la forma de emitir mensajes (ambas dependientes del sistema operativo). Este conjunto de módulos fuentes son independientes del sistema operativo, inclusive son independiente de si trabaja con código ANSI u OEM (los editores de Windows trabajan con OEM, los de DOS y algunos de OS/2 con ANSI). La gramática que se dará de entrada a SLR1 en el archivo de texto plano ED.GLC es la siguiente: E --> Prem '#' F Prem --> Prem ',' F Prem --> F F --> F '<==>' I F --> I I --> I '==>' D I --> D D --> D '+' C D --> C C --> C '*' Prop C --> Prop Prop --> c Prop --> v Prop --> '(' F ')' Prop --> - Prop En esta gramática, el símbolo # sirve para decir que a continuación viene la conclusión, la coma separa a las premisas (debe haber por lo menos una). Cada premisa debe ser una fórmula bien formada del cálculo proposicional. El símbolo <==> se utiliza para la doble implicación, el ==> se utiliza para el la implicación, el + para la disyunción, el * para la conjunción, el - para la negación y los ( ) para la asociatividad y ruptura de precedencia. Los tres siguientes son ejemplos de esquemas deductivos reconocibles por la gramática dada: Ejemplo 1: p ==> q, p # q Ejemplo 2: 121

122 Documentación de SLR1 versión 1.x -p + q, p # q Ejemplo 3: - ( p * r ), q ==> r, q # -p Los esquemas dados le pueden ser familiares. Los puede encontrar en algún libro de lógica. La forma en que se escriban las fórmulas no debe ser importante, inclusive los espacios que se dejan entre ellas. Este problema será resuelto por el analizador lexicográfico que se da en el archivo ALED.CMM. El prototipo del analizador lexicográfico es similar al dado en páginas anteriores. Los códigos que devuelven coinciden con los indicados por SLR1 v 1 en el conjunto T de símbolos terminales, el cual se transcribe: T = { ('#', 1), (',', 2), ('<==>', 3), ('==>', 4), ('+', 5), ('*', 6), (c, 7), (v, 8), ('(', 9), (')', 10), (-, 11) En el conjunto se muestra cada terminal como un par ordenado en donde el primer componente es la cadena de caracteres usado en la gramática fuente y el número dado en el segundo componente es el código asignado. Debido a que los códigos que se le asignan a los terminales pueden cambiar con sólo cambiar el orden de las reglas, se #definen macros para tener independencia entre código fuente y los códigos asignados (ver ALED.CMM). El analizador lexicográfico mantiene el último identificador de variable o constante en la variable global ult_c_v definida en ALED.CMM a los efectos de no tener que modificar la estructura de los elementos de la pila para acceder a la cadena de caracteres usada en el fuente, durante el análisis sintáctico. La entrada al analizador sintáctico es una cadena de caracteres que se fija con la función FijarFuente definida en ALED.CMM. La función de análisis lexicográfico será llamada por la función Analizar a medida que se necesite un símbolo de entrada. La función de acciones semánticas cuyo nombre es AccSemEsqDed y está definida en ALED.CMM, traduce el esquema deductivo en notación entrefijo del fuente a notación postfijo. La fórmula resultante se guarda en el vector fnp, desde el elemento 0 en adelante; la variable global ifnp tiene el 122

123 Documentación de SLR1 versión 1.x tamaño de fnp. En una tabla de símbolos que se implementa en un vector con nombre tds y tamaño itds se almacenan las variables que aparecen en la fórmula, una copia por variable. Esta tabla de símbolos se utiliza para asignar la interpretación a las variables y poder luego evaluar la fórmula. Observar en el código de AccSemEsqDed que al reducir por regla 1 (E---> Prem # F ) o 6 ( I --> I '==>' D ), a los operadores '#' y '==>' se los traduce a '==>'. En este caso, el '#' puede interpretarse como un '==>' de menor precedencia. Lo mismo ocurre con la reducción por reglas 2 ( Prem --> Prem ',' F ) o 10 ( C --> C '*' Prop ), en donde los operadores ',' y '*' se traducen a '*', esto es, ',' tiene el mismo significado (semántica) que '*', sólo que es un operador de menor precedencia. (Como ejercicio para el lector se propone hacer la tabla de precedencia de operadores para la gramática dada.) Observar también que en la reducción de las reglas 12 y 13, el símbolo que nos interesa es exactamente el anterior al actual, y se usa la variable ult_c_v para accederlo. La función que verifica si la fórmula proposicional en notación postfijo es tautología se llama Tautologia y se encuentra definida en el módulo TAUTO.CMM. La misma genera interpretaciones de las variables que aparecen en la fórmula hasta encontrar alguna interpretación para la que la fórmula dé falso. La interpretación (un juego de valores de verdad asignado a las variables de la fórmula) se codifica en un número binario de 16 bits, en donde el bit 0 tiene el valor de verdad de la variable tds[0], el bit 1 a tds[1], y así sucesivamente. Como el número tiene 16 bits, a lo sumo pueden haber 16 variables distintas posibles. El pasaje de binario a la tabla de símbolos de una interpretación lo realiza la función AsigInterp definida en ASIGINT.CMM. La función Evaluar realiza la evaluación de una fórmula proposicional dada en postfijo para una interpretación dada. Más documentación puede encontrar en los archivos fuente. A continuación se transcriben los módulos fuentes enumerados en el primer grupo de módulos fuentes, que son los que realmente se escribieron para esta aplicación. Los demás módulos fueron generados automáticamente, y sólo son importantes las líneas transcriptas a éste documento, y por tal razón no se incluyen aquí. 123

124 Documentación de SLR1 versión 1.x Módulos fuentes escritos para el ejemplo ED.GLC E --> Prem '#' F Prem --> Prem ',' F Prem --> F F --> F '<==>' I F --> I I --> I '==>' D I --> D D --> D '+' C D --> C C --> C '*' Prop C --> Prop Prop --> c Prop --> v Prop --> '(' F ')' Prop --> - Prop ED.CMM????????? No se incluye por ser salida de SLR1 v 1 y generable a partir de ED.GLC EVALUAR.CMM // evaluar.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" # include <string.h> // int ValorDe( const char * s, const SimbFormProp * tds, unsigned ttds ); // Esta función busca el símbolo cuya cadena es s en el vector tds cuyo // tamaño es ttds. // Debido a que para la comparación de cadenas se usa strcmp, se hace // diferencia entre mayúsculas y minúsculas. // La función devuelve el valor de verdad asignado a la variable. static int ValorDe( const char * s, const SimbFormProp * tds, unsigned ttds ) { register int i; for (i = 0; i < ttds; ++i) if (! strcmp(s, tds[i].var)) break; if (i == ttds) return 0; else return tds[i].valor; 124

125 Documentación de SLR1 versión 1.x // int Evaluar( char p, char q, int operacion ); // Evaluar evalúa la operacion pasada como parámetro a partir de los // operandos p y q. // En p y q debe venir 0 si es falso o distinto a 0 si es verdadero. // La función devuelve 0 si el resultado de la operación es falso y // distinto a 0 si el resultado de la operación es verdadero. // La operación se codifica con la enumeración dada en simbolo.h dentro // de la clase SimbFormProp, y se acceden desde cualquier lugar usando el // operador :: para romper ámbitos. Ellos son: // DOBLEIMPL: <==> doble implicación, binario, p <==> q // IMPLIC: ==> implicación, binario, p ===> q // DISYUN: + disyunción, binario, p + q // CONJUN: * conjunción, binario, p * q // NEGAR: - negación, unario, - p int Evaluar( char p, char q, int operacion ) { p = p? 1 : 0; // llevar a 0 o 1 q = q? 1 : 0; register int r = 0; switch (operacion) { case SimbFormProp::DOBLEIMPL: // <==> r =! (p ^ q); break; case SimbFormProp::IMPLIC: // ==> r =! p q; break; case SimbFormProp::DISYUN: // + (disyunción) r = p q; break; case SimbFormProp::CONJUN: // * (conjunción) r = p & q; break; case SimbFormProp::NEGAR: // - (negación) r =! p; break; return r; // int Evaluar( const SimbFormProp * fnp, unsigned tfnp, // const SimbFormProp * tds, unsigned ttds ); // Evaluar evalúa la expresión en postfijo pasado en el vector fnp, cuyo // tamaño es tfnp. // La interpretación a utilizar es la que viene en la tabla de símbolos tds, // cuyo tamaño es ttds. // La función devuelve 0 si la fórmula bien formada en notación postfijo fnp // es falsa para la interpretación dada, y distinto a 0 si es verdadera. // El algoritmo que se usa para evaluar la expresión en notación postfijo // puede ser encontrado en cualquier libro de Estructuras de datos y // Algoritmos. int Evaluar( const SimbFormProp * fnp, unsigned tfnp, const SimbFormProp * tds, unsigned ttds ) { char pila[20], pp = 0; register unsigned ifnp; for (ifnp = 0; ifnp < tfnp; ++ifnp) { if (fnp[ifnp].cod > SimbFormProp::MAXVAR && 125

126 Documentación de SLR1 versión 1.x fnp[ifnp].cod < SimbFormProp::ULTIMOOPERADOR) { // efectuar operación if (fnp[ifnp].cod == SimbFormProp::NEGAR) { // operación unaria, desapila 1 y apila el resultado pila[pp - 1] = Evaluar(pila[pp-1], 0, fnp[ifnp].cod); else { // operación binaria, desapila 2 y apila el resultado pila[pp - 2] = Evaluar(pila[pp-2], pila[pp-1], fnp[ifnp].cod); --pp; else pila[pp++] =! fnp[ifnp].cod? fnp[ifnp].valor : ValorDe(fnp[ifnp].var, tds, ttds); return pila[pp-1]; ASIGINT.CMM // asigint.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" // void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ); // AsigInterp asigna a la tabla de símbolos la interpretación que viene // codificada en vv. // Si numeramos los bits de vv de 0 a 15, el bit 0 corresponde a tds[0], // el bit 1 a tds[1],..., y el bit 15 a tds[15]. // El valor de verdad de cada variable se asigna al campo valor de cada // elemento de tds. void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ) { register int i, bit; for (i = 0, bit = 1; i < ttds; ++i, bit <<= 1) tds[i].valor = vv & bit; TAUTO.CMM // tauto.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" // int Tautologia( const SimbFormProp * fnp, unsigned tfnp, // SimbFormProp * tds, unsigned ttds ); // Tautologia chequea si la fórmula bien formada del cálculo proposicional // que viene en fnp en formato notación postfijo es una tautología. // El tamaño de la fórmula es tfnp (número de elementos del vector). // La tabla de símbolos que se recibe como parámetros es a los efectos de // poder generar las interpretaciones posibles. El tamaño de la tabla de // símbolos (vector) viene dado en ttds. 126

127 Documentación de SLR1 versión 1.x // La función devuelve 0 si no es tautología y distinto a 0 si lo es. int Tautologia( const SimbFormProp * fnp, unsigned tfnp, SimbFormProp * tds, unsigned ttds ) { int i, t; t = (1 << ttds) - 1; // 2 elevado a la número de variables menos 1 for (i = 0; i <= t; ++i) { AsigInterp(tds, ttds, i); if (! Evaluar(fnp, tfnp, tds, ttds)) // si alguno es falso break; // entonces no es tautología return i > t; // si se probaron todas las interpretaciones // entonces es tautología VESQDED.CMM // vesqded.cmm validación de esquemas deductivos. // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" # include "slr1.h" // función Analizar // Variables definidas en aled.cmm extern SimbFormProp fnp[ ], tds[ ]; extern unsigned ifnp, itds; // Los siguientes se encuentan en ed.cmm (generado por SLR1 v 1) extern unsigned noterm_long_pd[ ], accion[ ], ir_a[ ]; // AccSemEsqDed son las acciones semánticas que traducen a postfijo a la // fórmula. Ver documentación en aled.cmm. void AccSemEsqDed( unsigned numregla ); int ValidarEsqDed( const char * EsqDed ) { FijarFuente(EsqDed); if ( Analizar(noterm_long_pd, accion, ir_a, AnaLexEsqDed, AccSemEsqDed) ) return Tautologia(fnp, ifnp, tds, itds)? EDVAL : EDINVAL; else return EDERROR; ALED.CMM // aled.cmm // Funciones para el analizador lexicográfico. // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" # include <string.h> # include <fvarias.h> // de la biblioteca BCE se usa SimbSig const char * cadena = 0, * psimb = 0; 127

128 Documentación de SLR1 versión 1.x SimbFormProp fnp[50], // fórmula en notación postfijo tds[16]; // tabla de símbolos unsigned ifnp = 0, itds = 0; // void FijarFuente( const char * cad ); // FijarFuente fija a cad como el texto fuente a analizar. // El texto puede ser cargado por una simple lectura binaria a un archivo // de texto, pidiendo que lea tantos bytes como la longitud del archivo y // recibirlo en un bloque de caracteres de ese tamaño más uno. Al último // elemento le debe poner un 0. void FijarFuente( const char * cad ) { psimb = (char *) ( cadena = cad ); ifnp = itds = 0; // int AgregarTDS( const char * s ); // Agrega s a la tabla de símbolo. s es la cadena que identifica al símbolo. // Si la variable ya existe en la tabla entonces no lo agrega. // Siempre devuelve el código asignado a la variable, que es un número // entre 1 y 16. int AgregarTDS( const char * s ) { register int i; for (i = 0; i < itds; ++i) if (! strcmp(tds[i].var, s)) break; if (i == itds) { strncpy(tds[itds].var, s, 10); tds[itds].valor = 0; ++itds; tds[itds].cod = itds; return itds; // devuelve el código que se le asigna return tds[i].cod; // dev. el cód. asignado anteriormente // Las cadenas de caracteres siguientes sirven para trabajar con SimbSig. // El ^ del comienzo significa cualquier caracter menos los que se enumeran // a la derecha. La sintaxis de las cadenas dadas es la misma que usa LEX // de UNIX para la especificación de rangos de caracteres. // Puede consultar la documentación de la función SimbSig de BCE o la // documentación del LEX en cualquier manual de UNIX, XENIX o LINUX. static char * blanco = "^ \n\r\t", * ident = "^0-9_a-zA-ZáéíóúñÑüÜ", * cons = "^01", // 0 falso, 1 verdadero * neg = "^-", * disy = "^+", * conj = "^*", * impl = "^=>", * doblimp = "^<=>", * a_par = "^(", * c_par = "^)", * coma = "^,", * conclu = "^#"; // SLR1 v1 informó en el archivo ed.txt (luego de procesar a ed.glc) que // los símbolos terminales deben tener asignados los siguientes códigos: // T = { ('#', 1), (',', 2), ('<==>', 3), ('==>', 4), ('+', 5), // ('*', 6), (c, 7), (v, 8), ('(', 9), (')', 10), (-, 11) // Se utilizan sentencias #define para trabajar independientemente de 128

129 Documentación de SLR1 versión 1.x // los códigos que asigne SLR1 v1 (puede cambiarlos si cambiamos la gramática). # define IDENT 8 # define CONS 7 # define NEG 11 # define DISY 5 # define CONJ 6 # define IMPL 4 # define DOBLIMP 3 # define A_PAR 9 # define C_PAR 10 # define COMA 2 # define CONCLU 1 # define NO_SE 12 char simbolo[100], // aquí devuelve el analizador léxico la cadena encontrada ult_c_v[100]; // último constante o variable. Se usa para las acciones // a efectos de no implementar una pila especial. // unsigned char AnaLexEsqDed(); // Realiza el análisis lexicográfico de un esquema deductivo del cálculo // proposicional. // El prototipo de esta función coincide con el requerido por la SLR1 v 1. // Devuelve el código del símbolo léxico encontrado, 0 si es fin de archivo. // Previo al uso de la función, las variables internas deben ser // inicializadas llamando a la función FijarFuente y pasando como parámetro // la cadena de texto a analizar (la que contiene el esquema deductivo). unsigned char AnaLexEsqDed() { psimb = SimbSig(psimb, simbolo, blanco); // saltear blancos if (psimb = SimbSig(psimb, simbolo, cons), *simbolo) { strcpy(ult_c_v, simbolo); return CONS; if (psimb = SimbSig(psimb, simbolo, ident), *simbolo) { strcpy(ult_c_v, simbolo); return IDENT; if (psimb = SimbSig(psimb, simbolo, neg), *simbolo) return NEG; if (psimb = SimbSig(psimb, simbolo, disy), *simbolo) return DISY; if (psimb = SimbSig(psimb, simbolo, conj), *simbolo) return CONJ; if (psimb = SimbSig(psimb, simbolo, impl), *simbolo) return IMPL; if (psimb = SimbSig(psimb, simbolo, doblimp), *simbolo) return DOBLIMP; if (psimb = SimbSig(psimb, simbolo, a_par), *simbolo) return A_PAR; if (psimb = SimbSig(psimb, simbolo, c_par), *simbolo) return C_PAR; if (psimb = SimbSig(psimb, simbolo, coma), *simbolo) return COMA; if (psimb = SimbSig(psimb, simbolo, conclu), *simbolo) return CONCLU; if (psimb!= 0 && *psimb) return NO_SE; else return 0; // void AccSemEsqDed( unsigned numregla ); // Esta función realiza las acciones semánticas durante el análisis sintáctico. // La forma de trabajo de estas acciones semánticas se basa en la traducción // a postfijo de la expresión fuente. Es similar a la que se hace como // ejercicio en los cursos de compiladores y la puede encontrar en la // bibliografía de construcción de compiladores e intérpretes. // Fundamentos de las acciones semánticas dadas: // - Un esquema deductivo puede ser traducido a una fórmula bien formada 129

130 Documentación de SLR1 versión 1.x // del cálculo proposicional, concatenando las premisas con conjunciones // y hacer que la fórmula resultante implique la conclusión. // - A tal efecto, el # se considera como un ==> con precedencia mucho menor // que todos los demás operadores, y la coma (,) como el * (conjunción) // pero con precedencia mayor que el # y menor que el resto de los // operadores. // - A partir de los dos puntos previos, el esquema deductivo ya viene con // la forma de la implicación asociada, y el único trabajo que queda // es traducirla a notación postfijo para poder evaluarla. void AccSemEsqDed( unsigned numregla ) { switch (numregla) { case 1: // E ---> Prem '#' F case 6: // I ---> I '==>' D fnp[ifnp].cod = SimbFormProp::IMPLIC; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "==>"); ++ifnp; break; case 2: // Prem ---> Prem ',' F case 10: // C ---> C '*' Prop fnp[ifnp].cod = SimbFormProp::CONJUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "*"); ++ifnp; break; case 4: // F ---> F '<==>' I fnp[ifnp].cod = SimbFormProp::DOBLEIMPL; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "<==>"); ++ifnp; break; case 8: // D ---> D '+' C fnp[ifnp].cod = SimbFormProp::DISYUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "+"); ++ifnp; break; case 12: // Prop ---> c fnp[ifnp].cod = SimbFormProp::CONSTANTE; fnp[ifnp].valor = *ult_c_v == '0'? 0 : 1; strncpy(fnp[ifnp].var, ult_c_v, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; break; case 13: // Prop ---> v // Escribir en la expresión en notación postfijo fnp[ifnp].cod = AgregarTDS(ult_c_v); // agrega a tds y asigna el código fnp[ifnp].valor = 0; strncpy(fnp[ifnp].var, ult_c_v, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; break; case 15: // Prop ---> - Prop fnp[ifnp].cod = SimbFormProp::NEGAR; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "-"); 130

131 Documentación de SLR1 versión 1.x ++ifnp; break; ANALIZAR.CMM Este módulo viene provisto con SLR1 v 1. Se lo incluye a modo de ilustración. // Analizar() # include "slr1.h" # include "elem.h" # include <ascii0.h> void MostrarPila( void (* fver)( char * mensaje ), ElemPila * p, unsigned pp ) { if (fver == 0) return; (*fver)("pila: "); Ascii0 cad; unsigned c = 0; while (c <= pp) { cad.printf("%u%c,%u ", p[c].s.cod, p[c].s.noterminal? 'n' : 't', p[c].e); (*fver)(cad); ++c; (*fver)("\n"); int Analizar( unsigned * ntlpd, unsigned * accion, unsigned * ir_a, unsigned char (* analex)(), void (* accion_sem)( unsigned numregla ), void (* fver)( char * mensaje ) ) { if (analex == 0) return 0; ElemPila pila[100]; Accion a; unsigned x1,x2,x3; // variables auxiliares unsigned numiter; unsigned pp = 0; // puntero de pila pila[0].e = 0; // estado inicial. Ascii0 msj; unsigned char s, nt = ntlpd[0] >> 8, t = ntlpd[0] + 1; // pila[pp].e es el estado actual. int salir = 0, cumple; s = (*analex)(); // pide un s mbolo de entrada if (s > t) return 0; // s mbolo inesperado numiter = 0; while (! salir ) { if (fver!= 0) { msj.printf("iter: %4u ", ++numiter); (*fver)(msj); MostrarPila(fver, pila, pp); msj.printf("est: %u, S mb: %u, ", pila[pp].e, (unsigned) s); (*fver)(msj); 131

132 Documentación de SLR1 versión 1.x a = x1 = accion[pila[pp].e * t + s]; if (! x1) { // error salir = 1; cumple = 0; if (fver!= 0) (*fver)("error.\n"); else { switch (a.cod) { case 0: // desplazar s e ir a a.n pila[++pp].s.cod = s; pila[pp].s.noterminal = 0; pila[pp].e = a.n; if (fver!= 0) { msj.printf("d%u\n", a.n); (*fver)(msj); s = (*analex)(); // pide otro s mbolo de entrada if (s > t) { // s mbolo inesperado salir = 1; cumple = 0; break; case 1: // aceptar salir = cumple = 1; if (fver!= 0) (*fver)("aceptar.\n"); break; case 2: // reducir por a.n x1 = ntlpd[a.n]; x2 = x1 & 0x00FF; // apaga el byte alto pp = x2 > pp? 0 : pp - x2; // desapila x2 elementos x1 >>= 8; // c d del no terminal x3 = ir_a[ pila[pp].e * nt + x1-1 ]; if (accion_sem!= 0) (*accion_sem)(a.n); pila[++pp].s.cod = x1; pila[pp].s.noterminal = 1; pila[pp].e = x3; if (fver!= 0) { msj.printf("r%u, lpdr: %u, EstExp: %u, NT: %u\n", a.n, x2, pila[pp-1].e, x1); (*fver)(msj); break; default: // problemas salir = 1; cumple = 0; if (fver!= 0) (*fver)("problemas EN LAS TABLAS.\n"); // else // while return cumple; ESQDED.H # ifndef ESQDED_H # define ESQDED_H // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May

133 Documentación de SLR1 versión 1.x // Ult Mod: 01 Nov 95 // clase SimbFormProp: implementa un símbolo de una fórmula del cálculo // proposicional. Un símbolo puede ser una constante (V o F), una variable // o un operador lógico. class SimbFormProp { public: unsigned cod; char valor; char var[10]; enum { CONSTANTE = 0, MAXVAR = 999, DOBLEIMPL, IMPLIC, DISYUN, CONJUN, NEGAR, ULTIMOOPERADOR ; ; // Funciónes de operaciones lógicas: // Evaluación de una fórmula con 1 o 2 operandos: int Evaluar( char p, char q, int operacion ); // Evaluación de una fórmula compleja dada en notación postfijo. // fnp es la fórmula (en vector), tfnp el número de elementos. // tds es la tabla de variables de la fórmula de ttds elementos. // En el campo valor de cada elemento de tds está el valor de verdad // asignado a la variable (==0 falso,!=0 verdadero). int Evaluar( const SimbFormProp * fnp, unsigned tfnp, const SimbFormProp * tds, unsigned ttds ); // Asignación de una interpretación (ver doc en asigint.cmm): void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ); // Verificación de si una fórmula es tautología o no: // La fórmula a comprobar viene en notación postfijo en fnp, representado // como vector de tamaño tfnp. La tabla de variables viene en tds, un vector // de tamaño ttds. El campo valor de los elementos de tds son modificados. int Tautologia( const SimbFormProp * fnp, unsigned tfnp, SimbFormProp * tds, unsigned ttds ); // Funciones para el análisis léxico y traducción del esquema deductivo a // implicación asociada: // FijarFuente debe ser llamado previo a llamar a la función Analizar. // El parámetro es una cadena de caracteres con el esquema deductivo. // Ver documentación en aled.cmm. void FijarFuente( const char * cad ); // AgregarTDS agrega a s como variable en la tabla de símbolos interna. // Devuelve el código asignado a la variable. int AgregarTDS( const char * s ); // AnaLexEsqDed es la función que realizará el análisis lexicográfico // del esquema deductivo. Se pasa como parámetro a la función Analizar. unsigned char AnaLexEsqDed(); // Validar un esquema deductivo dado en formato texto: // la función devuelve: // EDVAL si es válido. 133

134 Documentación de SLR1 versión 1.x // EDINVAL si es inválido. // EDERROR si hay error léxico o de sintaxis. int ValidarEsqDed( const char * EsqDed ); # define EDVAL 0 # define EDINVAL 1 # define EDERROR 2 # endif SLR1.H Este módulo viene provisto con SLR1 v 1. Se lo incluye a modo de ilustración. #ifndef SLR1_H #define SLR1_H // slr1.h: declaraci n de la funci n que realiza el an lisis sint ctico. // // Copyright (c) 1992 by Domingo Becker. // All rights reserved. int Analizar( unsigned * ntlpd, unsigned * accion, unsigned * ir_a, unsigned char (* analex)(), void (* accion_sem)( unsigned numregla ) = 0, void (* fver)( char * mensaje ) = 0 ); #endif ELEM.H Este módulo viene provisto con SLR1 v 1. Se lo incluye a modo de ilustración. #ifndef ELEM_H #define ELEM_H // elem.h: definici n de las clases para las tablas que maneja el analizador // sint ctico slr(1) // // Copyright (c) 1992 by Domingo E. Becker. // All rights reserved. #ifndef ASCII0_H #include "ascii0.h" #endif class Simbolo { public: unsigned char cod; unsigned char noterminal; ; class SimbCad : public Simbolo { public: Ascii0 cad; 134

135 Documentación de SLR1 versión 1.x ; class ElemPila { // estructura de un elemento de la pila del analizador public: Simbolo s; unsigned e; ElemPila() { s.cod = s.noterminal = 0; e = 0; ; class Accion { // estructura de una acci n public: unsigned n : 14; unsigned cod : 2; // 00=d, 01=a, 10=r Accion() { *(unsigned *) this = 0; Accion( unsigned a ) { *(unsigned *) this = a; Accion & operator = ( unsigned a ); int operator == ( unsigned a ) { return *(unsigned *) this == a; ; inline Accion & Accion::operator = ( unsigned a ) { *(unsigned *) this = a; return *this; #endif // #ifndef ELEM_H 135

136 Documentación de SLR1 versión 2.x Capítulo 12: Documentación de SLR1 versión 2.x El texto de este capítulo es la documentación de SLR1 v 2.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación Introducción SLR1 es un programa para generar tablas de análisis sintáctico LR(1) Simples a partir de la especificación de la gramática del lenguaje. Las tablas generadas por este programa serán usadas para la realización del análisis sintáctico de oraciones de ese lenguaje. Este documento es la guía del usuario y manual de referencia del generador SLR1 v 2.x. Su lectura presupone conocimientos básicos de la Teoría de los Lenguajes Formales. Además se aconseja leer la documentación de AFD v 3.x. Lo que más adelante se menciona como metagramática tiene un significado análogo al término metalenguaje en lingüística. En lingüística, metalenguaje significa un lenguaje para hablar de lenguajes. Aquí, metagramática significa una gramática para hablar de gramáticas Descripción general del funcionamiento Se desarrollaron 3 implementaciones de SLR1 v 2, una para DOS, otra para Windows y otra para OS/2 (Presentation Manager). La entrada de SLR1 v 2 es un archivo de texto plano con la gramática que se desea tratar, en donde se incluyen las acciones semánticas (opcionalmente) para las reglas, entre otras cosas. La salida del programa son 3 archivos: un archivo.cmm con las acciones semánticas y el analizador léxico (en C++), un archivo.tab con las tablas en formato C++ que serán utilizadas para el análisis sintáctico, y un archivo.est con las tablas de análisis sintáctico en formato similar al que se 136

137 Documentación de SLR1 versión 2.x presenta en la bibliografía, con los estados y con los códigos asignados a los símbolos terminales y no terminales. Los archivos.cmm y.tab son los que luego se utilizan para realizar el análisis sintáctico, incluyéndolo en la compilación junto con otros módulos fuentes del proyecto en desarrollo Especificación de una gramática La gramática debe ser escrita usando un archivo de texto plano con un editor adecuado para el sistema operativo con el que se trabaja (al archivo que contiene la gramática de entrada se lo mencionará en adelante como archivo.glc). El editor debe trabajar con código ANSI si es de Windows y con OEM si es de DOS u OS/2. Si usa un editor que trabaja con OEM y procesa la gramática con la versión de Windows entonces tendrá problemas con los acentos y la ñ Especificación 2.0 de la metagramática La metagramática que a continuación se presenta, describe la sintaxis que se debe usar para especificar una gramática que se usará como entrada de SLR1. La puede encontrar en el archivo GRAM.GLC, en donde además se especifican las acciones semánticas para cada regla. Gram ---> Fuentes Directivas Reglas ; Fuentes ---> BlqCod ; Fuentes ---> ; Directivas ---> Directivas Directiva ; Directivas ---> ; Directiva ---> '#' 'ACCSEM' Ident BlqCod ; Directiva ---> '#' 'TERMINAL' Ident ':' Ident ; Directiva ---> '#' 'TERMINAL' Ident ; Directiva ---> '#' 'IGNORAR' Ident ':' Ident ; Directiva ---> '#' 'IGNORAR' Ident ; Directiva ---> '#' 'ELEMPILA' Ident ; Reglas ---> Reglas Regla ; Reglas ---> Regla ; Regla ---> ParteIzq SimbSep ListaAltern ';' ; ParteIzq ---> Ident ; SimbSep ---> Flecha ; SimbSep ---> ':' ; ListaAltern ---> ListaAltern ' ' Altern ; ListaAltern ---> Altern ; Altern ---> ListaSimb CodAccSem ; ListaSimb ---> ListaSimb Simb ; ListaSimb ---> ; CodAccSem ---> '*' Ident ; CodAccSem ---> BlqCod ; CodAccSem ---> ; Simb ---> Ident ; 137

138 Documentación de SLR1 versión 2.x Simb ---> Cadena ; Las reglas se numeran de arriba a abajo de 1 a N, si hay N reglas. Una gramática está formada por un conjunto no vacío de reglas, es decir, al menos debe haber una regla Lexicografía del lenguaje de especificación de gramáticas v 2.0 A continuación se documentarán los componentes léxicos que pueden usarse en la escritura de una gramática según la especificación 2.0 de la metagramática (ver GRAM.GLC o página anterior). El analizador lexicográfico fue construido usando la versión 3 de AFD, junto con la biblioteca expreg.lib de soporte. El analizador lexicográfico busca todas las especificaciones léxicas que reconocen al símbolo léxico actual (la expresión regular es un ejemplo de especificación léxica, una palabra clave es otro ejemplo). Si hay más de una especificación léxica que reconoce al símbolo léxico actual se procede según las siguientes reglas: 1. Si hay una especificación que reconoce una secuencia de caracteres más larga que el resto de las especificaciones entonces se elige ésa. En caso de empate entre dos o más especificaciones se procede según las reglas siguientes. 2. Si el grupo de especificaciones son expresiones regulares, se elige la que fue listada primero en el analizador léxico (ver más adelante). 3. Si en el grupo hay una palabra clave o un caracter reservado, se elige éste. Así, TERMINAL puede ser un identificador, pero el analizador léxico da prioridad a la palabra clave y lo reconoce como tal. A continuación se enumeran los componentes léxicos. Los que fueron especificados por medio de expresiones regulares aparecen en el orden en el que fueron dados en el analizador léxico (a efectos de poder aplicar las 3 reglas anteriores para la resolución de conflictos). Palabras claves: únicamente en mayúsculas, se reservan las siguientes palabras: ACCSEM, TERMINAL, IGNORAR y ELEMPILA. No es posible mezclar mayúsculas con minúsculas. 138

139 Documentación de SLR1 versión 2.x Caracteres reservados: se reserva el uso de los siguientes caracteres: # para escritura de directivas al generador : para escritura de las directivas TERMINAL, IGNORAR y como reemplazo de la flecha ; para finalizar una o más reglas. para la especificación de alternativas * para referenciar a un bloque de código definido previamente con ACCSEM. Bloque de código: un bloque de código es una porción de texto (de una o más líneas) que comienza con el par de caracteres %{ y termina con %. Identificador: según la siguiente expresión regular: # Letra [a-za-z_áéíóúññüü] # Digito [0-9] Letra (Letra Digito)* Flecha: uno o más guiones o signos menos seguido de un signo mayor que: - + > Cadena: según la siguiente expresión regular: # CarEsp [abtnvfr] # Otros [^\'\\\"\n] '\'' ( ( '\\' ( '\\' '\'' '\"' CarEsp ) ) CarEsp Otros ) + '\'' '\"' ( ( '\\' ( '\\' '\'' '\"' CarEsp ) ) CarEsp Otros ) + '\"' Una cadena de caracteres es similar a las que se dan en C++, a excepción de que como delimitadores pueden usarse las comillas simples además de las dobles. Las secuencias de escape son las mismas que las del C++ (\a, \b, \t, \n, \v, \f, \r, \', \" y \\). Blancos: los blancos se ignoran: [ \n\r\f\t\v] + Comentarios: los comentarios se ignoran. Son similares a los // del C++: / /.* 139

140 Documentación de SLR1 versión 2.x Semántica subyacente en la especificación 2.0 de la Metagramática La sintaxis a usar en la escritura de una gramática es la definida por la metagramática dada previamente. La semántica se documenta a continuación. La estructura de un archivo que contiene una gramática según la especificación 2.0 se define por: 1. Partes del archivo.glc El archivo de gramática consta de tres partes a saber: Bloque de código fuente previo a las acciones semánticas. Directivas al generador de analizadores sintácticos. Conjunto de reglas con sus correspondientes acciones semánticas. 2. Bloques de código previo a las acciones semánticas Al comienzo del archivo.glc se puede incluir un bloque de código fuente C++ que será transcripto literalmente al archivo.cmm (el bloque es opcional, ver en páginas previas cómo se escribe un bloque de código). 3. Directivas al generador de analizadores sintácticos. Las directivas al generador son sentencias similares a las del C++ que comienzan con # (en el C++ están el #define, #include, #pragma, etc.). Las directivas tienen como objetivo dar información adicional al generador y simplificar la escritura de la gramática. Son opcionales. ACCSEM En total son 4 las directivas disponibles en esta versión del generador: '#' 'ACCSEM' Ident BlqCod Ejemplo: # ACCSEM Bloque1 %{ // hacer algo aquí % El Ident que se define aquí puede servir posteriormente para referenciar al bloque de código definido. Se debe usar *Ident para referenciar al bloque, 140

141 Documentación de SLR1 versión 2.x caso contrario Ident será tomado como un símbolo (terminal o no terminal, según corresponda). El objetivo de esta directiva es ahorrar escribir varias veces el mismo bloque de acciones semánticas en distintas reglas, ahorrando espacio y tiempo de escritura; el fuente.cmm generado es también más pequeño. TERMINAL dos posibilidades: '#' 'TERMINAL' Ident ':' Ident ó '#' 'TERMINAL' Ident Esta directiva define a Ident como un símbolo terminal. Si se usa la primera versión (la que tiene ':') entonces el Ident de más a la derecha es el nombre del archivo que contiene la expresión regular que lo define, el cual deberá ser procesado por AFD v 3.x. Si se usa la segunda versión, el archivo tiene nombre Ident. Ejemplo: # TERMINAL Cadena // archivo con expresión regular: cadena.er # TERMINAL BloqueDeCodigo : BlqCod // archivo con expr reg: blqcod.er Al generar el analizador lexicográfico, se agregarán líneas que #incluirán al archivo.cmm generado por AFD. Para los ejemplos dados se genera: # include "Cadena.cmm" // afdcadena # include "BlqCod.cmm" // afdbloquedecodigo Si usa acentos o ñ en el primer Ident entonces luego tendrá problemas al compilar el C++ generado. IGNORAR dos posibilidades: '#' 'IGNORAR' Ident ':' Ident ó '#' 'IGNORAR' Ident Esta directiva define a Ident como un componente léxico que puede aparecer en el archivo fuente que analizará el analizador sintáctico generado, 141

142 Documentación de SLR1 versión 2.x pero que deberá ser ignorado por el analizador lexicográfico que utilizará ése analizador sintáctico. Si se usa la primera versión (la que tiene ':') entonces el Ident de más a la derecha es el nombre del archivo que contiene la expresión regular que lo define, el cual deberá ser procesado por AFD v 3.x. Si se usa la segunda versión, el archivo tiene nombre Ident. Ejemplo: # IGNORAR Cadena // archivo con expresión regular: cadena.er # IGNORAR BloqueDeCodigo : BlqCod // archivo con expr reg: blqcod.er Al generar el analizador lexicográfico, se agregarán líneas que #incluirán al archivo.cmm generado por AFD. Para los ejemplos dados se generará: # include "Cadena.cmm" // afdcadena # include "BlqCod.cmm" // afdbloquedecodigo Si usa acentos o ñ en el primer Ident entonces luego tendrá problemas al compilar el C++ generado. ELEMPILA '#' 'ELEMPILA' Ident Esta directiva define a Ident como la clase que implementará el elemento de la pila del analizador sintáctico generado. Por ejemplo, si en el archivo.glc se define: # ELEMPILA ClaseElementoDeLaPila En el archivo.cmm generado, en el prototipo de la función que realiza las acciones semánticas se cambiará el parámetro Pila así: void AccSem( Ent16ns NumRegla, ClaseElementoDeLaPila * Pila, Ent16ns PP ) Debe recordar #incluir la declaración de la ClaseElementoDeLaPila al comienzo del archivo.cmm, ya sea a través del bloque previo a las acciones semánticas o bien a través de un #include del archivo.h agregado a mano en el archivo.cmm. En teoría, el #include o la definición de la clase debe estar en el bloque de código previo a las acciones semánticas. 4. Conjunto de reglas con sus correspondientes acciones semánticas. Las reglas dadas en la metagramática son ejemplos de escritura de las mismas, comienzan con un identificador a la izquierda y terminan con un punto y coma. Si hay varias reglas que tienen el mismo identificador a la 142

143 Documentación de SLR1 versión 2.x izquierda, pueden ser agrupadas usando el símbolo de separación de alternativa. Por ejemplo, el grupo de reglas siguiente: CodAccSem ---> '*' Ident ; CodAccSem ---> BlqCod ; CodAccSem ---> ; puede ser agrupado así: CodAccSem ---> '*' Ident BlqCod ; que son tres reglas escritas de manera simplificada. Las acciones semánticas se especifican usando bloques de códigos o *IdentBlqCod donde IdentBlqCod es un identificador definido con la sentencia #ACCSEM. Como ejemplo se incluye una porción del archivo GRAM.GLC: Directiva ---> '#' 'ACCSEM' Ident BlqCod %{ NodoLSE<DescrAccSem> * p; p = new NodoLSE<DescrAccSem>; if (p == 0) return; p->dato.ident << $3.CadTerm; // inicializa nodo descriptor p->dato.txtaccsem << $4.CadTerm; p->psig = pgram->tablaaccsem.entrada; // engancha al comienzo pgram->tablaaccsem.entrada = p; % '#' 'TERMINAL' Ident ':' Ident *ExpRegIdNom '#' 'TERMINAL' Ident *ExpRegId '#' 'IGNORAR' Ident ':' Ident *ExpRegIdNom '#' 'IGNORAR' Ident *ExpRegId '#' 'ELEMPILA' Ident %{ pgram->claseelempila << $3.CadTerm; % ; En el ejemplo dado hay 6 reglas resumidas usando el símbolo. Hay bloques de códigos (entre %{ y %) que especifican acciones semánticas que deben ejecutarse al reducir por esa regla, también se usa *Ident, donde Ident fue definido previamente usando la sentencia #ACCSEM (ver archivo GRAM.GLC). Para el acceso a los símbolos actuales de la regla se debe utilizar el símbolo $ dentro del bloque de código. Por ejemplo, para la primera regla del ejemplo: Directiva ---> '#' 'ACCSEM' Ident BlqCod $$ referencia Directiva, $3 referencia a Ident, y 143

144 Documentación de SLR1 versión 2.x $4 referencia a BlqCod. En general, $N, con 1 <= N <= Longitud de la regla, referencia al símbolo que está en la posición N de la parte derecha de la regla. No se efectúan chequeos de contorno, por lo que debe tener cuidado de que N no sea mayor que la longitud de la regla. En particular, $$ referencia a la parte izquierda de la regla, posición que es compartida con $1. La expansión de $X a código C++ es como sigue: $$ expande a Pila[PP+1] dentro de la función AccSemXXX. $N expande a Pila[PP+N] dentro de la función AccSemXXX. Vea como ejemplo el archivo GRAM.GLC, donde se ilustra la escritura de una gramática Descripción del archivo.cmm generado El archivo.cmm generado consta de 3 partes: Bloque de código previo a las acciones semánticas. Función de acciones semánticas. Definición del analizador léxico. El bloque de código previo a las acciones semánticas es una transcripción literal del que se especificó en la gramática fuente (archivo.glc), sólo que se quitaron los %{ y %. La función de acciones semánticas agrupa las acciones semánticas definidas en el archivo.glc. El prototipo es el siguiente: void AccSemXXX( Ent16ns NumRegla, ElemPila * Pila, Ent16ns PP ); donde: XXX: es el sufijo usado durante la generación. Ent16ns: es un typedef que corresponde a un tipo entero no signado de 16 bits. Se define en TIPOS.H que se incluye más adelante. 144

145 Documentación de SLR1 versión 2.x NumRegla: es el número de regla por la que se va a reducir. ElemPila: es el tipo de elementos de la pila del analizador sintáctico. Pila: es la pila, implementada en vector. PP: es el puntero de pila, índice para acceder al vector Pila. La función consta de una sola sentencia switch, que pregunta por el número de regla por la que se va a reducir. Dentro del mismo verá cases con el número de regla, y a la par, como comentario, la regla en formato texto para que cualquiera lo pueda leer sin necesidad de conocimientos profundos. En el caso de que se haya usado la sentencia ACCSEM, verá varios cases seguidos (uno en cada línea), que significa que todas esas reglas tienen la misma acción semántica. Los $X dentro de las acciones semánticas se expandieron a Pila[PP+X]; ya fueron documentados más atrás. Esta función será usada en la construcción del analizador sintáctico. Para la definición del analizador lexicográfico, se generan #includes para los símbolos terminales que serán reconocidos por autómatas finitos determinísticos (obtenidos a partir de expresiones regulares). A continuación se define un vector de reconocedores de símbolos que define al analizador lexicográfico. Cada reconocedor de símbolo ocupa una línea de la declaración, la que comienza con "static RecSimb vrs[ ] = {". El objeto vrs es estático (conocido sólo en éste módulo). Los reconocedores de símbolos son cuádruplas ordenadas, en donde: a. La primera componente es el código asignado a ése símbolo terminal, 0 si el símbolo léxico debe ser ignorado. El código no debe ser tocado ya que es asignado por SLR1 y de su valor depende el correcto uso de las tablas generadas. b. La segunda componente es un entero. Si tiene un 0 significa que el símbolo léxico es un literal, será tratado por el analizador léxico con una simple comparación de cadena de caracteres. Si tiene un 1 significa que hay un autómata finito determinístico que reconoce al símbolo léxico, el que será usado por el analizador lexicográfico. c. La tercera componente es un puntero al analizador lexicográfico, que se supone fue generado por AFD v 3.x o posterior. La definición de cada autómata se encuentra en el archivo.cmm que genera AFD y cuyos include se dieron previamente en éste archivo. Usted puede 145

146 Documentación de SLR1 versión 2.x construir sus propios autómatas finitos determinísticos, pero debe tener cuidado de no producir errores. Si la segunda componente tenía 0, esta componente tendrá 0. d. La cuarta componente es una cadena de caracteres C++ que contiene el literal del símbolo léxico. Si la segunda componente tenía 1 entonces encontrará un 0 en esta componente. Si tenía un 0 entonces encontrará una cadena. En el caso de que un terminal no haya sido correctamente definido usando la sentencia #TERMINAL, aparecerá luego de ésta componente un mensaje que le indicará que debe construir/modificar éste reconocedor de símbolo para que no se tome a ése terminal como palabra clave. Una vez definido el conjunto de reconocedores de símbolos se define el número de elementos del mismo (NUMRECSIMB) y seguidamente el analizador léxico a usar durante el análisis sintáctico. El mismo se construye usando la biblioteca ExpReg de soporte de AFD v 3.x Descripción del archivo.est generado El archivo.est generado es un archivo de texto que contiene una versión legible de las tablas generadas. En él se muestran en ese orden lo siguiente: 1. El conjunto de símbolos no terminales junto con sus respectivos códigos de uso interno del analizador sintáctico. 2. El conjunto de símbolos terminales junto con sus respectivos códigos de uso interno del analizador sintáctico. Observe que los códigos coinciden con los que aparecen como primera componente de cada elemento del vector de reconocedores de símbolos definido en el archivo.cmm. 3. A continuación se escribe la gramática. (Aparecerá sin los ';' de fin de regla). 4. Luego el conjunto de estados a partir de los cuales se construyen las tablas. 5. Finalmente las funciones acción(estado, símbolo) e ir_a(estado, símbolo), una a la par de la otra, en formato similar el que se usa en la bibliografía. Si desea imprimir la tabla bajo Windows u OS/2 utilice una 146

147 Documentación de SLR1 versión 2.x fuente en la que el ancho de todos los caracteres es el mismo, por ejemplo Courier Descripción del archivo.tab generado El archivo.tab es un archivo C++ que contiene las tablas de análisis sintáctico LR(1) Simple. Debe ser #incluido en el módulo en donde se realiza la definición del analizador sintáctico. El archivo contiene 3 vectores. Los mismos pueden tener un sufijo XXX que es el mismo usado durante la generación del analizador sintáctico Ent16ns ntlpdxxx[ ]; Este vector de enteros sin signo de 2 bytes, contiene: 1. En el elemento 0, el byte alto es el número de símbolos no terminales y el byte bajo es el número de terminales. Estos números son usados para calcular el número de columnas de las tablas accion e ir_a (ver las tablas en archivos.est, las columnas se indexan por símbolos terminal/no terminal). 2. En los elementos 1 en adelante, el byte alto es el código del no terminal que se halla a la izquierda de la regla cuyo número es el índice del elemento actual, y el byte bajo de éste elemento especifica el número de símbolos que hay a la derecha de la flecha (las reglas se numeran de 1 en adelante, en el orden en que aparecen en el archivo fuente.glc). En el momento de ejecutar una reducción, se necesita la información contenida en estos elementos, para desapilar la cantidad adecuada de elementos y poder indexar la columna de la tabla ir_a que requiere el código del no terminal de la izquierda Ent16ns accionxxx[ ]; Este vector de enteros sin signo de 2 bytes, es la transcripción de la tabla acción que se muestra en el archivo.est en formato más legible. La tabla acción mostrada en ese archivo comienza en el primer terminal y termina en el símbolo FA, que también es considerado terminal. Las filas se indexan por estado, y las columnas se indexan por símbolo terminal. 147

148 Documentación de SLR1 versión 2.x En este vector los elementos se codifican de la siguiente manera, lo que se muestra en formato C++: class Accion { // estructura de una acción public: unsigned n : 14; unsigned cod : 2; ; La clase Accion es una estructura de 16 bits, en los cuales los 14 bits menos significativos son los números que aparecen luego de la letra d o r en la tabla accion, y se acceden usando el campo n de esta estructura. El campo cod contiene el código de acción a ejecutar (en binario): 00 para desplazar, 01 para aceptar y 10 para reducir. El 11 no se utiliza. En el vector observará que: el elemento que tiene el es el que corresponde a aceptar, todos los elementos menores que ese número son los de desplazamiento (aparecen con la letra d en la tabla en el archivo.est) y todos los mayores que ese número son los de reducción (aparecen con la letra r en la tabla en el archivo.est) Ent16ns ir_axxx[ ]; Este vector implementa la tabla ir_a, que en el archivo.est se muestra a la par de la tabla acción a continuación del símbolo FA. Las columnas de esta tabla se indexan por símbolos no terminales y las filas por estados. La tabla se almacena en el vector por filas (fila mayor). Cada elemento contiene directamente el número que se muestra en la tabla ir_a del archivo.est Definición de un analizador sintáctico Si la versión de SLR1 disponible es menor que 2.3, entonces la definición del analizador sintáctico se debe realizar a mano, si es igual o mayor entonces la definición la realiza el mismo generador en el archivo.cmm generado. 148

149 Documentación de SLR1 versión 2.x Definición a mano del objeto Analizador Sintáctico Definición en C++ significa que el objeto es declarado y reside en el ámbito del lugar donde se declara. Para definir el objeto analizador sintáctico debe #incluir el archivo.tab con las tablas y el archivo.cmm, ambos generados por SLR1. Ejemplo: suponga que el archivo fuente con la gramática se llamaba gram.glc y que el sufijo usado fue Gram, entonces, en el módulo donde se necesite el analizador sintáctico se hará: // código previo del usuario... // inclusión de los módulos generados por SLR1 v 2 # include "gram.cmm" // acciones semánticas y analizador lexicográfico # include "gram.tab" // tablas del analizador sintáctico SLR(1) // más código del usuario... // función donde se requiere el analizador: int f( const char * TxtFuente ) { //... // definición del analizador sintáctico: AnalSLR1<ElemPila> analizador(ntlpdgram, acciongram, ir_agram, AccSemGram); algram.reiniciar(txtfuente); Ascii0 CadPosErr; if (! analizador.analizar(algram, CadPosErr)) { return CODIGO_DE_ERROR; //... // fin de la función f // más código del usuario... El parámetro TxtFuente de la función f es una cadena de caracteres válida del C++, puede ser obtenida de la manera que se desee. Es tratada por el analizador lexicográfico generado, que se encuentra en el archivo.cmm generado por SLR1 v 2. El uso de cadenas C++ para la especificación de la entrada al analizador sintáctico no impone obligaciones sobre el esquema de E/S a usar, que no es el caso del YACC, el cual impone la entrada/salida estándar (stdin y stdout en C, su redireccionamiento es criptográfico y difícil de tratar). Dado que la clase que implementa al analizador sintáctico es una clase template, el parámetro ElemPila que se da entre < > es el que implementa el tipo de elemento de la pila del analizador sintáctico. Si usted usó la sentencia 149

150 Documentación de SLR1 versión 2.x #ELEMPILA entonces debe usar el mismo Ident allí indicado dentro de los < > en la definición del objeto analizador. Ver el ejemplo que se incluye en este documento para más detalle de una implementación adecuada Definición automática por versiones 2.3 o posterior Si se usa las versiones 2.3 o posterior, la definición del analizador sintáctico es realizada por el generador. Sólo si su aplicación lo requiere, deberá mover la definición del objeto a otro ámbito. En estas versiones sólo se debe #incluir el archivo.cmm generado y usar los analizadores La clase template AnalSLR1 La clase template AnalSLR1 implementa el analizador sintáctico. El argumento template de la clase es el tipo del elemento de la pila del analizador. Esta clase está declarada en SLR1.H. Lo que caracteriza al analizador sintáctico son las tablas, las que deben ser dadas en el momento de la construcción del objeto analizador. El analizador lexicográfico no caracteriza al analizador sintáctico, por lo que se lo da como parámetro al momento de realizar un análisis sintáctico, llamando a la función Analizar de esta clase Funciones miembro públicas: AnalSLR1( Ent16ns * ntlpd, Ent16ns * accion, Ent16ns * ir_a, void (* faccsem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp ) = 0, void (* flimpila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple ) = 0, void (* fver)( const char * mensaje ) = 0 ); Este es el único constructor de la clase. Los tres primeros parámetros (ntlpd, accion e ir_a) son los objetos que aparecen en el archivo.tab generado. Si se especificó un sufijo al procesar la gramática, el prefijo utilizado es exactamente igual a éstos (por ejemplo, si el sufijo era Expr entonces tenemos ntlpdexpr, accionexpr e ir_aexpr, respectivamente). El parámetro faccsem es la función que realizará el análisis semántico, debe especificar aquí la función que fue generada en el archivo.cmm 150

151 Documentación de SLR1 versión 2.x generado. Por defecto este parámetro vale 0, lo que significa que no hay acciones semánticas. Puede no especificarlo. flimpila es una función que limpia la pila del analizador sintáctico. Si ELEMPILA es una clase distinta de ElemPila entonces puede ser necesario efectuar ciertas acciones especiales para la correcta destrucción de los elementos de la pila. Esas acciones especiales lo puede realizar esta función, aunque siempre es preferible que el trabajo de la correcta destrucción de los elementos sea hecho por el destructor de su clase. El parámetro cumple tiene 0 si la cadena analizada sintácticamente no cumple con la estructura definida por la gramática del lenguaje, y distinto a 0 si es sintácticamente correcta. fver esta función sirve para mostrar las distintas acciones que se van ejecutando durante el análisis sintáctico. La función recibe un parámetro que es una cadena de texto con la descripción de la acción. Ent8 Analizar( AnaLex & analex, Ascii0 & CadErr, Ent16ns MaxPila = 50 ); Esta función es la que realiza el análisis sintáctico. El parámetro analex es el analizador lexicográfico, el cual debe estar previamente inicializado. El analizador lexicográfico puede ser cambiado en tiempo de ejecución, pero debe tener cuidado de que los códigos de símbolos que devuelva la función AnaLex::Examinar sean los que espera este analizador sintáctico. CadErr es el objeto donde se devuelve la cadena con la posición donde se produjo el error léxico. Debido a que el acceso al objeto pasado en analex es posible en éste ámbito, se puede tener un completo informe de la posición donde se produjo el error a través del mismo. Consulte la documentación de la biblioteca ExpReg. TamPila es el tamaño de la pila del analizador sintáctico LR(1) simple. Por defecto es 50, si hace falta una más grande o más chica entonces especifique un valor nuevo. La función devuelve 1 si la oración es sintácticamente correcta, 0 si es sintácticamente incorrecta. En el miembro dato CodError devuelve un código de error. Los códigos son los siguientes (acceder desde otro ámbito usando la sintaxis AnalSLR1<ELEMPILA>:: ): 0: no hay problemas. 151

152 Documentación de SLR1 versión 2.x ERRORLEX: error léxico, puede utilizar el analizador lexicográfico para obtener mayor información. TERMINESP: terminal inesperado. El terminal siguiente es válido pero está en una posición sintácticamente incorrecta. PILACHICA: el tamaño de la pila del analizador sintáctico especificado por TamPila es chico. Pruebe pasar un valor mayor que el que fue especificado. ERRORENTABLAS: hay un problema en las tablas (puede haber sido provocado por una modificación manual de las mismas) Campos dato públicos: Ent16ns CodError; En este campo se devuelve el código de error devuelto luego de realizar el análisis sintáctico, un valor entero sin signo Campos dato protegidos: Ent16ns * ntlpd, Ent16ns * accion, Ent16ns * ir_a; void (* faccsem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp ); void (* flimpila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple ); void (* fver)( const char * mensaje ); ntlpd, accion, e ir_a son las tablas del analizador sintáctico que fueron generadas en el archivo.cmm. faccsem es un puntero a la función que realiza las acciones semánticas. flimpila es un puntero a una función auxiliar que ayuda a una correcta limpieza de los elementos de la pila. fver es una función que sirve para seguir paso a paso el trabajo efectuado por el analizador sintáctico. 152

153 Documentación de SLR1 versión 2.x 12.9 Clase SimboloSLR1 Esta clase está declarada en SLR1.H. Implementa un símbolo de una gramática. La declaración es muy pequeña y se transcribe a efectos de simplificar la documentación: class SimboloSLR1 { public: Ent8ns Id; Ent8ns NoTerminal; SimboloSLR1() { Id = NoTerminal = 0; ; El campo Id es el código asignado al símbolo terminal/no terminal. Los códigos son asignados por SLR1. El campo NoTerminal tiene 0 si el símbolo es terminal, distinto a 0 si es no terminal. Por defecto ambos campos valen 0, valores reservados para el símbolo terminal Fin de Archivo Clase ElemPila Esta clase define la estructura por defecto del elemento de la pila del analizador sintáctico SLR(1). Está declarada en SLR1.H y su declaración se transcribe: class ElemPila { public: SimboloSLR1 Simb; Ent16ns Estado; Ascii0 CadTerm; ElemPila( ) { Estado = 0; ; Simb es el símbolo que se encuentra en esta posición de la pila. Estado es el estado del analizador en esta posición de la pila. CadTerm es la cadena de caracteres del terminal apilado (sólo si el símbolo de ésta posición de la pila era terminal). Al hacer un desplazamiento se guarda en este campo una copia de la cadena de caracteres devuelta por el analizador lexicográfico. Debido a que el destructor de Ascii0 hace la correcta devolución de la memoria dinámica usada por la cadena es que no hace falta utilizar la función flimpila para la limpieza de la pila. 153

154 Documentación de SLR1 versión 2.x Si va a usar una definición alternativa de la clase que implementa el elemento de la pila del analizador, recuerde que: 1. el símbolo debe ser implementado por una clase derivada de SimboloSLR1 y el nombre del campo debe ser Simb. 2. el campo Estado debe ser declarado exactamente igual a como se declara en esta clase. 3. el campo CadTerm debe ser declarado exactamente igual a como se declara en esta clase. 4. incluir SLR1.H, no modificar este archivo Consideraciones a tener el cuenta para el uso de gramáticas ambiguas El uso de gramáticas ambiguas puede dar como resultado tablas más pequeñas, lo que reduce el espacio de memoria necesario para almacenarlas. Pero el nivel de conocimiento necesario para poder resolver los conflictos es demasiado elevado. En la automatización de la generación de programas el objetivo es que el usuario (en este caso el programador) no pierda tiempo aprendiendo cosas que no hacen falta, ya que el generador hace ese trabajo y lo hace bien. El uso de gramáticas ambiguas imposibilita cumplir ese objetivo, y por lo general, el resultado no es "mucho mejor" que el de usar gramáticas no ambiguas. En general, la diferencia obtenida es infinitesimal y no justifica perder el tiempo dada la disponibilidad de memoria en las computadoras actuales y la velocidad de procesamiento de las mismas. Hay gramáticas que no son ambiguas, pero presentan conflictos pasa la técnica de análisis LR(1) Simple debido a la forma de trabajo del algoritmo (es decir, es ambigua para la técnica SLR(1) ). La gramática para la sentencia si...entonces...sino... es no ambigua pero presenta un conflicto de desplazamiento/reducción con el símbolo sino y la regla SentSi --> si Expr entonces Sent. (Nota: en este caso se debe elegir desplazar sino). El conflicto de desplazamiento/reducción que provoca esta gramática hace que SLR1 genere un analizador sintáctico para esa gramática que pueda construir 2 posibles árboles de análisis sintáctico para una oración que tenga la parte sino..., y es por eso que decimos que es ambigua para la técnica LR(1) Simple. 154

155 Documentación de SLR1 versión 2.x Desarrollo de un proyecto de programación con SLR1 v 2.x El proyecto a desarrollar es un sistema para la verificación de la validez de un esquema deductivo del cálculo proposicional. Para que un esquema deductivo sea válido debe primero ser sintácticamente correcto (aquí usaremos SLR1). Luego, la validez puede ser demostrada de dos maneras: utilizando la Teoría de la Demostración o utilizando la Teoría Semántica del Cálculo Proposicional. En este ejemplo se utilizará la Teoría Semántica del Cálculo Proposicional. En la Teoría Semántica del Cálculo Proposicional, un sistema deductivo es válido cuando la implicación asociada a éste es tautología. Entonces, para verificar la validez de un esquema deductivo se ejecutarán las siguientes operaciones: 1. Primero se realizará el análisis sintáctico del esquema deductivo fuente, a efectos de detectar errores sintácticos. 2. Luego se traducirá el esquema deductivo a su implicación asociada, la que será internamente almacenada en un vector en notación postfijo, a efectos de facilitar su posterior evaluación. Al traducir se generará una tabla con todas las variables que figuran en la fórmula. 3. A continuación se comprueba si la implicación asociada es una tautología, para lo cual se van generando las interpretaciones posibles para las variables que aparecen en la fórmula, y para cada una se comprueba el valor de verdad de la fórmula. Si para alguna interpretación la fórmula da falso, la misma no es tautología. Si ocurre que la fórmula si es tautología, el esquema deductivo es válido. 4. Se informa el resultado del paso 3. La secuencia de pasos que se siguieron para la escritura y utilización de la gramática fue: a. Primero se escribe el archivo ED.GLC que tiene la gramática con las correspondientes acciones semánticas en cada regla y con las especificaciones necesarias para la construcción automática del analizador lexicográfico. b. Se procesa ED.GLC con SLR1 v 2 usando "EsqDed" como sufijo y se obtienen así los archivos ED.CMM y ED.TAB. En ED.CMM están 155

156 Documentación de SLR1 versión 2.x las acciones semánticas y el analizador lexicográfico. En ED.TAB están las tablas a incluir. c. Se procesan los archivos.er con las expresiones regulares para obtener los correspondientes.cmm, cuyos #includes están en ED.CMM y fueron automáticamente escritos por SLR1 v 2. d. Se escribe el archivo VESQDED.CMM que valida un esquema deductivo. En ese archivo se incluyen las líneas que se indicaron anteriormente para el correcto compilamiento. e. Se compila VESQDED.CMM y, eureka, eso es todo. Los archivos importantes que componen el proyecto son los que se enumeran a continuación, y son independientes del sistema operativo en el que se implemente la aplicación: ED.GLC: entrada para SLR1 v 2.x. La salida de SLR1 son los archivos ED.CMM y ED.TAB que deben ser incluidos en el módulo donde será utilizado el analizador sintáctico (VESQDED.CMM). ED.CMM: salida de SLR1 al especificar como entrada a ED.GLC. ED.TAB: salida de SLR1 al especificar como entrada a ED.GLC. EVALUAR.CMM: en este módulo se dan las funciones para evaluar una fórmula bien formada del cálculo proposicional dada en notación postfijo. ASIGINT.CMM: en este módulo se da una función que traduce una interpretación codificada en binario, cargando en cada variable de la tabla de símbolos dada el valor de verdad correspondiente. TAUTO.CMM: en este módulo se da una función que verifica si una fórmula dada en notación postfijo es o no tautología. VESQDED.CMM: en este módulo se da una función que verifica un esquema deductivo dado como entrada. Se podría considerar a este módulo como el principal. ESQDED.H: este es un archivo cabecera en donde se declaran las clases y prototipos de las funciones desarrolladas, a los efectos de poder reusar fácilmente los módulos fuentes escritos. SLR1.H: declaración las clases necesarias para realizar el análisis sintáctico. Viene provisto junto con SLR1 v 2.x. 156

157 Documentación de SLR1 versión 2.x EXPREG.H: declaración de las clases necesarias para realizar el análisis lexicográfico. Viene provisto junto con AFD v 3.x. EXPREG.LIB: biblioteca de soporte de AFD v 3.x. BCE.LIB: biblioteca auxiliar. Se usa la clase Ascii0 únicamente. Su inclusión es además requerida por EXPREG.LIB. La interfase con el usuario final de la aplicación ha sido generada con el Application Expert (AppExpert) del Turbo C para Windows; la aplicación correrá sin problemas en Windows 3.1 en adelante, incluyendo a Windows NT. El código generado puede ser recompilado sin problemas usando el Borland C para OS/2, y obtener así una versión que correrá en OS/2 2.1 y OS/2 Warp, pero se debe tener cuidado con ese C++ puesto que es de 32 bits (en C++ no se dan especificaciones sobre la implementación de los tipos de datos fundamentales, la misma queda a elección del fabricante del compilador; por ejemplo, en C++ para Win16 el tamaño del tipo int es de 16 bits y en C++ para OS/2 y Win32 es de 32 bits). La aplicación generada es un editor multitexto (se pueden editar varios textos a la vez) con todas las funciones de un editor de textos, y en el menú archivo se agregó una opción para "Verificar validez" del esquema deductivo que se encuentra en la ventana de texto actual (la que se encuentre activa). La generación de la aplicación es en realidad más simple de lo que se pueda entender a partir de la lectura de este documento (fue muy fácil), y es esa la razón por la que se eligió AppExpert para diseñar la interfase. Los archivos generados son: EDAPP.CMM: módulo principal. EDEDITVW.CMM: clase que maneja un objeto editor de texto. A este módulo se lo modificó para procesar la opción "Verificar validez". EDMDICLN.CMM: clase para manejar un objeto cliente de un objeto MDI. EDMDICHL.CMM: clase para manejar un objeto hijo de un objeto MDI. APXPREV.CPP: presentación preliminar de un documento editado. APXPRINT.CPP: objeto para imprimir un documento. EDABTDLG.CPP: ventana Acerca de... de la aplicación. APXPREV.H: cabecera de APXPREV.CPP. 157

158 Documentación de SLR1 versión 2.x APXPRINT.H: cabecera de APXPRINT.CPP. EDABTDLG.H: cabecera de EDABTDLG.CPP EDAPP.H: cabecera de EDAPP.CMM. EDEDITVW.H: cabecera de EDEDITVW.CMM. EDMDICLN.H: cabecera de EDMDICLN.CMM. EDMDICHL.H: cabecera de EDMDICHL.CMM. La clase EDEditView, cuya definición está en EDEDITVW.CMM, y que sirve para manejar una vista de un documento de texto (el generador AppExpert da soporte al modelo documento/vista introducido por Borland en sus C++ desde la versión 4.0 para Windows y 2.0 para OS/2), fue modificada para manejar el evento CM_VERIFICAR que se genera al seleccionar la opción "Verificar validez". La función agregada es la VerificarValidez de esta clase, cuya declaración y prototipado inicial fue realizado por ClassExpert, el administrador de clases de AppExpert. La función se transcribe a continuación, y el código agregado es el que está después del comentario // INSERT>>... y termina en la llamada a MessageBox final. (Se aclara nuevamente que el resto del código, salvo dos líneas que se encuentran al comienzo de éste módulo, no ha sido escrito por el autor). void EDEditView::VerificarValidez () { // INSERT>> Your code here. // Primero sacar el texto del EditView de Windows Ent16ns tam = GetTextLen() + 1; Ascii0 cad(tam); if (! GetText(cad, tam)) return; // si no hay texto editado // Luego llamar a la función que verifica validez: int r = ValidarEsqDed(cad); // y finalmente se informa el resultado: const char * msj = "CODIGO DESCONOCIDO"; uint icono = MB_OK; switch (r) { case EDVAL: msj = "El esquema deductivo es VALIDO."; icono = MB_ICONINFORMATION; break; case EDINVAL: msj = "El esquema deductivo es INVALIDO."; icono = MB_ICONEXCLAMATION; break; case EDERROR: msj = "Error sintáctico en el fuente."; icono = MB_ICONQUESTION; break; MessageBox(msj, "Resultado del análisis", icono); 158

159 Documentación de SLR1 versión 2.x La única sentencia importante en esta función es la llamada a ValidarEsqDed, que es la función que actualmente ejecuta toda la operación de validación. Todas las sentencias antes de ésta sirven para extraer el texto del objeto ventana editor del Windows (a través de la clase TEdit, clase base de TEditView y EDEditView). El código siguiente a la llamada a ValidarEsqDed es el que presenta el resultado en una ventana de mensajes. El problema de obtener el texto de entrada e informar el resultado de la validación es un problema aparte, y se puede realizar como uno quiera y en el sistema operativo que desee. También se agregaron dos líneas más al comienzo del módulo EDEDITVW.CMM, que son las siguientes: # include <ascii0.h> // de BCE # include "esqded.h" // de esta aplicación Los.h incluidos hacen posible la utilización de la clase Ascii0 (para obtener el texto) y de la función ValidarEsqDed (que no está definida en este módulo). El código restante (enumerado en el primer grupo de módulos fuentes) es el que realiza todo el trabajo de validación. No contempla la forma de obtener el texto ni la forma de emitir mensajes (ambas dependientes del sistema operativo). Este conjunto de módulos fuentes son independientes del sistema operativo, inclusive son independiente de si trabaja con código ANSI u OEM (los editores de Windows trabajan con OEM, los de DOS y algunos de OS/2 con ANSI). En el lenguaje de entrada cuya gramática está dada en ED.GLC, el símbolo # sirve para indicar que a continuación viene la conclusión, la coma separa a las premisas (debe haber por lo menos una). Cada premisa debe ser una fórmula bien formada del cálculo proposicional. El símbolo <==> se utiliza para la doble implicación, el ==> se utiliza para el la implicación, el + para la disyunción, el * para la conjunción, el - para la negación y los ( ) para la asociatividad y ruptura de precedencia. Los tres siguientes son ejemplos de esquemas deductivos reconocibles por la gramática dada: Ejemplo 1: p ==> q, p # q 159

160 Documentación de SLR1 versión 2.x Ejemplo 2: -p + q, p # q Ejemplo 3: - ( p * r ), q ==> r, q # -p Los esquemas dados le pueden ser familiares. Los puede encontrar en cualquier libro de lógica. La forma en que se escriban las fórmulas no debe ser importante, inclusive los espacios que se dejan entre ellas. Este problema será resuelto por el analizador lexicográfico generado. La función que verifica si la fórmula proposicional en notación postfijo es tautología se llama Tautologia y se encuentra definida en el módulo TAUTO.CMM. La misma genera interpretaciones de las variables que aparecen en la fórmula hasta encontrar alguna interpretación para la que la fórmula dé falso. La interpretación (un juego de valores de verdad asignado a las variables de la fórmula) se codifica en un número binario de 16 bits, en donde el bit 0 tiene el valor de verdad de la variable tds[0], el bit 1 a tds[1], y así sucesivamente. Como el número tiene 16 bits, a lo sumo pueden haber 16 variables posibles. El pasaje de binario a la tabla de símbolos de una interpretación lo realiza la función AsigInterp definida en ASIGINT.CMM. La función Evaluar realiza la evaluación de una fórmula proposicional dada en postfijo para una interpretación dada. Más documentación puede encontrar en los archivos fuente. A continuación se transcriben los módulos fuentes enumerados en el primer grupo de módulos fuentes, que son los que realmente se escribieron para esta aplicación. Los demás módulos fueron generados automáticamente, y sólo son importantes las líneas transcriptas a éste documento, y por tal razón no se incluyen aquí. 160

161 Documentación de SLR1 versión 2.x Módulos fuentes escritos para el proyecto ED.GLC // Gramática para la carga en memoria en notación postfijo de // un esquema deductivo del cálculo proposicional. // Creación: 04 Dic 95 - Versión 2 - Para SLR1 v 2.x // Especificar EsqDed como sufijo en SLR1. %{ # include <string.h> # ifndef ESQDED_H # include "esqded.h" # endif SimbFormProp fnp[50], // fórmula en notación postfijo tds[16]; // tabla de símbolos unsigned ifnp = 0, itds = 0; // primer elemento libre en fnp y tds respectivamente. // int AgregarTDS( const char * s ); // Agrega s a la tabla de símbolo. s es la cadena que identifica al símbolo. // Si la variable ya existe en la tabla entonces no lo agrega. // Siempre devuelve el código asignado a la variable, que es un número // entre 1 y 16. int AgregarTDS( const char * s ) { register int i; for (i = 0; i < itds; ++i) if (! strcmp(tds[i].var, s)) break; if (i == itds) { strncpy(tds[itds].var, s, 10); tds[itds].valor = 0; ++itds; tds[itds].cod = itds; return itds; // devuelve el código que se le asigna return tds[i].cod; // dev. el cód. asignado anteriormente % # TERMINAL Variable : Var // Expr regular en var.er # TERMINAL DobleImpl : DImpl // Expr regular en dimpl.er # TERMINAL Implica : Impl // Expr regular en impl.er # IGNORAR Blancos : Blanco // Expr regular en blanco.er # IGNORAR Comentarios : Coment // Expr regular en coment.er // Para las reglas // E --> Prem '#' F ; // I --> I Implica D ; # ACCSEM ACImplicación %{ fnp[ifnp].cod = SimbFormProp::IMPLIC; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "==>"); ++ifnp; % // Para las reglas // Prem --> Prem ',' F ; 161

162 Documentación de SLR1 versión 2.x // C --> C '*' Prop ; # ACCSEM ACConjunción %{ fnp[ifnp].cod = SimbFormProp::CONJUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "*"); ++ifnp; % E --> Prem '#' F *ACImplicación; Prem --> Prem ',' F *ACConjunción; Prem --> F ; F --> F DobleImpl I %{ fnp[ifnp].cod = SimbFormProp::DOBLEIMPL; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "<==>"); ++ifnp; %; F --> I ; I --> I Implica D *ACImplicación; I --> D ; D --> D '+' C %{ fnp[ifnp].cod = SimbFormProp::DISYUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "+"); ++ifnp; %; D --> C ; C --> C '*' Prop *ACConjunción; C --> Prop ; Prop --> Constante %{ fnp[ifnp].cod = SimbFormProp::CONSTANTE; fnp[ifnp].valor = strcmp($1.cadterm, "0"); strncpy(fnp[ifnp].var, $1.CadTerm, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; %; Prop --> Variable %{ // Escribir en la expresión en notación postfijo fnp[ifnp].cod = AgregarTDS($1.CadTerm); // agrega a tds y asigna el código fnp[ifnp].valor = 0; strncpy(fnp[ifnp].var, $1.CadTerm, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; %; Prop --> '(' F ')' ; Prop --> '-' Prop %{ fnp[ifnp].cod = SimbFormProp::NEGAR; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "-"); ++ifnp; %; 162

163 Documentación de SLR1 versión 2.x Constante --> '0' '1'; ED.CMM // Código fuente generado por SLR1 versión 2.1 (Nov 95) // Domingo Eduardo Becker, Sgo. del Estero, Tel (085) // Código de acciones semánticas. // Código generado por Gramatica::ImprAccSem versión 2. // Domingo Eduardo Becker. # ifndef SLR1_H # include "slr1.h" # endif // Código fuente previo a las acciones semánticas. # include <string.h># ifndef ESQDED_H # include "esqded.h" # endif SimbFormProp fnp[50], // fórmula en notación postfijo tds[16]; // tabla de símbolos unsigned ifnp = 0, itds = 0; // primer elemento libre en fnp y tds respectivamente. // int AgregarTDS( const char * s );// Agrega s a la tabla de símbolo. s es la cadena que identifica al símbolo. // Si la variable ya existe en la tabla entonces no lo agrega. // Siempre devuelve el código asignado a la variable, que es un número // entre 1 y 16. int AgregarTDS( const char * s ) { register int i; for (i = 0; i < itds; ++i) if (! strcmp(tds[i].var, s)) break; if (i == itds) { strncpy(tds[itds].var, s, 10); tds[itds].valor = 0; ++itds; tds[itds].cod = itds; return itds; // devuelve el código que se le asigna return tds[i].cod; // dev. el cód. asignado anteriormente // Fin del código fuente previo... void AccSemEsqDed( Ent16ns NumRegla, ElemPila * Pila, Ent16ns PP ) { switch (NumRegla) { case 4: // F ---> F DobleImpl I { fnp[ifnp].cod = SimbFormProp::DOBLEIMPL; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "<==>"); ++ifnp; break; case 8: // D ---> D '+' C { fnp[ifnp].cod = SimbFormProp::DISYUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "+"); ++ifnp; break; 163

164 Documentación de SLR1 versión 2.x case 12: // Prop ---> Constante { fnp[ifnp].cod = SimbFormProp::CONSTANTE; fnp[ifnp].valor = strcmp(pila[pp+1].cadterm, "0"); strncpy(fnp[ifnp].var, Pila[PP+1].CadTerm, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; break; case 13: // Prop ---> Variable { // Escribir en la expresión en notación postfijo fnp[ifnp].cod = AgregarTDS(Pila[PP+1].CadTerm); // agrega a tds y asigna el código fnp[ifnp].valor = 0; strncpy(fnp[ifnp].var, Pila[PP+1].CadTerm, 10); fnp[ifnp].var[9] = 0; // asegura terminación en 0 ++ifnp; break; case 15: // Prop ---> '-' Prop { fnp[ifnp].cod = SimbFormProp::NEGAR; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "-"); ++ifnp; break; case 2: // Prem ---> Prem ',' F case 10: // C ---> C '*' Prop { fnp[ifnp].cod = SimbFormProp::CONJUN; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "*"); ++ifnp; break; case 1: // E ---> Prem '#' F case 6: // I ---> I Implica D { fnp[ifnp].cod = SimbFormProp::IMPLIC; fnp[ifnp].valor = 0; strcpy(fnp[ifnp].var, "==>"); ++ifnp; break; // switch (NumRegla) // Fin de AccSemEsqDed // Fin del código de acciones semánticas. // Analizador lexicográfico para la gramática. // Generado por Gramatica::ImprAnaLex versión 2. // Domingo Eduardo Becker. # ifndef EXPREG_H # include "expreg.h" # endif # include "Coment.cmm" // afdcomentarios # include "Blanco.cmm" // afdblancos # include "Impl.cmm" // afdimplica # include "DImpl.cmm" // afddobleimpl # include "Var.cmm" // afdvariable static RecSimb vrs[] = { 1, 0, 0, "#", 2, 0, 0, ",", 3, 1, & afddobleimpl, 0, 4, 1, & afdimplica, 0, 5, 0, 0, "+", 164

165 Documentación de SLR1 versión 2.x 6, 0, 0, "*", 7, 1, & afdvariable, 0, 8, 0, 0, "(", 9, 0, 0, ")", 10, 0, 0, "-", 11, 0, 0, "0", 12, 0, 0, "1", 0, 1, & afdcomentarios, 0, 0, 1, & afdblancos, 0 ; # define NUMRECSIMB 14 AnaLex alesqded(vrs, NUMRECSIMB); ED.TAB No se incluye porque su lectura es complicada y es un archivo generable a partir de ED.GLC listado previamente EVALUAR.CMM // evaluar.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" # include <string.h> // int ValorDe( const char * s, const SimbFormProp * tds, unsigned ttds ); // Esta función busca el símbolo cuya cadena es s en el vector tds cuyo // tamaño es ttds. // Debido a que para la comparación de cadenas se usa strcmp, se hace // diferencia entre mayúsculas y minúsculas. // La función devuelve el valor de verdad asignado a la variable. static int ValorDe( const char * s, const SimbFormProp * tds, unsigned ttds ) { register int i; for (i = 0; i < ttds; ++i) if (! strcmp(s, tds[i].var)) break; if (i == ttds) return 0; else return tds[i].valor; // int Evaluar( char p, char q, int operacion ); // Evaluar evalúa la operacion pasada como parámetro a partir de los // operandos p y q. // En p y q debe venir 0 si es falso o distinto a 0 si es verdadero. // La función devuelve 0 si el resultado de la operación es falso y // distinto a 0 si el resultado de la operación es verdadero. // La operación se codifica con la enumeración dada en simbolo.h dentro // de la clase SimbFormProp, y se acceden desde cualquier lugar usando el // operador :: para romper ámbitos. Ellos son: // DOBLEIMPL: <==> doble implicación, binario, p <==> q 165

166 Documentación de SLR1 versión 2.x // IMPLIC: ==> implicación, binario, p ===> q // DISYUN: + disyunción, binario, p + q // CONJUN: * conjunción, binario, p * q // NEGAR: - negación, unario, - p int Evaluar( char p, char q, int operacion ) { p = p? 1 : 0; // llevar a 0 o 1 q = q? 1 : 0; register int r = 0; switch (operacion) { case SimbFormProp::DOBLEIMPL: // <==> r =! (p ^ q); break; case SimbFormProp::IMPLIC: // ==> r =! p q; break; case SimbFormProp::DISYUN: // + (disyunción) r = p q; break; case SimbFormProp::CONJUN: // * (conjunción) r = p & q; break; case SimbFormProp::NEGAR: // - (negación) r =! p; break; return r; // int Evaluar( const SimbFormProp * fnp, unsigned tfnp, // const SimbFormProp * tds, unsigned ttds ); // Evaluar evalúa la expresión en postfijo pasado en el vector fnp, cuyo // tamaño es tfnp. // La interpretación a utilizar es la que viene en la tabla de símbolos tds, // cuyo tamaño es ttds. // La función devuelve 0 si la fórmula bien formada en notación postfijo fnp // es falsa para la interpretación dada, y distinto a 0 si es verdadera. // El algoritmo que se usa para evaluar la expresión en notación postfijo // puede ser encontrado en cualquier libro de Estructuras de datos y // Algoritmos. int Evaluar( const SimbFormProp * fnp, unsigned tfnp, const SimbFormProp * tds, unsigned ttds ) { char pila[20], pp = 0; register unsigned ifnp; for (ifnp = 0; ifnp < tfnp; ++ifnp) { if (fnp[ifnp].cod > SimbFormProp::MAXVAR && fnp[ifnp].cod < SimbFormProp::ULTIMOOPERADOR) { // efectuar operación if (fnp[ifnp].cod == SimbFormProp::NEGAR) { // operación unaria, desapila 1 y apila el resultado pila[pp - 1] = Evaluar(pila[pp-1], 0, fnp[ifnp].cod); else { // operación binaria, desapila 2 y apila el resultado pila[pp - 2] = Evaluar(pila[pp-2], pila[pp-1], fnp[ifnp].cod); --pp; else pila[pp++] =! fnp[ifnp].cod? fnp[ifnp].valor 166

167 Documentación de SLR1 versión 2.x return pila[pp-1]; : ValorDe(fnp[ifnp].var, tds, ttds); ASIGINT.CMM // asigint.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" // void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ); // AsigInterp asigna a la tabla de símbolos la interpretación que viene // codificada en vv. // Si numeramos los bits de vv de 0 a 15, el bit 0 corresponde a tds[0], // el bit 1 a tds[1],..., y el bit 15 a tds[15]. // El valor de verdad de cada variable se asigna al campo valor de cada // elemento de tds. void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ) { register int i, bit; for (i = 0, bit = 1; i < ttds; ++i, bit <<= 1) tds[i].valor = vv & bit; TAUTO.CMM // tauto.cmm // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 01 Nov 95 # include "esqded.h" // int Tautologia( const SimbFormProp * fnp, unsigned tfnp, // SimbFormProp * tds, unsigned ttds ); // Tautologia chequea si la fórmula bien formada del cálculo proposicional // que viene en fnp en formato notación postfijo es una tautología. // El tamaño de la fórmula es tfnp (número de elementos del vector). // La tabla de símbolos que se recibe como parámetros es a los efectos de // poder generar las interpretaciones posibles. El tamaño de la tabla de // símbolos (vector) viene dado en ttds. // La función devuelve 0 si no es tautología y distinto a 0 si lo es. int Tautologia( const SimbFormProp * fnp, unsigned tfnp, SimbFormProp * tds, unsigned ttds ) { int i, t; t = (1 << ttds) - 1; // 2 elevado a la número de variables menos 1 for (i = 0; i <= t; ++i) { AsigInterp(tds, ttds, i); if (! Evaluar(fnp, tfnp, tds, ttds)) // si alguno es falso break; // entonces no es tautología return i > t; // si se probaron todas las interpretaciones // entonces es tautología 167

168 Documentación de SLR1 versión 2.x VESQDED.CMM // vesqded.cmm validación de esquemas deductivos. // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 04 Dic 95 # include "ed.cmm" # include "ed.tab" int ValidarEsqDed( const char * EsqDed ) { alesqded.reiniciar(esqded); ifnp = itds = 0; // variables globales AnalSLR1<ElemPila> analizador(ntlpdesqded, accionesqded, ir_aesqded, AccSemEsqDed); Ascii0 CadPosErr; if (analizador.analizar(alesqded, CadPosErr)) return Tautologia(fnp, ifnp, tds, itds)? EDVAL : EDINVAL; else return EDERROR; ESQDED.H # ifndef ESQDED_H # define ESQDED_H // Copyright 1993 by Domingo Eduardo Becker. // All rights reserved. // Creación: 26 May 93 // Ult Mod: 04 Dic 95 // clase SimbFormProp: implementa un símbolo de una fórmula del cálculo // proposicional. Un símbolo puede ser una constante (V o F), una variable // o un operador lógico. class SimbFormProp { public: unsigned cod; char valor; char var[10]; enum { CONSTANTE = 0, MAXVAR = 999, DOBLEIMPL, IMPLIC, DISYUN, CONJUN, NEGAR, ULTIMOOPERADOR ; ; // Funciónes de operaciones lógicas: // Evaluación de una fórmula con 1 o 2 operandos: int Evaluar( char p, char q, int operacion ); // Evaluación de una fórmula compleja dada en notación postfijo. // fnp es la fórmula (en vector), tfnp el número de elementos. // tds es la tabla de variables de la fórmula de ttds elementos. // En el campo valor de cada elemento de tds está el valor de verdad // asignado a la variable (==0 falso,!=0 verdadero). 168

169 Documentación de SLR1 versión 2.x int Evaluar( const SimbFormProp * fnp, unsigned tfnp, const SimbFormProp * tds, unsigned ttds ); // Asignación de una interpretación (ver doc en asigint.cmm): void AsigInterp( SimbFormProp * tds, unsigned ttds, int vv ); // Verificación de si una fórmula es tautología o no: // La fórmula a comprobar viene en notación postfijo en fnp, representado // como vector de tamaño tfnp. La tabla de variables viene en tds, un vector // de tamaño ttds. El campo valor de los elementos de tds son modificados. int Tautologia( const SimbFormProp * fnp, unsigned tfnp, SimbFormProp * tds, unsigned ttds ); // Funciones para el análisis léxico y traducción del esquema deductivo a // implicación asociada: // AgregarTDS agrega a s como variable en la tabla de símbolos interna. // Devuelve el código asignado a la variable. int AgregarTDS( const char * s ); // AnaLexEsqDed es la función que realizará el análisis lexicográfico // del esquema deductivo. Se pasa como parámetro a la función Analizar. unsigned char AnaLexEsqDed(); // Validar un esquema deductivo dado en formato texto: // la función devuelve: // EDVAL si es válido. // EDINVAL si es inválido. // EDERROR si hay error léxico o de sintaxis. int ValidarEsqDed( const char * EsqDed ); # define EDVAL 0 # define EDINVAL 1 # define EDERROR 2 # endif SLR1.H Este módulo viene provisto con SLR1 v 2.x. Se lo incluye más adelante en este mismo documento (en la sección 12.14) EXPREG.H Este módulo viene provisto con AFD v 3.x. Se lo incluye más adelante en este mismo documento a modo de ilustración (en la sección 12.14). 169

170 Documentación de SLR1 versión 2.x Archivos provistos con SLR1 v 2.x SLR1.H # ifndef SLR1_H # define SLR1_H // slr1.h: definición de las clases para las tablas que maneja el analizador // sintáctico SLR(1) // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # ifndef TIPOS_H # include "tipos.h" # endif # ifndef ASCII0_H # include "ascii0.h" # endif # ifndef EXPREG_H # include "expreg.h" # endif // Clase SimboloSLR1: es una versión simplificada de la clase SimboloSLR1 // declarada en gramlc.h. Aquí Codigo tiene 8 bits en vez de 15 y NoTerminal // tiene 8 bits en vez de 1. Esto hace que el código funcione más rápido. class SimboloSLR1 { public: Ent8ns Id; Ent8ns NoTerminal; SimboloSLR1() { Id = NoTerminal = 0; ; class SimbCad : public SimboloSLR1 { public: Ascii0 cad; ; class ElemPila { // estructura de un elemento de la pila del analizador public: SimboloSLR1 Simb; Ent16ns Estado; Ascii0 CadTerm; ElemPila() { Estado = 0; ; class Accion { // estructura de una acción public: Ent16ns n : 14; // puede tener el número de regla o número de estado Ent16ns Codigo : 2; // 00=d, 01=a, 10=r Accion() { *(Ent16ns *) this = 0; Accion( Ent16ns a ) { *(Ent16ns *) this = a; Accion & operator = ( Ent16ns a ); Ent8 operator == ( Ent16ns a ) { return *(Ent16ns *) this == a; ; 170

171 Documentación de SLR1 versión 2.x inline Accion & Accion::operator = ( Ent16ns a ) { *(Ent16ns *) this = a; return *this; template <class ELEMPILA> class AnalSLR1 { protected: Ent16ns * ntlpd, * accion, * ir_a; void (* faccsem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp ); void (* flimpila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple ); void (* fver)( const char * mensaje ); public: AnalSLR1( Ent16ns * ntlpd, Ent16ns * accion, Ent16ns * ir_a, void (* faccsem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp ) = 0, void (* flimpila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple ) = 0, void (* fver)( const char * mensaje ) = 0 ); ~AnalSLR1() { Ent8 Analizar( AnaLex & analex, Ascii0 & CadErr, Ent16ns MaxPila = 50 ); Ent16ns CodError; enum { ERRORLEX = 1, TERMINESP, PILACHICA, ERRORENTABLAS ; ; template <class ELEMPILA> AnalSLR1<ELEMPILA>::AnalSLR1( Ent16ns * _ntlpd, Ent16ns * _accion, Ent16ns * _ir_a, void (* _faccsem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp ), void (* _flimpila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple ), void (* _fver)( const char * mensaje ) ) { ntlpd = _ntlpd; accion = _accion; ir_a = _ir_a; faccsem = _faccsem; flimpila = _flimpila; fver = _fver; template <class ELEMPILA> Ent8 AnalSLR1<ELEMPILA>::Analizar( AnaLex & analex, Ascii0 & CadErr, Ent16ns MaxPila ) { Ent16ns idterm; Ascii0 cadterm; Accion a; Ent16ns x1,x2,x3; // variables auxiliares Ent16ns numiter; Ascii0 msj; Ent8ns nt = ntlpd[0] >> 8, t = ntlpd[0] + 1; // pila[pp].estado es el estado actual. Ent8 salir = 0, cumple; idterm = analex.examinar(cadterm); // pide un símbolo de entrada if (idterm > t) { // error léxico analex.caderror(caderr); CodError = ERRORLEX; return 0; // carácter o símbolo inesperado ELEMPILA * pila; 171

172 Documentación de SLR1 versión 2.x pila = new ELEMPILA[MaxPila]; // se def aquí para que el constructor no se // ejecute si no va a ser usado. if (pila == 0) return 0; // falta memoria Ent16ns pp; pp = 0; // puntero de pila pila[0].estado = 0; // estado inicial. numiter = 0; while (! salir ) { if (fver!= 0) { msj.printf("iter: %4u ", ++numiter); (*fver)(msj); // MostrarPila(fVer, pila, pp); msj.printf("est: %u, Símb: %u, ", pila[pp].estado, (Ent16ns) idterm); (*fver)(msj); a = x1 = accion[pila[pp].estado * t + idterm]; if (! x1) { // error sintáctico, terminal inesperado salir = 1; cumple = 0; analex.caderror(caderr); CodError = TERMINESP; if (fver!= 0) (*fver)("error.\n"); else { switch (a.codigo) { case 0: // desplazar terminal actual e ir a a.n if (++pp == MaxPila) { msj.printf("sobreflujo en la pila, intente con MaxPila > %u.\n", MaxPila); if (fver!= 0) (*fver)(msj); salir = 1; cumple = 0; CodError = PILACHICA; break; pila[pp].simb.id = idterm; pila[pp].simb.noterminal = 0; pila[pp].cadterm << cadterm; pila[pp].estado = a.n; if (fver!= 0) { msj.printf("d%u\n", a.n); (*fver)(msj); idterm = analex.examinar(cadterm); // pide otro símbolo de entrada if (idterm > t) { // error léxico analex.caderror(caderr); salir = 1; cumple = 0; CodError = ERRORLEX; // símbolo inesperado break; case 1: // aceptar salir = cumple = 1; CodError = 0; if (fver!= 0) (*fver)("aceptar.\n"); break; case 2: // reducir por a.n 172

173 Documentación de SLR1 versión 2.x x1 = ntlpd[a.n]; x2 = x1 & 0x00FF; // apaga el byte alto pp = x2 > pp? 0 : pp - x2; // desapila x2 elementos x1 >>= 8; // cód del no terminal x3 = ir_a[ pila[pp].estado * nt + x1-1 ]; if (faccsem!= 0) (*faccsem)(a.n, pila, pp); pila[++pp].simb.id = x1; pila[pp].simb.noterminal = 1; pila[pp].estado = x3; if (fver!= 0) { msj.printf("r%u, lpdr: %u, EstExp: %u, NT: %u\n", a.n, x2, pila[pp-1].estado, x1); (*fver)(msj); break; default: // problemas salir = 1; cumple = 0; CodError = ERRORENTABLAS; if (fver!= 0) (*fver)("problemas EN LAS TABLAS.\n"); // else // while if (flimpila!= 0) (*flimpila)(pila, pp, cumple); delete [] pila; return cumple; # endif // # ifndef SLR1_H EXPREG.H # ifndef EXPREG_H # define EXPREG_H // expreg.h: definición de una clase para el manejo de // expresiones regulares. // Creación: Lun 09 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # ifndef TIPOS_H # include <tipos.h> # endif class AutomFinDet { public: const char * ExpRegular; const char * * Caracter; Ent16ns NumCar; const Ent16ns * Transicion, * EstFinales; ; # define TNDEF 0xffff // ExpRegular: cadena de la Expresión Regular que define el lenguaje. // Caracter: vector de const char *, contiene los caracteres posibles como 173

174 Documentación de SLR1 versión 2.x // caracteres simples o como rangos (para rangos ver doc). // NumCar: número de caracteres, número de columnas de la tabla Transición. // Transicion: tabla de transición de estados del autómata finito deter- // minístico (AFD) que reconoce el lenguaje de la expr. regular. // Los elementos se enumeran por filas. Si una transici n no // est definida se debe utilizar TNDEF o el número 0xffff. // EstFinales: vector de estados finales del AFD. El elemento 0 de este // vector contiene el número de elementos, a continuación se // enumeran los estados finales. El vector no necesita estar // ordenado porque se utiliza búsqueda secuencial. // Ejemplo de definición est tica de AutomFinDet: // (Los objetos static pueden no serlo. Se los hace estáticos para que // sus nombres sean sólo conocidos en el módulo donde se define al // objeto AutomFinDet.) /* static const char ExpRegular[] = "a+(b c)*d+"; static const char * Caracteres[] = { "a", "b", "c", "d" ; static const Ent16ns Transiciones[] = { 1, TNDEF, TNDEF, TNDEF, 1, 2, 2, 3, TNDEF, 2, 2, 3, TNDEF, TNDEF, TNDEF, 3 ; static const Ent16ns EstFinales[] = { 1, // elemento 0 para indicar la cantidad de estados finales. 3 ; AutomFinDet automata = { ExpRegular, Caracteres, 4, Transiciones, EstFinales ; // La tabla de Transiciones del ejemplo es: // a b c d // // // // */ class Archivo; const char * CadSecEscape( char c ); char CarSecEscape( const char * pcad ); int ImprCaracter( Archivo & arch, char c ); int ImprCadena( Archivo & arch, const char * cad ); int CambSecEsc( char * pcad ); int ImprimirAFD( Archivo & arch, const AutomFinDet & afd ); int ImprFuenteAFD( Archivo & arch, const AutomFinDet & afd, const char * NomObj = 0, const char * NomSubobj = 0 ); // Los nombres ser n los siguientes: // erxxx: expresi n regular // vcxxx: vector de caracteres o rangos // txxx: tabla de transiciones // efxxx: vector con los estados finales // afdyyy: variable AutomFinDet (aut mata finito determin stico) // XXX corresponde al valor NomSubobj y YYY a NomObj. Si NomSubobj == 0 // entonces se usa el valor de NomObj como NomSubobj. Si ambos son nulos // entonces aparecer el/los caracteres iniciales, por ejemplo, si // NomObj == 0 o == "" entonces el nombre de la variable AutomFinDet // ser 'afd'. // Si no especifica al menos NomObj, y agrupa varios AutomFinDet generados 174

175 Documentación de SLR1 versión 2.x // por ExpReg en un solo m dulo fuente entonces tendr colisi n de nombres // al compilar. class Ascii0; class ExpReg { protected: AutomFinDet * afd; char apu; public: ExpReg() { afd = 0; apu = 0; ExpReg( const AutomFinDet & _afd ) { afd = 0; apu = 0; Reasignar(_afd); ExpReg( const char * er, Ascii0 & CadPosErr ); ~ExpReg() { Vaciar(); int Reasignar( const AutomFinDet & afd ); int Reasignar( const char * CadExpReg, Ascii0 & CadPosErr ); void Vaciar(); int Imprimir( Archivo & arch ) const; int ImprFuente( Archivo & arch, const char * NomObj = 0, const char * NomSubobj = 0 ) const; int ImprFuente( const char * NomArch, const char * NomObj = 0, const char * NomSubobj = 0 ) const; int Examinar( const char * Cadena, unsigned Inicio, unsigned & Tam ) const; int Buscar( const char * Cadena, unsigned & Inicio, unsigned & Tam, unsigned BuscarDesde = 0 ) const; enum { AFDNDEF = 1, AFDERR, CADNULA, CARINESP, TRANSNDEF ; // para Buscar ; inline ExpReg::ExpReg( const char * er, Ascii0 & CadPosErr ) { afd = 0; apu = 0; Reasignar(er, CadPosErr); inline int ExpReg::Reasignar( const AutomFinDet & Afd ) { Vaciar(); afd = (AutomFinDet *) & Afd; apu = 1; return 1; inline int ExpReg::Imprimir( Archivo & arch ) const { return afd!= 0? ImprimirAFD(arch, *afd) : 0; inline int ExpReg::ImprFuente( Archivo & arch, const char * NomObj, const char * NomSubobj ) const { return afd!= 0? ImprFuenteAFD(arch, *afd, NomObj, NomSubobj) : 0; // Clase RecSimb: Reconocedor de Símbolo, esta clase contiene un puntero a // una cadena donde está la cadena del símbolo léxico (si es constante) o // bien un puntero al autómata finito determinístico (AFD) que reconoce al // símbolo léxico. También contiene el identificador léxico del símbolo. class RecSimb { public: unsigned Id; char EsAFD; 175

176 Documentación de SLR1 versión 2.x const AutomFinDet * pafd; const char * pcad; ; // Id: número identificador del símbolo léxico. No puede usar 0xffff porque // est reservado para error léxico. Si usa 0 el símbolo léxico ser // ignorado por un objeto AnaLex, ste lo saltear y buscar el // siguiente. La clase AnaLex reserva el 0 para el fin de archivo (FA). // No hay problemas acerca de Id repetidos en un vector de RecSimb a // ser usado por un objeto AnaLex. // EsAFD: si == 0 entonces el símbolo es constante y est descripto por pcad, // en este caso pafd == 0. Si!= 0 entonces el reconocedor del símbolo // es un Autómata Finito Determinístico apuntado por pafd, pcad == 0 // en este caso. // pafd: apunta a un AutomFinDet creado a mano o con un generador de AFD. // pcad: apunta a la cadena que describe al símbolo. Por ejemplo, si el // símbolo es '>=' entonces no hace falta un AFD, es más eficiente // especificar ">=". Para el caso de los Naturales con el 0, estamos // hablando de una familia de símbolos similares, y ya no podemos usar // este campo sino pafd con un autómata que reconozca "[0-9]+". // Clase AnaLex: Analizador Lexicográfico, esta clase encapsula un analizador // lexicográfico. Se implementa mediante un vector de RecSimb (reconocedores // de símbolos) creado previamente. Informa sobre el estado del analizador y // los errores encontrados. class AnaLex { protected: const RecSimb * vrs; unsigned tvrs; const char * ptxt; unsigned Inic, Tama, NumLin; unsigned (* ferrorlex) ( int MayIgualMin, AnaLex & analex, Ascii0 & Cad ); AnaLex(); // no se puede crear objetos sin vrs. public: unsigned CodError; // usado por función Examinar unsigned ComLin; // índice del comienzo de línea AnaLex( const RecSimb * pvrs, unsigned n, unsigned (* fsihayerrorlex) ( int, AnaLex &, Ascii0 & ) = 0 ); ~AnaLex() { vrs = 0; ptxt = 0; ferrorlex = 0; void Reiniciar( const char * Texto ); unsigned Examinar( Ascii0 & CadSimb, int MayIgualMin = 0 ); unsigned Inicio() const { return Inic; unsigned Tam() const { return Tama; unsigned NumLinea() const { return NumLin + 1; unsigned Simbolo( Ascii0 & CadSimb ) const; char Saltear(); unsigned Saltear( unsigned tam ); unsigned CadError( Ascii0 & Cad ) const; ; inline AnaLex::AnaLex() { vrs = 0; ptxt = 0; 176

177 Documentación de SLR1 versión 2.x Inic = Tama = CodError = NumLin = ComLin = 0; ferrorlex = 0; inline AnaLex::AnaLex( const RecSimb * pvrs, unsigned n, unsigned (* fsihayerrorlex) ( int, AnaLex &, Ascii0 & ) ) { vrs = pvrs; tvrs = n; ptxt = 0; Inic = Tama = CodError = NumLin = ComLin = 0; ferrorlex = fsihayerrorlex; inline void AnaLex::Reiniciar( const char * Texto ) { ptxt = Texto; Inic = Tama = CodError = NumLin = ComLin = 0; inline char AnaLex::Saltear() { if (! Tama && ptxt!= 0 && ptxt[inic]) Tama = 1; return ptxt[inic]; // El siguiente objeto es global y lo puede usar cualquiera. // Es usado por ExpReg::Reasignar( const char * CadExpReg ) para // analizar CadExpReg en busca de símbolos léxicos. // Recuerde siempre Reiniciar antes de usarlo por primera vez. extern AnaLex AnaLexExpReg; // Al llamar a AnaLex::Examinar con AnaLexExpReg, Examinar devuelve alguno // de los siguientes códigos o 0 si hay fin de texto. No devuelve error // léxico porque reconoce cualquier caracter excepto el 0 de fin de cadena // al cual lo toma como fin de texto. # define _id_numeral 1 # define _id_ident 2 # define _id_ctecar 3 # define _id_rango 4 # define _id_disyuncion 5 # define _id_opcional 6 # define _id_uno_o_mas 7 # define _id_cero_o_mas 8 # define _id_abre_paren 9 # define _id_cierra_paren 10 # endif // # ifndef EXPREG_H 177

178 Documentación de la Biblioteca ExpReg v 1.x Capítulo 13: Documentación de la Biblioteca ExpReg v 1.x El texto de este capítulo es la documentación de ExpReg v 1.x, y debe ser tomado como un libro aparte. No se hacen referencias a otros capítulos de este trabajo. En el capítulo 18, donde se comenta la historia del desarrollo de las herramientas presentadas en este trabajo, se puede encontrar información interesante acerca de esta implementación Introducción La biblioteca ExpReg para C++ es un conjunto de clases y rutinas para manejar expresiones regulares, autómatas finitos determinísticos y analizadores lexicográficos. El objetivo principal de esta biblioteca es la de servir como soporte para las aplicaciones AFD y SLR1. AFD utiliza la implementación de autómatas finitos determinísticos y la implementación de expresiones regulares de ésta. SLR1 utiliza además a la implementación de analizadores lexicográficos. Esta biblioteca ha sido definida independiente del sistema operativo en el que se trabaje. Saca provecho además de la implementación que los compiladores hagan del tipo de dato fundamental int del C++. Cuando use EXPREG.LIB recuerde incluir en su proyecto la biblioteca BCE, ya que esta biblioteca usa a la clase Ascii0 de BCE Autómatas finitos determinísticos - Clase AutomFinDet La clase AutomFinDet implementa un autómata finito determinístico. La teoría sobre los autómatas finitos determinísticos debe ser consultada en la bibliografía de compiladores. La declaración de la clase se realiza en el archivo EXPREG.H y es como sigue: class AutomFinDet { 178

179 Documentación de la Biblioteca ExpReg v 1.x public: const char * ExpRegular; const char * * Caracter; Ent16ns NumCar; const Ent16ns * Transicion, * EstFinales; ; en donde: ExpRegular: cadena de la Expresión Regular que define el lenguaje. Puede obviar darle valor a este campo pasando un 0. Caracter: vector de const char *, contiene los caracteres posibles como caracteres simples o como rangos. NumCar: número de caracteres, número de columnas de la tabla Transición. Transicion: tabla de transición de estados del autómata finito determinístico (AFD) que reconoce el lenguaje de la expr. regular. Los elementos se enumeran por filas. Si una transición no está definida se debe utilizar TNDEF o el número 0xffff. EstFinales: vector de estados finales del AFD. El elemento 0 de este vector contiene el número de elementos, a continuación se enumeran los estados finales. El vector no necesita estar ordenado porque se utiliza búsqueda secuencial. La clase se define de esa manera a efectos de poder realizar definiciones estáticas de objetos de la misma. Por ejemplo, la siguiente es una definición válida de un autómata finito determinístico: // (Los objetos static pueden no serlo. Se los hace estáticos para que // sus nombres sean sólo conocidos en el módulo donde se define al // objeto AutomFinDet.) static const char ExpRegular[ ] = "a+(b c)*d+"; static const char * Caracteres[ ] = { "a", "b", "c", "d" ; static const Ent16ns Transiciones[ ] = { 1, TNDEF, TNDEF, TNDEF, 1, 2, 2, 3, TNDEF, 2, 2, 3, TNDEF, TNDEF, TNDEF, 3 ; static const Ent16ns EstFinales[ ] = { 1, // elemento 0 para indicar la cantidad de estados finales. 3 ; AutomFinDet automata = { ExpRegular, Caracteres, 4, Transiciones, EstFinales ; 179

180 Documentación de la Biblioteca ExpReg v 1.x 13.3 Funciones globales para el tratamiento de AutomFinDet ImprimirAFD int ImprimirAFD( Archivo & arch, const AutomFinDet & afd ); Esta función imprime afd en arch en formato texto. El formato texto es similar al utilizado por la bibliografía que trata sobre autómatas. Archivo es una clase de la biblioteca BCE que sirve de soporte para esta biblioteca. La función devuelve 0 si no pudo imprimir, 1 si pudo imprimir ImprFuenteAFD int ImprFuenteAFD( Archivo & arch, const AutomFinDet & afd, const char * NomObj = 0, const char * NomSubobj = 0 ); Imprime afd en arch en formato C++ (como texto plano). La salida es similar a la que se muestra como ejemplo de definición de un objeto de la clase AutomFinDet. La clase Archivo pertenece a la biblioteca BCE que sirve de soporte a esta biblioteca. NomObj es el sufijo utilizado para el nombre del objeto AutomFinDet. El nombre del mismo será afdnomobj. Si pasa 0 (valor por defecto) entonces no se usa sufijo. NomSubobj es el sufijo utilizado para la definición de los subobjetos componentes del autómata finito determinístico. El campo ExpRegular será definido como ernomsubobj, el campo Caracter será definido como vcnomsubobj, Transicion como tnomsubobj y EstFinales como efnomsubobj. En el caso de que se especifique 0 se utiliza NomObj como NomSubobj, y, si este último era 0 entonces no se usa sufijo para los nombres de los subobjetos. Los subobjetos serán definidos como static. NomObj y NomSubobj se deben utilizar para evitar la duplicación de nombres de objetos en un proyecto. La función devuelve 0 si hay problemas con el autómata o no se pudo imprimir, 1 si pudo imprimir. 180

181 Documentación de la Biblioteca ExpReg v 1.x 13.4 Expresiones regulares - Clase ExpReg La clase ExpReg declarada en EXPREG.H implementa el uso de una expresión regular, la cual deberá ser especificada por medio de una cadena de caracteres válida del C++, la que puede ser obtenida por lectura directa y completa de un archivo de texto plano. A partir de la expresión regular especificada como parámetro, esta clase construye un autómata finito determinístico que reconoce el lenguaje definido por esa expresión regular, a efectos de posterior consulta usando la expresión regular. También se puede armar un objeto a partir de un autómata finito determinístico definido previamente Campos dato de la clase ExpReg Los miembros dato protegidos de la clase son: AutomFinDet * afd; Es el autómata finito determinístico que reconoce el lenguaje definido por la expresión regular. char apu; Tiene 1 si éste objeto fue construido a partir de un AutomFinDet, y 0 si el campo afd fue construido por la función local Reasignar Funciones miembro de la clase ExpReg Las funciones miembro públicas de esta clase son: ExpReg::ExpReg( ); Construye un objeto ExpReg por defecto (nulo). ExpReg::ExpReg( const AutomFinDet & _afd ); 181

182 Documentación de la Biblioteca ExpReg v 1.x Construye un objeto ExpReg a partir del autómata finito determinístico pasado como parámetro. ExpReg::ExpReg( const char * er, Ascii0 & CadPosErr ); Construye un objeto ExpReg a partir de la expresión regular pasada en el parámetro er. Si hay error de sintaxis o léxico en el texto de entrada (er) entonces se devuelve una cadena con la posición del error en CadPosErr. Este constructor utiliza la función local Reasignar para efectuar este trabajo. int ExpReg::Reasignar( const AutomFinDet & afd ); int ExpReg::Reasignar( const char * CadExpReg, Ascii0 & CadPosErr ); La primera versión reasigna el campo afd de éste objeto. La segunda versión construye un objeto AutomFinDet a partir de la expresión regular CadExpReg. Se realiza un análisis lexicográfico y sintáctico de la expresión regular y luego se procede a la construcción del autómata finito determinístico a partir de esa expresión regular. Si hay error léxico o de sintaxis, en CadPosErr se devuelve la cadena de texto con la línea donde se produjo el error. Ambas funciones devuelven 0 si no se pudo reasignar, distinto a 0 si no hubo ningún problema. void ExpReg::Vaciar(); Vacía este objeto. Vaciar significa hacer nulo. Si el objeto construyó el AutomFinDet afd entonces lo destruye devolviendo correctamente la memoria usada. int ExpReg::Imprimir( Archivo & arch ) const; Imprime éste objeto en arch. Se utiliza la función global ImprimirAFD, y se devuelven los mismos códigos de error. 182

183 Documentación de la Biblioteca ExpReg v 1.x int ExpReg::ImprFuente( Archivo & arch, const char * NomObj = 0, const char * NomSubobj = 0 ) const; int ExpReg::ImprFuente( const char * NomArch, const char * NomObj = 0, const char * NomSubobj = 0 ) const; Estas funciones imprimen el objeto afd en el archivo especificado. La primera versión espera que arch sea un archivo abierto y que se pueda escribir en él. La segunda crea un archivo con nombre NomArch eliminando la versión previa del mismo si es que existía. Se utiliza la función global ImprFuenteAFD. Consulte en su documentación el uso de NomObj y NomSubobj. Las funciones devuelven 0 si hay error, distinto a 0 si no hay problemas. int ExpReg::Examinar( const char * Cadena, unsigned Inicio, unsigned & Tam ) const; Esta función examina la Cadena desde la posición Inicio ( Cadena[Inicio] ) para ver si la secuencia siguiente de caracteres cumple con ésta expresión regular. Si cumple entonces la función devuelve 0 y en Tam el número de caracteres del símbolo del lenguaje. El símbolo comienza en Cadena[Inicio] y termina en Cadena[Inicio + Tam - 1], y no incluye un 0 de fin de cadena en Cadena[Inicio + Tam]. Se puede usar la función estándar del C++ strncpy para sacar una copia del símbolo, se debe indicar como fuente a Cadena + Inicio, y como n a Tam; la cadena destino debe ser lo suficientemente grande como para contener al símbolo (Tam + 1 de largo); luego del llamado a strncpy debe hacer "Simbolo[Inicio + Tam] = 0;" para asegurar la terminación en 0 de la cadena. Si no cumple devuelve un valor distinto de 0 que es el código de error. Debe utilizar el enum definido dentro de ésta clase para ver qué error se produjo. Utilice la sintaxis ExpReg::XXX donde XXX es el id del código. Los códigos son los siguientes: AFDNDEF: el autómata finito determinístico es nulo (no definido). AFDERR: el autómata finito determinístico está mal definido. 183

184 Documentación de la Biblioteca ExpReg v 1.x CADNULA: la Cadena pasada es nula (== 0 o Inicio > Longitud(Cadena)). CARINESP: el caracter que sigue (el de Cadena[Inicio + Tam - 1]) es inesperado. No se lo encuentra dentro del vector this->afd.caracter. TRANSNDEF: para el caracter que sigue no hay transición definida desde el estado actual. Debido a que esta función no hace movimiento de datos, es muy conveniente su utilización en analizadores lexicográficos, ya que no desperdicia tiempo en movimientos de datos innecesarios. int ExpReg::Buscar( const char * Cadena, unsigned & Inicio, unsigned & Tam, unsigned BuscarDesde = 0 ) const; Esta función Examina la Cadena desde la posición BuscarDesde (Cadena[BuscarDesde] ) para ver si desde esa posición hasta el primer caracter 0 siguiente hay una secuencia de caracteres que pertenece al lenguaje definido por ésta expresión regular. Esta función utiliza la función Examinar para la búsqueda de la primera ocurrencia de un símbolo perteneciente al lenguaje. Si no hay problemas devuelve 0. En Inicio devuelve la posición inicial de la primera ocurrencia. En Tam devuelve el tamaño de la cadena. Si Tam == 0 entonces puede que no haya una ocurrencia de algún símbolo del lenguaje (si la expresión regular es anulable entonces Tam puede venir con 0 sin que haya error, esto es, lambda (λ) pertenece al lenguaje; λ es la cadena nula). Si Tam es mayor que 0, la cadena comienza en la posición Inicio (Cadena[Inicio]) y termina en Tam - 1 (Cadena[Inicio + Tam - 1] ). Devuelve ExpReg::CADNULA si Cadena == 0 o si BuscarDesde > Longitud(Cadena) Reconocedores de símbolos - Clase RecSimb Denominaremos reconocedor de símbolo al objeto que describe cómo se debe realizar la búsqueda de un símbolo de un lenguaje dado. El lenguaje dado puede ser especificado por una expresión regular, y en este caso se usaría un autómata finito determinístico para reconocer un símbolo. La otra forma 184

185 Documentación de la Biblioteca ExpReg v 1.x posible es la de usar una simple comparación de cadena, como la que se usaría para reconocer palabras claves u operadores de un lenguaje. Esta clase contempla las dos posibilidades. La declaración se la realizó como sigue: class RecSimb { public: unsigned Id; char EsAFD; const AutomFinDet * pafd; const char * pcad; ; Id: número identificador del símbolo léxico. No puede usar 0xffff porque está reservado para error léxico. Si usa 0 el símbolo léxico será ignorado por un objeto AnaLex, éste lo salteará y buscará el siguiente. No hay problemas acerca de Ids repetidos en un vector de RecSimb a ser usado por un objeto AnaLex. EsAFD: si == 0 entonces el símbolo es constante y está descripto por pcad (pcad apuntará a una cadena que es una copia del símbolo), en este caso pafd == 0. Si!= 0 entonces el reconocedor del símbolo es un Autómata Finito Determinístico apuntado por pafd, pcad == 0 en este caso. pafd: apunta a un AutomFinDet creado a mano o con un generador de AFD. Este campo debe valer 0 si EsAFD == 0. pcad: apunta a la cadena que describe al símbolo si EsAFD == 0. Por ejemplo, si el símbolo es '>=' entonces no hace falta un AFD, es más eficiente especificar ">=". Para el caso de los Naturales con el 0, estamos hablando de una familia de símbolos similares, y ya no podemos usar este campo sino que debemos usar pafd con un autómata que reconozca "[0-9]+". El siguiente es un ejemplo de definición de un vector de reconocedores de símbolos que puede servir para la construcción de un analizador lexicográfico a través de la clase AnaLex: static RecSimb vrs[] = { 1, 1, & afdblqcod, 0, 2, 0, 0, "#", 3, 0, 0, "ACCSEM", 4, 1, & afdident, 0, 5, 0, 0, "TERMINAL", 6, 0, 0, ":", 7, 0, 0, "IGNORAR", 8, 0, 0, "ELEMPILA", 9, 0, 0, ";", 10, 1, & afdflecha, 0, 185

186 Documentación de la Biblioteca ExpReg v 1.x 11, 0, 0, " ", 12, 0, 0, "*", 13, 1, & afdcadena, 0, 0, 1, & afdblancos, 0, 0, 1, & afdcomentario, 0 ; Este ejemplo ha sido extraído del módulo donde se define el analizador lexicográfico de SLR1 v 2.x. Los afdxxx han sido generados por AFD v 3.x y residen en sus respectivos archivos.cmm; no fueron transcriptos. Las cadenas de caracteres (entre comillas) son palabras claves y operadores del lenguaje. Los dos últimos reconocedores indican que se deben ignorar blancos y comentarios. Debe evitarse la utilización directa de esta clase. En su lugar permita que SLR1 v 2.x o posterior, junto con AFD v 3.x o posterior sean los que construyan el analizador lexicográfico por usted. A tal efecto consulte la documentación de SLR1 y de AFD Analizadores lexicográficos - Clase AnaLex La clase AnaLex implementa los analizadores lexicográficos. Un analizador lexicográfico se concibe como un conjunto no vacío de reconocedores de símbolos que se utilizan para el análisis de un texto de entrada. El lenguaje que reconoce el analizador lexicográfico es la unión de los lenguajes reconocidos por cada reconocedor de símbolo que componen el analizador. Esta clase ha sido definida inicialmente para servir de soporte a los analizadores sintácticos generados por SLR1 v 2.x y posteriores. De hecho, SLR1 v 2.x genera definiciones de analizadores lexicográficos implementados con esta clase de manera automática (en el archivo.cmm generado incluye una instancia de esta clase, la que posteriormente será utilizada como el analizador lexicográfico para el lenguaje a analizar). Se aconseja consultar los archivos.cmm de salida de SLR1 v 2.x o posterior a los efectos de ver la mejor forma de definir objetos AnaLex Campos dato de la clase AnaLex Los miembros dato públicos de esta clase son: unsigned ComLin; 186

187 Documentación de la Biblioteca ExpReg v 1.x Indice del comienzo de línea actual. unsigned CodError; Usado por función local Examinar para almacenar el código de error. No es usado por la implementación actual de esta clase. Su declaración es agregada a efectos de que sea usada por la función que corrige errores lexicográficos. Miembros dato protegidos: const RecSimb * vrs; Vector de reconocedores de símbolos léxicos. Tiene tamaño tvrs. unsigned tvrs; Tamaño del vector vrs. Número de elementos del conjunto de reconocedores de símbolos de este analizador lexicográfico. const char * ptxt; Puntero al inicio del texto de entrada que se está analizando. unsigned Inic; Indice del inicio para la siguiente posición a Examinar. unsigned Tama; Tamaño del último símbolo léxico reconocido. unsigned NumLin; Número de línea actual. unsigned (* ferrorlex) ( int MayIgualMin, AnaLex & analex, Ascii0 & Cad ); Puntero a una función especificada por el programador en la creación de éste objeto. La función debe resolver el error léxico que se acaba de producir, y devolver un número que indica el código del símbolo terminal siguiente o 0xffff si es que hay realmente un error léxico. En caso de que no haya error léxico, debe devolver en Cad el símbolo reconocido y modificar el analizador 187

188 Documentación de la Biblioteca ExpReg v 1.x lexicográfico analex que se le pasa como parámetro a efectos de que éste último saltee los caracteres que se acaban de reconocer. El parámetro MayIgualMin indica si se debe hacer distinción entre mayúsculas y minúsculas Funciones miembro de la clase AnaLex Funciones miembro protegidas de esta clase: AnaLex::AnaLex(); El constructor por defecto de esta clase fue declarado protegido a efectos que nadie pueda construir un objeto sin especificar un conjunto de reconocedores de símbolos. Funciones miembro públicas de esta clase: AnaLex::AnaLex( const RecSimb * pvrs, unsigned n, unsigned (* fsihayerrorlex) ( int, AnaLex &, Ascii0 & ) = 0 ); Este constructor construye un AnaLex a partir del vector de reconocedores de símbolos pvrs de tamaño n. La función fsihayerrorlex es una función que corrige un error léxico encontrado por éste analizador; su especificación es opcional, por defecto no hay función para corregir errores léxicos. void AnaLex::Reiniciar( const char * Texto ); Asigna a Texto como el texto fuente a analizar lexicográficamente. Este es una cadena válida C++, la que puede ser obtenida por lectura directa de un archivo de texto plano, de un objeto editor de Windows u OS/2, etc. Llamar a esta función pasando 0 como Texto no tiene sentido ni produce ningún efecto. 188

189 Documentación de la Biblioteca ExpReg v 1.x Si no hay problemas, se reinician todas las variables de este analizador lexicográfico de manera que el mismo comience a Examinar desde la posición 0 del Texto fuente. unsigned AnaLex::Examinar( Ascii0 & CadSimb, int MayIgualMin = 0 ); Esta función Examina desde la posición actual al texto fuente especificado en un llamado previo a Reiniciar, para ver si desde esa posición hay un símbolo que cumple con algún reconocedor de símbolo de este analizador lexicográfico. Si hay alguno devuelve el código del símbolo encontrado, si es fin de archivo (no hay más texto a partir de la posición de actual, se llegó al 0 de fin de cadena de C++) devuelve un 0. Si hay un error léxico entonces devuelve 0xffff (65535) y en el campo CodError de éste objeto el número 1. Si hay 2 o más reconocedores de símbolos que reconocen a una cadena de caracteres que comienza desde la posición actual, el conflicto se resuelve de acuerdo a las siguientes reglas: 1. Si hay un reconocedor que reconoce una secuencia de caracteres más larga que el resto de los reconocedores entonces se elige ése. En caso de empate entre dos o más reconocedores se procede según las reglas siguientes. 2. Si el grupo de reconocedores para los que se produjo el empate son autómatas finitos determinísticos (posiblemente construidos a partir de expresiones regulares), se elige el que fue listado primero en el vector de reconocedores de símbolos que se especificó como parámetro al construir éste objeto. 3. Si en el grupo hay una palabra clave o un caracter o secuencia de caracteres reservados, se elige éste. Así, por ejemplo, TERMINAL puede ser un identificador, pero si fue incluido como una cadena de caracteres dentro de un reconocedor de símbolo el analizador léxico dará prioridad a la palabra clave y lo reconocerá como tal. El comportamiento del analizador léxico que definen las tres reglas precedentes hacen que el mismo funcione exactamente igual que los generador por el LEX de UNIX, a diferencia que los construidos con objetos de esta clase no realizan movimientos innecesarios de datos. 189

190 Documentación de la Biblioteca ExpReg v 1.x unsigned AnaLex::Inicio() const; Esta función devuelve el valor del campo Inic. unsigned AnaLex::Tam() const; Esta función devuelve el valor del campo Tama. unsigned AnaLex::NumLinea() const; Esta función devuelve NumLin + 1. unsigned AnaLex::Simbolo( Ascii0 & CadSimb ) const; Esta función devuelve el tamaño del último símbolo léxico que reconoció éste analizador lexicográfico, 0 si hay problemas. Y en CadSimb la cadena del mismo. char AnaLex::Saltear(); unsigned AnaLex::Saltear( unsigned tam ); La primera versión hace que éste analizador lexicográfico saltee el caracter actual. Devuelve el caracter que se acaba de saltear, 0 si se llegó al fin del texto fuente (fin de archivo). La segunda versión llama tam veces a la primera versión. Como efecto se saltean tam caracteres a partir de la posición actual. unsigned AnaLex::CadError( Ascii0 & Cad ) const; Arma en Cad una cadena de caracteres válida del C++ en donde se muestra la línea de texto actual y la posición exacta en donde se encuentra éste analizador lexicográfico, lo que se muestra por medio de un símbolo ^ abajo del caracter actual en la línea siguiente a la actual. En el caso de que en la última llamada a Examinar no hubiera dado error, el símbolo apuntador apuntará al comienzo del símbolo léxico que se reconoció al último. Si hay problemas, Cad será una cadena Ascii0 nula (Cad.Long() valdrá 0), y la función devolverá 0. En caso contrario, devolverá la longitud de Cad (que incluirá a la línea nueva agregada). 190

191 Documentación de la Biblioteca ExpReg v 1.x 13.7 Objeto global AnaLexExpReg extern AnaLex AnaLexExpReg; Este objeto AnaLex es utilizado por ExpReg::Reasignar para realizar el análisis lexicográfico del texto de entrada fuente. Fue definido como global a los efectos de que sea utilizado por cualquiera que lo necesite. Este analizador lexicográfico devuelve 0 si se llegó al final del texto (fin de archivo), en caso contrario devuelve alguno de los siguientes códigos (definidos en EXPREG.H): _id_numeral: caracter '#' _id_ident: es un identificador. La longitud es por de dos o más caracteres. _id_ctecar: es un caracter. _id_rango: un rango especificado con '[' y ']'. _id_disyuncion: el caracter ' ' _id_opcional: el caracter '?' _id_uno_o_mas: el caracter '+' _id_cero_o_mas: el caracter '*' _id_abre_paren: el caracter '(' _id_cierra_paren: el caracter ')' Este analizador lexicográfico no fue escrito a mano, fue generado por SLR1 v 2.x. El analizador nunca devuelve error léxico. Esto se hizo especificando una función adicional que resuelve cualquier problema léxico. Antes de utilizarlo por primera vez en cada sesión de análisis léxico, recuerde Reiniciar el mismo con el texto fuente de entrada. 191

192 Parte 4. Ejemplos de Aplicación 192

193 Una Calculadora Simple Capítulo 14: Una Calculadora Simple En este capítulo se desarrollará un intérprete de expresiones aritméticas, que será usado en un programa para Windows 3.1 o posterior y OS/2 2.1 o posterior, para implementar una calculadora que incluye la posibilidad de "recordar" las últimas operaciones. El código fuente de este ejemplo se incluye al final de este capítulo Análisis y Diseño Visión General La calculadora a desarrollar consta de 2 partes bien definidas: 1. Una parte es la que implementa el evaluador de expresiones aritméticas usando un analizador sintáctico y lexicográfico. Esta parte es independiente del sistema operativo en el que funcione la calculadora, esto es, el código se escribe una sola vez y no se lo toca más a menos que haya que corregir un error. 2. La otra parte es la que usa el software de la primera parte, y es dependiente del sistema operativo donde funcionará la calculadora. Se utilizará la técnica de análisis sintáctico SLR; el analizador sintáctico será generado por SLR1 v 2.x. El analizador lexicográfico será automáticamente generado por SLR1 a partir de la especificación de la gramática para expresiones aritméticas dada de entrada. La evaluación estará a cargo de una función que ocultará todo el procesamiento inherente a la evaluación de expresiones aritméticas Sintaxis de una Expresión Aritmética La sintaxis de una expresión aritmética queda descripta por la gramática siguiente, la que con algunas modificaciones se usará como entrada para SLR1. Para la gramática se utiliza la sintaxis usada por SLR1 v 2.x. Fórmula --> Suma ; Suma --> Suma '+' Producto Suma '-' Producto Producto ; Producto --> Producto '*' Factor Producto '/' Factor Factor ; Factor --> Numero '(' Suma ')' '-' Factor ; 193

194 Una Calculadora Simple La gramática precedente define precedencias de operadores, se las resume en la siguiente tabla: Orden Operador Observaciones 1. La más alta ( ) - 2 * / 3. La más baja + - Paréntesis Menos unario Multiplicación División Suma Resta Fig. 19 Precedencia de operadores de una expresión aritmética No se incluye la posibilidad de incluir variables en una expresión aritmética. Los siguientes son ejemplos de expresiones aritméticas: Ejemplo 1: 2*5+3*2 Resultado: 16 Ejemplo 2: -32/4 + 2 Resultado: -6 La estructura de un número se describe mediante la siguiente expresión regular, la que define números muy similares a los de punto flotante del C++: // Expresión regular para un número real en formato C++: # Díg [0-9] Díg * '.'? Díg + ( (e E) Díg + )? El analizador lexicográfico ignorará los espacios en blanco. Se considera espacio en blanco a: [ \n\r\f\t\v] + Donde \n es nueva línea, \r es retorno de carro, \f es alimentación de página, \t es tab y \v es tabulado vertical Las Acciones Semánticas Las acciones semánticas que se incluirán realizan directamente la evaluación de la expresión aritmética, usando la pila para acarrear los resultados parciales de los distintos subárboles. El resultado final se encontrará en la raíz del árbol de análisis sintáctico. 194

195 Una Calculadora Simple El tipo de elemento de la pila se cambia a ElemPilaCalc, la que agrega a ElemPila el resultado parcial para esa posición de la pila. La declaración de la clase se la da en el archivo CALC.GLC, la que finalmente irá a parar al archivo CALC.CMM. La interfase en pantalla del programa será generado por Application Expert, el generador de aplicaciones para OWL 2.x de Borland C++. Sólo se agregó un poco de código a un módulo generado por AppExpert. Las acciones semánticas se muestran en el archivo CALC.GLC. En particular se debe observar la que se da para la regla "Fórmula --> Suma" que coloca el resultado final de la evaluación en una variable global llamada ResultadoFinal. También se debe observar que el error de división por 0 se detecta en la acción semántica para la regla de división, y se fija la variable global ErrDivPor0 a 1 si hay problemas. De esta manera el sistema operativo no detiene la ejecución del programa por división por La función Evaluar La función Evaluar que se encuentra en el módulo EVALUAR.CMM es la que realiza la evaluación de una expresión aritmética. Oculta todo el procesamiento que se realiza para evaluar la expresión aritmética dada en formato texto plano y emitir el resultado en formato double (real de doble precisión) del C++. El prototipo de la misma es el siguiente: Ent8 Evaluar( const char * Formula, double & Resultado ); El parámetro Formula es una cadena de caracteres válida del C++ conteniendo la fórmula a evaluar. Básicamente es un texto plano, sin formato. Se lo puede obtener de cualquier forma. El parámetro Resultado es el resultado de la evaluación de la Formula. La función devuelve 0 si hay error de sintaxis, lexicográfico o división por 0, y distinto a 0 si no hay problemas. Si no hay problemas, Resultado contiene el resultado final. 195

196 Una Calculadora Simple 14.2 Módulos fuentes independientes del Sistema Operativo A continuación se enumeran los modulos fuentes que han sido escritos para la calculadora y que son independientes del sistema operativo CALC.GLC: La gramática con acciones semánticas Este módulo se debe especificar como entrada para SLR1 v 2.x. A partir del mismo se generan los módulos CALC.CMM, CALC.TAB y CALC.EST. // Gramática fuente para una calculadora simple. // Versión Domingo E. Becker, Tel (085) %{ # ifndef SLR1_H // por las dudas # include "slr1.h" # endif class ElemPilaCalc : public ElemPila { public: double Resultado; // resultado de la operación hasta esta posición. ElemPilaCalc() { ~ElemPilaCalc() { ; static double ResultadoFinal; static Ent8 ErrDivPor0; % // definiciones para el analizador lexicográfico: # TERMINAL Numero // en numero.er está la expresión regular # IGNORAR Blancos // elemento de la pila # ELEMPILA ElemPilaCalc // Reglas: Fórmula --> Suma %{ ResultadoFinal = $1.Resultado; // asignamos a variable global. % ; Suma --> Suma '+' Producto %{ $$.Resultado = $1.Resultado + $3.Resultado; % Suma '-' Producto %{ $$.Resultado = $1.Resultado - $3.Resultado; % Producto ; Producto --> Producto '*' Factor %{ $$.Resultado = $1.Resultado * $3.Resultado; % Producto '/' Factor %{ if ($3.Resultado!= 0) $$.Resultado = $1.Resultado / $3.Resultado; else ErrDivPor0 = 1; % Factor ; 196

197 Una Calculadora Simple Factor --> Numero %{ $$.Resultado = $1.CadTerm; // conversión automática de tipo por // Ascii0 % '(' Suma ')' %{ $$.Resultado = $2.Resultado; % '-' Factor %{ $$.Resultado = - $2.Resultado; % ; CALC.CMM: módulo generado por SLR1 v 2.x A partir de CALC.GLC se generó este módulo en el cual SLR1 v 2.x escribió la función con las acciones semánticas, el analizador lexicográfico y el analizador sintáctico. Se lo incluye aquí a modo de ejemplo. // Código fuente generado por SLR1 versión 2.2 (Dic 95) // Domingo Eduardo Becker, Sgo. del Estero, Tel (085) // Código de acciones semánticas. // Código generado por Gramatica::ImprAccSem versión 2. // Domingo Eduardo Becker. # ifndef SLR1_H # include "slr1.h" # endif // Código fuente previo a las acciones semánticas. # ifndef SLR1_H // por las dudas # include "slr1.h" # endif class ElemPilaCalc : public ElemPila { public: double Resultado; // resultado de la operación hasta esta posición. ElemPilaCalc() { ~ElemPilaCalc() { ; static double ResultadoFinal; static Ent8 ErrDivPor0; // Fin del código fuente previo a las acciones semánticas. void AccSemCalc( Ent16ns NumRegla, ElemPilaCalc * Pila, Ent16ns PP ) { switch (NumRegla) { case 1: // Fórmula ---> Suma { ResultadoFinal = Pila[PP+1].Resultado; // asignamos a variable global. break; case 2: // Suma ---> Suma '+' Producto { Pila[PP+1].Resultado = Pila[PP+1].Resultado + Pila[PP+3].Resultado; break; 197

198 Una Calculadora Simple case 3: // Suma ---> Suma '-' Producto { Pila[PP+1].Resultado = Pila[PP+1].Resultado - Pila[PP+3].Resultado; break; case 5: // Producto ---> Producto '*' Factor { Pila[PP+1].Resultado = Pila[PP+1].Resultado * Pila[PP+3].Resultado; break; case 6: // Producto ---> Producto '/' Factor { if (Pila[PP+3].Resultado!= 0) Pila[PP+1].Resultado = Pila[PP+1].Resultado / Pila[PP+3].Resultado; else ErrDivPor0 = 1; break; case 8: // Factor ---> Numero { Pila[PP+1].Resultado = Pila[PP+1].CadTerm; // conversión automática de tipo por Ascii0 break; case 9: // Factor ---> '(' Suma ')' { Pila[PP+1].Resultado = Pila[PP+2].Resultado; break; case 10: // Factor ---> '-' Factor { Pila[PP+1].Resultado = - Pila[PP+2].Resultado; break; // switch (NumRegla) // Fin de AccSemCalc // Fin del código de acciones semánticas. // Analizador lexicográfico para la gramática. // Generado por Gramatica::ImprAnaLex versión 2. // Domingo Eduardo Becker. # ifndef EXPREG_H # include "expreg.h" # endif # include "Blancos.cmm" // afdblancos # include "Numero.cmm" // afdnumero static RecSimb vrs[] = { 1, 0, 0, "+", 2, 0, 0, "-", 3, 0, 0, "*", 4, 0, 0, "/", 5, 1, & afdnumero, 0, 6, 0, 0, "(", 7, 0, 0, ")", 0, 1, & afdblancos, 0 ; # define NUMRECSIMB 8 AnaLex alcalc(vrs, NUMRECSIMB); // Definición del analizador sintáctico (SLR1 v 2.2). 198

199 Una Calculadora Simple // Cambiar de lugar la definición si es necesario. # include "CALC.tab" // tablas del analizador sintáctico AnalSLR1<ElemPilaCalc> ascalc(ntlpdcalc, accioncalc, ir_acalc,accsemcalc); NUMERO.ER: expresión regular para un número real no negativo La expresión regular que se encuentra en este módulo se especifica como entrada a AFD v 3.x. El sufijo a utilizar es Numero. // Expresión regular para un número real en formato C++: # Díg [0-9] Díg * '.'? Díg + ( (e E) Díg + )? BLANCOS.ER: expresión regular para los blancos de un texto La expresión regular siguiente se especificará como entrada a AFD v 3.x. El sufijo a utilizar es Blancos. // Expresión regular para blancos: [ \n\r\f\t\v] NUMERO.CMM: generado por AFD v 3.x a partir de NUMERO.ER Se lo incluye a modo de ejemplo. // AFD generado por la versión 3.4 de ExpReg::Reasignar (Nov 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char ernumero[] = "// Expresión regular para un número real en formato C++:\r\n# Díg [0-9]\r\nDíg * \'.\'? Díg + ( (e E) Díg + )?\r\n"; static const char * vcnumero[] = { "0-9", ".", "e", "E" ; static const Ent16ns tnumero[] = { 1, 2, TNDEF, TNDEF, 1, 2, 3, 3, 4, TNDEF, TNDEF, TNDEF, 5, TNDEF, TNDEF, TNDEF, 4, TNDEF, 3, 3, 5, TNDEF, TNDEF, TNDEF ; static const Ent16ns efnumero[] = { 3, 1, 4, 5 ; AutomFinDet afdnumero = { ernumero, vcnumero, 4, tnumero, efnumero ; 199

200 Una Calculadora Simple BLANCOS.CMM: generado por AFD v 3.x a partir de BLANCOS.ER Se lo incluye a modo de ejemplo. // AFD generado por la versión 3.4 de ExpReg::Reasignar (Nov 95) // Domingo Eduardo Becker, Sgo. del Estero, (085) /1088. # ifndef EXPREG_H # include "expreg.h" # endif static const char ernumero[] = "// Expresión regular para un número real en formato C++:\r\n# Díg [0-9]\r\nDíg * \'.\'? Díg + ( (e E) Díg + )?\r\n"; static const char * vcnumero[] = { "0-9", ".", "e", "E" ; static const Ent16ns tnumero[] = { 1, 2, TNDEF, TNDEF, 1, 2, 3, 3, 4, TNDEF, TNDEF, TNDEF, 5, TNDEF, TNDEF, TNDEF, 4, TNDEF, 3, 3, 5, TNDEF, TNDEF, TNDEF ; static const Ent16ns efnumero[] = { 3, 1, 4, 5 ; AutomFinDet afdnumero = { ernumero, vcnumero, 4, tnumero, efnumero ; EVALUAR.CMM: función Evaluar Obsérvese en este módulo la simplicidad del uso del analizador sintáctico y del analizador lexicográfico generado. // evaluar.cmm Jue 07 Dic 95 // Domingo E. Becker # include "calc.cmm" // generado a partir de calc.glc por SLR1 v 2.x Ent8 Evaluar( const char * Formula, double & Resultado ) { if (Formula == 0) return 0; // imposible evaluar Formula alcalc.reiniciar(formula); Ascii0 CadPosErr; ErrDivPor0 = 0; Ent8 r = ascalc.analizar(alcalc, CadPosErr) &&! ErrDivPor0; if (r) Resultado = ResultadoFinal; return r; 14.3 Módulos dependientes del sistema operativo La interfase en pantalla es la parte dependiente del sistema operativo de la calculadora. 200

201 Una Calculadora Simple Se eligió hacer un diálogo en el que habrá un campo de entrada de texto en donde se escribirá la fórmula a evaluar, otro en donde se mostrará el resultado, una lista de cadenas en donde se encuentren las últimas fórmulas evaluadas y dos botones, uno para evaluar y otro para salir (ver figura 20). Visualmente se ve lo siguiente: Fig. 20 Ventana principal de la Calculadora. La ventana ha sido construida usando el Resource Workshop del Borland C++. AppExpert se encargó de realizar y controlar todas las conexiones del diálogo con el código fuente que él mismo generó. Se hace la observación de que todos los módulos pueden ser recompilados sin ninguna modificación con el Borland C para OS/2. Sólo se debe procesar el archivo CALCAPP.RC con el convertidor de recursos para convertirlo a formato Presentation Manager de OS/2 (el procesado es muy simple). Los módulos fuente generado se muestran a continuación CALCAPP.H: generado por AppExpert #if!defined( calcapp_h) not already included. #define calcapp_h // Sentry, use file only if it's /* Project calc Copyright All Rights Reserved. 201

202 Una Calculadora Simple SUBSYSTEM: calc.exe Application FILE: calcapp.h AUTHOR: Domingo Eduardo Becker */ OVERVIEW ======== Class definition for TCalcApp (TApplication). #include <owl\owlpch.h> #pragma hdrstop #include "calcapp.rh" // Definition of all resources. //{{TApplication = TCalcApp class TCalcApp : public TApplication { private: public: TCalcApp (); virtual ~TCalcApp (); //{{TCalcAppVIRTUAL_BEGIN public: virtual void InitMainWindow(); //{{TCalcAppVIRTUAL_END //{{TCalcAppRSP_TBL_BEGIN protected: void CmHelpAbout (); //{{TCalcAppRSP_TBL_END DECLARE_RESPONSE_TABLE(TCalcApp); ; //{{TCalcApp #endif // calcapp_h sentry CALCDLG.H: generado por AppExpert #if!defined( calcdlg_h) not already included. #define calcdlg_h // Sentry, use file only if it's /* Project calc Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: calc.exe Application calcdlg.h Domingo Eduardo Becker */ OVERVIEW ======== Class definition for TCalcDLGClient (TDialog). #include <owl\owlpch.h> #pragma hdrstop 202

203 Una Calculadora Simple #include <owl\edit.h> #include <owl\listbox.h> #include "calcapp.rh" // Definition of all resources. //{{TDialog = TCalcDLGClient struct TCalcDLGClientXfer { //{{TCalcDLGClientXFER_DATA TListBoxData ListaDeFormulas; //{{TCalcDLGClientXFER_DATA_END ; class TCalcDLGClient : public TDialog { public: TCalcDLGClient (TWindow *parent, TResId resid = IDD_CLIENT, TModule *module = 0); virtual ~TCalcDLGClient (); //{{TCalcDLGClientVIRTUAL_BEGIN public: virtual void SetupWindow (); //{{TCalcDLGClientVIRTUAL_END //{{TCalcDLGClientRSP_TBL_BEGIN protected: void Evaluar (); void DobleClickLista (); //{{TCalcDLGClientRSP_TBL_END DECLARE_RESPONSE_TABLE(TCalcDLGClient); //{{TCalcDLGClientXFER_DEF protected: TListBox *ListaDeFormulas; //{{TCalcDLGClientXFER_DEF_END ; //{{TCalcDLGClient #endif // calcdlg_h sentry CALCAPP.CMM: generado por AppExpert /* Project calc Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: calc.exe Application calcapp.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of TCalcApp (TApplication). #include <owl\owlpch.h> #pragma hdrstop #include "calcapp.h" 203

204 Una Calculadora Simple #include "calcdlg.h" // Definition of client class. //{{TCalcApp Implementation // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(TCalcApp, TApplication) //{{TCalcAppRSP_TBL_BEGIN EV_COMMAND(CM_HELPABOUT, CmHelpAbout), //{{TCalcAppRSP_TBL_END END_RESPONSE_TABLE; ////////////////////////////////////////////////////////// // TCalcApp // ===== // TCalcApp::TCalcApp () : TApplication("Calculadora") { // INSERT>> Your constructor code here. TCalcApp::~TCalcApp () { // INSERT>> Your destructor code here. ////////////////////////////////////////////////////////// // TCalcApp // ===== // Application intialization. // void TCalcApp::InitMainWindow () { // if (ncmdshow!= SW_HIDE) // ncmdshow = (ncmdshow!= SW_SHOWMINNOACTIVE)? SW_SHOWNORMAL : ncmdshow; TFrameWindow *frame = new TFrameWindow(0, GetName(), new TCalcDLGClient(0), true); // Override the default window style for the main window. // frame->attr.style = WS_BORDER WS_CAPTION WS_CLIPCHILDREN WS_MINIMIZEBOX WS_SYSMENU WS_VISIBLE; frame->attr.style &= ~(WS_MAXIMIZEBOX WS_THICKFRAME); // // Assign ICON w/ this application. // frame->seticon(this, IDI_SDIAPPLICATION); SetMainWindow(frame); ////////////////////////////////////////////////////////// 204

205 Una Calculadora Simple // TCalcApp // =========== // Menu Help About calc.exe command void TCalcApp::CmHelpAbout () { int OwlMain (int, char* []) { try { TCalcApp app; return app.run(); catch (xmsg& x) { ::MessageBox(0, x.why().c_str(), "Exception", MB_OK); return -1; CALCDLG.CMM: generado por AppExpert y modificado por el autor Usando Class Expert (del AppExpert) se agregaron respuestas al botón Evaluar y al doble click con el ratón a una fórmula dentro de la lista de fórmulas ya evaluadas. Las funciones se llaman TCalcDLGClient::Evaluar y TCalcDLGClient::DobleClickLista respectivamente. /* Project calc Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: calc.exe Application calcdlg.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of TCalcDLGClient (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "calcapp.h" #include "calcdlg.h" # include <ascii0.h> Ent8 Evaluar( const char * Formula, double & Resultado ); // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(TCalcDLGClient, TDialog) //{{TCalcDLGClientRSP_TBL_BEGIN EV_BN_CLICKED(IDOK, Evaluar), EV_LBN_DBLCLK(IDC_LISTAFORMULAS, DobleClickLista), //{{TCalcDLGClientRSP_TBL_END END_RESPONSE_TABLE; 205

206 Una Calculadora Simple //{{TCalcDLGClient Implementation ////////////////////////////////////////////////////////// // TCalcDLGClient // ========== // Construction/Destruction handling. static TCalcDLGClientXfer TCalcDLGClientData; TCalcDLGClient::TCalcDLGClient (TWindow *parent, TResId resid, TModule *module) : TDialog(parent, resid, module) { //{{TCalcDLGClientXFER_USE ListaDeFormulas = new TListBox(this, IDC_LISTAFORMULAS); SetTransferBuffer(&TCalcDLGClientData); //{{TCalcDLGClientXFER_USE_END // INSERT>> Your constructor code here. TCalcDLGClient::~TCalcDLGClient () { Destroy(); // INSERT>> Your destructor code here. void TCalcDLGClient::Evaluar () { // INSERT>> Your code here. Ascii0 formula(300), resultadoascii; double resultado; Ent8 r; GetDlgItemText(IDC_FORMULA, formula, formula.tamblq()); if (formula.long()) { if (::Evaluar(formula, resultado)) { resultadoascii = resultado; SetDlgItemText(IDC_RESULTADO, resultadoascii); else SetDlgItemText(IDC_RESULTADO, "Error"); ListaDeFormulas->InsertString(formula, 0); void TCalcDLGClient::DobleClickLista () { // INSERT>> Your code here. Ascii0 cad(300); ListaDeFormulas->GetSelString(cad, cad.tamblq()); if (cad.long()) { SetDlgItemText(IDC_FORMULA, cad); TWindow(GetDlgItem(IDC_FORMULA)).SetFocus(); // Formula->SetFocus(); void TCalcDLGClient::SetupWindow () { TDialog::SetupWindow(); 206

207 Una Calculadora Simple // INSERT>> Your code here. TWindow(GetDlgItem(IDC_FORMULA)).SetFocus(); // Formula->SetFocus(); 207

208 Consultas a Archivos Base de Datos BCE Capítulo 15: Consultas a Archivos Base de Datos BCE El ejemplo desarrollado en este capítulo es un ejemplo de recuperación inteligente de la información en memoria. A tal efecto, antes se debe recuperar la información de un archivo base de datos. Cuando sea oportuno se mostrarán analogías con las sentencias de consultas SQL (structured query language). La Biblioteca BCE para C++ se usa aquí como una herramienta. El Administrador de Archivos Bases de Datos BCE es parte de esa biblioteca, y aunque su código fuente es incluido al final de este capítulo, su documentación no se incluye Conceptos Teóricos Básicos Bases de Datos Un archivo base de datos es una colección de registros, en el que se incluye principalmente información sobre la descripción de la estructura del registro, así como también otra información que pueda ser importante. Se puede representar por uno o más archivos del nivel del Sistema Operativo, aunque usualmente, un archivo base de datos se representa con un archivo del sistema operativo. Se denomina archivo del sistema operativo al concepto de archivo ofrecido por el mismo. Un archivo para el sistema operativo es una colección de bytes, cada uno de los cuales pueden ser accedidos al azar como un vector (si se desea) o bien secuencialmente, y de la E/S se encarga el Sistema Operativo. En general, un archivo del sistema operativo reside en memoria persistente (disco, cinta, etc.). En la documentación de Paradox (cualquier versión) y dbase V, a los archivos bases de datos se los denomina tablas. Una base de datos es una colección de tablas (archivos bases de datos). Se denomina cursor al nombre lógico usado para acceder a la tabla. Para una tabla, en una misma sesión pueden haber varios cursores. La denominación de tabla proviene de la confección manual de tablas que antes se hacía, un archivo base de datos debe 208

209 Consultas a Archivos Base de Datos BCE ser visto como una tabla en donde las filas son los registros y las columnas son los campos de los registros. A continuación, archivo base de datos se abreviará ABD. La razón de almacenar la descripción de la estructura del registro en el archivo es la de obtener la independencia del código fuente de la estructura del registro. Si no se usan ABDs, una vez escrito el código fuente, si la estructura del registro cambia se debe retocar el código fuente. En cambio, si se usaran ABDs, sólo se deben retocar los campos que cambiaron y/o agregar los accesos a los campos nuevos. El acceso a la implementación lógica del registro lo realiza el Administrador de Archivos Bases de Datos. El administrador de bases de datos trata con varias bases de datos a la vez; no debe ser confundido con el administrador de ABDs. El acceso a la implementación física del registro en el medio lo realiza el sistema operativo. Para este ejemplo, se utilizará el Administrador de Archivos Bases de Datos de la Biblioteca BCE, cuyo autor es quien suscribe. El código fuente se incluye al final de este capítulo El Lenguaje de Consultas Estructurado (SQL) Con la aparición de las bases de datos se hizo necesario un lenguaje especial que pudiera describir transacciones con los mismos. Estos lenguajes recibieron el nombre de lenguajes para bases de datos, de entre todos ellos, el más popular es el del dbase, el cual fue definido por convención como el estándar. Lamentablemente (para los programadores serios), ese lenguaje nació como un engendro que no cumplía con los conceptos más básicos y fundamentales de la ingeniería del software, pero era fácil de aprender y entender para los novatos que no tenían ninguna idea de programación. (Si resulta que un programador acostumbrado a trabajar con lenguajes como el C++ aprende el dbase, lo primero que se le viene a la cabeza es decir que, como lenguaje de programación, el dbase es un desastre. Esto no quita de que sea posible realizar grandes proyectos de programación con él.). A todo esto hay que agregarle que cada empresa que construía un paquete de bases de datos comercial, hacía un lenguaje que se decía ser compatible dbase, lo cual no resultó ser cierto. Con el lenguaje de consultas estructurado se intentó unificar los criterios y conceptos, así como también ofrecer un conjunto de comandos que permiten 209

210 Consultas a Archivos Base de Datos BCE agregar, cambiar y borrar datos, y una gran habilidad para consultar sin codificar demasiado. De todas las sentencias que tiene ese lenguaje, la más interesante y difícil de implementar es la sentencia SELECT. La sentencia SELECT selecciona registros de una base de datos de acuerdo a ciertos criterios que se especifican con cláusulas. Entre esas cláusulas se encuentra la cláusula LIKE que significa parecido a. La inclusión de esta cláusula hace necesario el uso de expresiones regulares para especificar la forma general a la que debe parecerse. El resultado de esta sentencia es una nueva tabla que contiene los registros seleccionados. Queda a consideración del diseñador del administrador de bases de datos si se creará la tabla nueva o si se trabajará directamente con las tablas que participan en la sentencia SELECT. El lenguaje Object PAL de Paradox 1.0 para Windows en adelante, incluye una sentencia muy interesante que comienza con la palabra clave QUERY, y no es otra cosa que la codificación de una consulta mediante ejemplos. En esa sentencia se incluye también la cláusula LIKE, con posibilidades similares a la cláusula LIKE de una sentencia SELECT de SQL. El resultado de una consulta con QUERY es una tabla, que tiene un nombre físico por defecto si no se lo especifica. En este ejemplo se simulará el funcionamiento del selector de registros de un administrador de bases de datos que trabaja con un lenguaje SQL Niveles de Abstracción en el Administrador de Archivos Bases de Datos BCE. La clase Archivo define la interfase general y mínima de una clase que se dice ser Archivo. En BCE un Archivo es un recipiente de bytes, con accesos básicos de lectura y escritura para los tipos de datos fundamentales (se consideran tipos de datos fundamentales a los tipos que provee el compilador). La clase Archivo es abstracta, lo que significa que no se pueden definir objetos de esta clase. Esta clase define el nivel más bajo de abstracción. Cualquier detalle particular de una implementación deberá ser ocultado por la clase derivada. La clase ArchivoSO es una clase derivada de Archivo, en la que se dan definiciones a las funciones virtuales puras declaradas en la clase base. Esta clase sirve de interfase para el acceso a las funciones básicas para el manejo de archivos en dispositivos de almacenamiento masivo ofrecidas por el Sistema Operativo. Las funciones básicas que ofrece el sistema operativo son: abrir, 210

211 Consultas a Archivos Base de Datos BCE leer, escribir, posicionar el puntero de archivo y cerrar. En ArchivoSO se provee el acceso directo a estas funciones. Para la E/S de datos se provee la posibilidad de controlar si será binaria o en formato texto. Si es binario, por ejemplo para un float se escribiría sizeof(float) = 4 bytes, en cambio, si es en formato entonces se pueden llegar a escribir hasta 32 bytes, que serán los caracteres correspondientes a la representación en texto del número. La clase ArchivoSO está en el mismo nivel de abstracción que la clase Archivo, sólo es una implementación particular del concepto de archivo introducido por BCE (que no es otra cosa que el mismo concepto introducido por COBOL). La clase ArchBD (que no es derivada de Archivo) implementa el administrador de archivos bases de datos. El código del administrador son las funciones miembro de esta clase. Cada objeto de esta clase es el nombre lógico de un archivo base de datos; en Paradox o dbase se diría cursor. Para cada archivo físico pueden haber varios cursores, esto es, varios objetos ArchBD con los que se estén realizando accesos. Esta clase ofrece las siguientes funciones básicas: abrir, leer registro, escribir registro, posicionar el puntero de archivo, cerrar y acceder a un campo particular del registro. Esta clase está en un nivel más alto de abstracción que la clase Archivo. En ArchBD los accesos se hacen por bloques de bytes, todos del mismo tamaño, a los que se los denomina registro. La estructura de los registros se incluye en el mismo archivo físico Coincidencia exacta / ocurrencia Dada una expresión que define un lenguaje (puede ser una expresión regular o un literal), se dirá que hay coincidencia exacta entre una cadena de caracteres y la expresión dada si la cadena de caracteres pertenece al lenguaje definido por la expresión (es oración de ese lenguaje). Por el contrario, se dirá que hay una ocurrencia de una oración del lenguaje definido por la expresión dentro de la cadena de caracteres dada, si existe alguna subcadena dentro de la cadena dada para la cual hay coincidencia exacta con la expresión dada. Para los usuarios normales de computadoras (que en general no son programadores), las definiciones precedentes son más comprensibles que los conceptos manejados en la Teoría de los Lenguajes Formales. Se los define aquí para poder usarlos más adelante en este ejemplo. 211

212 Consultas a Archivos Base de Datos BCE Se debe recordar que si la expresión dada es tomada como un literal, el lenguaje definido por la misma tendrá sólo una oración. Puede tener más de una oración si es tomada como expresión regular Análisis y Diseño Objetivos y metas de este ejemplo El objetivo de este ejemplo es el de acceder a un archivo base de datos BCE, y permitir realizar una consulta para un solo campo del mismo. Las metas para llegar al objetivo son: 1. Leer la estructura del registro y mostrar los campos del mismo en pantalla, permitiendo la selección de un solo campo. 2. Definir una expresión que dirá cómo debe ser el contenido de un campo. Asimismo, se incluirá la posibilidad de usar expresiones regulares y de indicar si la concordancia debe ser exacta o que contenga. 3. Para todos los campos que cumplen con la expresión dada se mostrará el número de registro y el contenido del campo en la pantalla Visión general del diseño de la interfase y el funcionamiento La interfase en pantalla del programa es la siguiente: 212

213 Consultas a Archivos Base de Datos BCE Fig. 21 Ventana principal del programa ejemplo de consultas Al arrancar el programa se le pedirá al usuario que ingrese un nombre de archivo o lo busque en los dispositivos de almacenamiento masivo. Una vez hecho esto se muestra el nombre del archivo en el campo Archivo de la ventana principal (ver Fig. 21). En la lista etiquetada con Campo a consultar se muestran los campos del archivo seleccionado, de los cuales se deberá seleccionar uno. En el campo Expresión se debe ingresar la expresión a usar en la consulta, la que puede ser un literal o una expresión regular (en ambos casos es una cadena de caracteres). Si es una expresión regular, se debe chequear la caja de chequeo etiquetada con Expresión Regular (aparecerá una cruz dentro del cuadradito cuando esté chequeada, de manera similar a la que se muestra en la misma Fig. 21). Si la consulta a realizar es por coincidencia exacta, la caja de chequeo etiquetada con Que contenga no debe estar chequeada. Si, en cambio, se busca 213

214 Consultas a Archivos Base de Datos BCE una ocurrencia de una oración dentro del campo a consultar, la caja de chequeo Que contenga debe estar chequeada (con una cruz). Para realizar la consulta se debe presionar el botón Consultar. Para poder consultar se debe ingresar la expresión regular y seleccionar el campo a consultar, caso contrario habrá error. Una vez presionado el botón para consultar, se limpiará la lista Registros que verifican y se la llenará con los contenidos de los campos de los registros que verifican con la expresión ingresada. Primero se colocará el número de registro y luego el valor del campo. En el único momento en que se mantiene abierto el archivo es cuando se está realizando la consulta (desde el momento de presionar Consultar hasta el momento en que se terminó de recorrer todo el archivo; luego se lo cierra). De esta manera se minimiza el número de archivos abiertos en el sistema. El botón Otro archivo permite seleccionar otro archivo base de datos. El diálogo para ingresar el nombre es similar al que se muestra al arrancar el programa, y es el estándar del API (application programming interfase) del sistema. Al aceptar el nombre, se intenta abrir el archivo base de datos. Si no hay problemas, se extraen los nombres de los campos y se los muestra en la lista Campo a consultar (previo vaciado de la misma); luego se cierra el archivo. Si no se puede abrir el archivo entonces se vuelve al estado anterior de la pantalla principal Implementación de la carga de la lista de campos La carga de la lista de campos la realiza la función ConsDlgClient:: CargarCampos, donde ConsDlgClient es la clase que representa a la ventana principal. Se intenta abrir el archivo base de datos cuyo nombre se pasa como parámetro. Si la apertura del archivo no falla, se limpia la lista de campos y se la llena con los campos del archivo que se acaba de abrir. Las funciones utilizada para averiguar los campos son: ArchBD::NumCampos y ArchBD::CampoNumero. La segunda devuelve el nombre del campo cuyo índice se pasa como parámetro. Al finalizar la carga de la lista de campos se cierra el archivo base de datos. 214

215 Consultas a Archivos Base de Datos BCE Implementación de la función que realiza la consulta La función que recibe la petición del usuario de realizar una consulta con los parámetros ingresados por pantalla es ConsDlgClient::Consultar. La función que realiza la consulta se llama Consultar y es global. El prototipo de la función Consultar es el siguiente: Ent16 Consultar( const char * NomArchBD, const char * Campo, TListBox * Registros, const char * Expresion, Ent8 EsExpReg = 1, Ent8 QueContenga = 1 ); El parámetro NomArchBD recibe el nombre del archivo base de datos a consultar. El parámetro Campo es el nombre del campo con el que se trabajará. El parámetro Registros es una lista de cadenas de caracteres. Inicialmente se la vaciará. Luego se la llenará con los registros cuyo campo Campo cumple con la expresión dada en Expresion. El parámetro Expresion contiene la expresión a usar para la consulta. El parámetro EsExpReg contiene 0 si Expresion no es expresión regular, y distinto de 0 si es expresión regular. El valor por defecto es que Expresion es expresión regular. El parámetro QueContenga tiene 0 si se buscará coincidencia exacta, y distinto a 0 si se buscarán ocurrencias. El valor por defecto es buscar ocurrencias. Si se observa el código fuente de esta función (en el módulo DLGCLI.CMM) se verá la simplicidad de la misma. Esta función sólo utiliza funciones provistas por el Administrador de Archivos Bases de Datos BCE, por la biblioteca ExpReg v 1.x y por la biblioteca estándar del C++. Los registros se leen uno por uno. Para cada uno, se extrae el campo en formato texto en una variable aparte del tipo Ascii0, con nombre CampoRegAct. Luego, como el contenido del campo está en la memoria de la máquina, entonces la recuperación inteligente de la información (consulta) se la realiza en memoria. Si EsExpReg entonces se utiliza un objeto ExpReg para realizar la consulta. En este caso, si se buscan ocurrencias (QueContenga) entonces se 215

216 Consultas a Archivos Base de Datos BCE usa la función ExpReg::Buscar, caso contrario se usa la función ExpReg:: Examinar. Estas dos funciones son de la biblioteca ExpReg. Si no EsExpReg entonces se hace simple comparación de cadenas con alguna función estándar del C++, a saber: si se buscan ocurrencias se utiliza la función strstr, y si se busca coincidencia exacta entonces stricmp. Esta última realiza comparación de cadenas asumiendo que las mayúsculas son iguales a las minúsculas. Si el campo seleccionado no es una cadena de caracteres (es algún otro tipo que fue almacenado en formato binario), entonces la conversión a cadena de caracteres se realiza automáticamente. Se utiliza la función ArchBD:: SacarCampoAutoConv, que convierte a texto automáticamente si el campo a extraer es almacenado en formato binario. Esta función da soporte a todos los tipos de datos fundamentales del C++, y trabaja conjuntamente con la clase Ascii0 de BCE Módulos fuentes de este ejemplo Gran parte de los módulos fuente han sido generado por AppExpert, el generador de interfases de aplicaciones que utilizan OWL 2.x para Windows y OS/2, del Turbo C para Windows. La aplicación generada fue probada en Windows. Procesando el archivo de recursos generado con el conversor de recursos del Borland C para OS/2 se lo puede recompilar sin problemas y obtener una versión para OS/2 de este ejemplo. Los módulos fuentes que han sido retocados por el autor a efectos de la implementación del ejemplo son DLGCLI.CMM y DLGCLI.H. A continuación se enumerarán los módulos fuentes DLGCLI.CMM: generado por AppExpert y modificado por el autor /* Project consulta Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: consulta.exe Application dlgcli.cmm Domingo Eduardo Becker OVERVIEW ======== Source file for implementation of ConsDlgClient (TDialog). 216

217 Consultas a Archivos Base de Datos BCE */ #include <owl\owlpch.h> #pragma hdrstop #include "consapp.h" #include "dlgcli.h" # include <owl\opensave.h> # include <bd.h> # include <expreg.h> // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(ConsDlgClient, TDialog) //{{ConsDlgClientRSP_TBL_BEGIN EV_BN_CLICKED(IDC_CONSULTAR, Consultar), EV_BN_CLICKED(IDC_CAMBIARARCH, CambiarArchivo), //{{ConsDlgClientRSP_TBL_END END_RESPONSE_TABLE; //{{ConsDlgClient Implementation ////////////////////////////////////////////////////////// // ConsDlgClient // ========== // Construction/Destruction handling. static ConsDlgClientXfer ConsDlgClientData; ConsDlgClient::ConsDlgClient (TWindow *parent, TResId resid, TModule *module) : TDialog(parent, resid, module) { //{{ConsDlgClientXFER_USE ListaDeCampos = new TListBox(this, IDC_CAMPOS); ListaDeRegistros = new TListBox(this, IDC_REGISTROS); QueContenga = new TCheckBox(this, IDC_CONTENGA, 0); EsExpReg = new TCheckBox(this, IDC_EXPREG, 0); SetTransferBuffer(&ConsDlgClientData); //{{ConsDlgClientXFER_USE_END // INSERT>> Your constructor code here. ConsDlgClient::~ConsDlgClient () { Destroy(); // INSERT>> Your destructor code here. const char NomArch[] = "agenda.bd"; int ConsDlgClient::CargarCampos( const char * NomArch, TListBox * Campos ) { ArchBD arch; if (arch.abrir(nomarch, MA_L)) return CodError; 217

218 Consultas a Archivos Base de Datos BCE Campos->ClearList(); Ent16ns i, NumCampos = arch.numcampos(); for (i = 0; i < NumCampos; ++i) ListaDeCampos->AddString( arch.camponumero(i)->nombre ); arch.cerrar(); return CodError; void ConsDlgClient::SetupWindow () { TDialog::SetupWindow(); // INSERT>> Your code here. ListaDeCampos->ClearList(); ListaDeRegistros->ClearList(); QueContenga->SetCheck(BF_CHECKED); EsExpReg->SetCheck(BF_CHECKED); CambiarArchivo(); Ent16 Consultar( const char * NomArchBD, const char * Campo, TListBox * Registros, const char * Expresion, Ent8 EsExpReg = 1, Ent8 QueContenga = 1 ) { // Si no hay campos seleccionados entonces Operación Inválida if (Campo == 0! *Campo Expresion == 0) return CodError = E_OPINV; // Intentar abrir el archivo para Lectura ArchBD arch; if (arch.abrir(nomarchbd, MA_L)) return CodError; Registros->ClearList(); // Armar la expresión regular si es necesario: ExpReg ExprRegular; Ascii0 CadPosError; if (EsExpReg &&! ExprRegular.Reasignar(Expresion, CadPosError)) return CodError = E_OPINV; // Comienza la consulta: Ascii0 Reg, CampoRegAct; Ent8 Cumple; Ent16ns i; unsigned inicio, tam; for (i = 0;! arch.leer(); ++i) { // Sacamos el campo y lo convertimos en cadena de caracteres if (arch.sacarcampoautoconv(campo, CampoRegAct)) break; // Error // Consultar: if (EsExpReg) Cumple = QueContenga?! ExprRegular.Buscar(CampoRegAct, inicio, tam) && tam :! ExprRegular.Examinar(CampoRegAct, 0, tam); else Cumple = QueContenga? strstr((const char *) CampoRegAct, Expresion)!= 0 : stricmp(camporegact, Expresion) == 0; // Si cumple agregarlo a la lista: if (Cumple) { Reg.printf("%u: %s", i, (char *) CampoRegAct); 218

219 Consultas a Archivos Base de Datos BCE Registros->AddString(Reg); arch.cerrar(); return CodError = E_SINERROR; void ConsDlgClient::Consultar () { // INSERT>> Your code here. // Obtener expresión regular: Ascii0 Expr(100); GetDlgItemText(IDC_EXPRESION, Expr, Expr.TamBlq()); // Obtener el nombre del campo a consultar: Ascii0 NomCampo(100); if (! ListaDeCampos->GetSelCount()) { MessageBox("Seleccione un campo.", "ERROR", MB_ICONHAND MB_OK); return; ListaDeCampos->GetSelString(NomCampo, NomCampo.TamBlq()); if (! NomCampo.Long()) { MessageBox("No se pudo determinar el nombre del campo.", "ERROR", MB_ICONHAND MB_OK); return; // Llamar a la función que realiza la consulta: if (::Consultar(NomArch, NomCampo, ListaDeRegistros, Expr, EsExpReg->GetCheck() == BF_CHECKED, QueContenga->GetCheck() == BF_CHECKED)) MessageBox("Problemas en función ::Consultar", "ERROR", MB_OK MB_ICONHAND); void ConsDlgClient::CambiarArchivo () { // INSERT>> Your code here. Ascii0 NomArch(100); TOpenSaveDialog::TData DatosDlg(OFN_HIDEREADONLY OFN_FILEMUSTEXIST OFN_NOCHANGEDIR, "Tipos ArchBD (*.BD) *.bd Todos los archivos (*.*) *.* ", 0, 0,"bd"); TFileOpenDialog dlg(this, DatosDlg); if (dlg.execute() == IDOK) { if (CargarCampos(DatosDlg.FileName, ListaDeCampos)) MessageBox("El archivo no es tipo ArchBD", "ERROR", MB_OK MB_ICONHAND); else SetDlgItemText(IDC_NOMARCH, DatosDlg.FileName); DLGCLI.H: generado por AppExpert y modificado por el autor #if!defined( dlgcli_h) already included. #define dlgcli_h // Sentry, use file only if it's not 219

220 Consultas a Archivos Base de Datos BCE /* Project consulta Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: consulta.exe Application dlgcli.h Domingo Eduardo Becker */ OVERVIEW ======== Class definition for ConsDlgClient (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include <owl\checkbox.h> #include <owl\listbox.h> #include "consapp.rh" // Definition of all resources. //{{TDialog = ConsDlgClient struct ConsDlgClientXfer { //{{ConsDlgClientXFER_DATA TListBoxData ListaDeCampos; TListBoxData ListaDeRegistros; bool QueContenga; bool EsExpReg; //{{ConsDlgClientXFER_DATA_END ; class ConsDlgClient : public TDialog { public: ConsDlgClient (TWindow *parent, TResId resid = IDD_CLIENT, TModule *module = 0); virtual ~ConsDlgClient (); int CargarCampos( const char * NomArch, TListBox * Campos ); //{{ConsDlgClientVIRTUAL_BEGIN public: virtual void SetupWindow (); //{{ConsDlgClientVIRTUAL_END //{{ConsDlgClientXFER_DEF protected: TListBox *ListaDeCampos; TListBox *ListaDeRegistros; TCheckBox *QueContenga; TCheckBox *EsExpReg; //{{ConsDlgClientXFER_DEF_END //{{ConsDlgClientRSP_TBL_BEGIN protected: void Consultar (); void CambiarArchivo (); //{{ConsDlgClientRSP_TBL_END DECLARE_RESPONSE_TABLE(ConsDlgClient); ; //{{ConsDlgClient #endif // dlgcli_h sentry. 220

221 Consultas a Archivos Base de Datos BCE CONSAPP.CMM: generado por AppExpert /* Project consulta Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: consulta.exe Application consapp.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of ConsultaApp (TApplication). #include <owl\owlpch.h> #pragma hdrstop #include "consapp.h" #include "dlgcli.h" // Definition of client class. //{{ConsultaApp Implementation // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(ConsultaApp, TApplication) //{{ConsultaAppRSP_TBL_BEGIN EV_COMMAND(CM_HELPABOUT, CmHelpAbout), //{{ConsultaAppRSP_TBL_END END_RESPONSE_TABLE; ////////////////////////////////////////////////////////// // ConsultaApp // ===== // ConsultaApp::ConsultaApp () : TApplication("Principal") { // INSERT>> Your constructor code here. ConsultaApp::~ConsultaApp () { // INSERT>> Your destructor code here. ////////////////////////////////////////////////////////// // ConsultaApp // ===== // Application intialization. // void ConsultaApp::InitMainWindow () { 221

222 Consultas a Archivos Base de Datos BCE if (ncmdshow!= SW_HIDE) ncmdshow = (ncmdshow!= SW_SHOWMINNOACTIVE)? SW_SHOWNORMAL : ncmdshow; TFrameWindow *frame = new TFrameWindow(0, GetName(), new ConsDlgClient(0), true); // // Assign ICON w/ this application. // frame->seticon(this, IDI_SDIAPPLICATION); SetMainWindow(frame); ////////////////////////////////////////////////////////// // ConsultaApp // =========== // Menu Help About consulta.exe command void ConsultaApp::CmHelpAbout () { int OwlMain (int, char* []) { try { ConsultaApp app; return app.run(); catch (xmsg& x) { ::MessageBox(0, x.why().c_str(), "Exception", MB_OK); return -1; CONSAPP.H: generado por AppExpert #if!defined( consapp_h) not already included. #define consapp_h // Sentry, use file only if it's /* Project consulta Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: consulta.exe Application consapp.h Domingo Eduardo Becker */ OVERVIEW ======== Class definition for ConsultaApp (TApplication). #include <owl\owlpch.h> #pragma hdrstop #include "consapp.rh" // Definition of all resources. 222

223 Consultas a Archivos Base de Datos BCE //{{TApplication = ConsultaApp class ConsultaApp : public TApplication { private: public: ConsultaApp (); virtual ~ConsultaApp (); //{{ConsultaAppVIRTUAL_BEGIN public: virtual void InitMainWindow(); //{{ConsultaAppVIRTUAL_END //{{ConsultaAppRSP_TBL_BEGIN protected: void CmHelpAbout (); //{{ConsultaAppRSP_TBL_END DECLARE_RESPONSE_TABLE(ConsultaApp); ; //{{ConsultaApp #endif // consapp_h sentry Fuentes del Administrador de Archivos Bases de Datos BCE La biblioteca de soporte es la BCE. Los módulos.cmm aquí listados son los del administrador de archivos bases de datos. Se los debe compilar una vez y sólo #incluir el archivo cabecera BD.H para el uso de la clase ArchBD. Para el encadenamiento se deberán incluir los.obj generados a partir de los.cmm. Los módulos.cmm contienen funciones cuya definición inline no era conveniente. Se enumeran de 01 a BD.H: cabecera con las declaraciones necesarias # ifndef BD_H # define BD_H // bd.h: declaración de clases para manejar archivos bases de dato // con formato BCE. # ifndef ARCHIVO_H # include <archivo.h> # endif # ifndef VECTOR_H # include <vector.h> # endif // Definiciones de tipos de archivos base de datos: 223

224 Consultas a Archivos Base de Datos BCE # define _ARCHBD 0 // Clase DescrCampoConstABD: una forma simple de describir un campo de un // registro en forma de constante. class DescrCampoConstABD { public: const char * Nombre; Ent16ns TamCampo; Ent8ns Tipo; ; # define tdascii0 0 # define tdent8 1 # define tdent8ns 2 # define tdent16 3 # define tdent16ns 4 # define tdent32 5 # define tdent32ns 6 # define tdfloat 7 # define tddouble 8 # define tdlongdouble 9 # define tdotro 10 extern const Ent16ns TamTipo[tdOtro]; // Ejemplo de declaración de la descripción de un registro: // DescrCampoConstABD EstrucReg[] = { // "Nombre", 30, tdascii0 // "DNI", sizeof(ent32ns), tdent32ns // internamente un DNI se maneja como Ent32ns // 0, 0, 0 // indica el fin de la descripción // ; // - El fin de la descripción siempre debe estar presente en una descripción // de registro, la misma se indica por la terna 0,0,0. // - Si TamCampo == 0 se debe asumir que el tamaño es sizeof(tipo). // No se permite TamCampo = 0 para tdascii0. // - Los Tipos de campo se especifican usando el prefijo 'td' y el // tipo de dato que usted desea. // - El Tipo de campo no es usado por la clase ArchBD, se incluye para // su uso durante la entrada de datos por pantalla. Si un campo es, por // ejemplo, de tipo tdfloat y el usuario lo maneja como tdent32 no hay ningún // problema (tdfloat y tdent32 son ambos de 4 bytes de largo). // - Una vista es una versión resumida o igual a la estructura del registro // del archivo base de datos. // Clase DescrCampo: descriptor de campo con cadenas manejadas por Ascii0. class DescrCampoABD { public: Ascii0 Nombre; Ent16ns TamCampo; Ent8ns Tipo; DescrCampoABD() { TamCampo = Tipo = 0; virtual ~DescrCampoABD() { ; ArchivoSO & operator << ( ArchivoSO & arch, const DescrCampoABD & dc ); ArchivoSO & operator >> ( ArchivoSO & arch, DescrCampoABD & dc ); 224

225 Consultas a Archivos Base de Datos BCE Ent16 EscrEstrReg( ArchivoSO & arch, const Vector<DescrCampoABD> & dc ); Ent16 LeerEstrReg( ArchivoSO & arch, Vector<DescrCampoABD> & dc ); // La siguiente función global da soporte a PonerCampo y SacarCampo // En Acceder: // Si Sentido == 0 se mueve así: EstrReg ===> BlqUsuario // Si Sentido!= 0 se mueve así: EstrReg <=== BlqUsuario Ent16 Acceder( const char * NomCampo, void * Reg, const DescrCampoABD * EstrReg, Ent16ns NumCampos, void * BlqUsuario, Ent8 Sentido ); Ent16ns MoverReg( void * RegDes, const DescrCampoABD * dregdes, Ent16ns ncregdes, const void * RegFue, const DescrCampoABD * dregfue, Ent16ns ncregfue ); const DescrCampoABD * BuscarDescr( const char * NomCampo, const DescrCampoABD * EstrReg, Ent16ns NumCampos, Ent16ns * Despl = 0 ); Ent16ns TamCampo( const char * NomCampo, const DescrCampoABD * EstrReg, Ent16ns NumCampos ); Ent8ns Tipo( const char * NomCampo, const DescrCampoABD * EstrReg, Ent16ns NumCampos ); Ent16ns TamReg( const DescrCampoABD * EstrReg, Ent16ns NumCampos ); // devuelve el número de bytes de la vista EstrReg de un archivo base de datos Ent16 EsVista( const DescrCampoABD * EstrReg, Ent16ns NumCamposReg, const DescrCampoABD * Vista, Ent16ns NumCamposVista ); // Clase ArchBD: significa archivo base de datos. Un archivo base de datos nos da // la independencia del código fuente de la estructura del registro del archivo. // Un archivo base de datos es también un archivo de tamaño de registro fijo. // En los sistemas administradores de bases de datos como Paradox o dbase se los // conoce como tablas. Un objeto ArchBD sería análogo a un cursor de esos sitemas. class ArchBD { protected: ArchivoSO Arch; Vector<DescrCampoABD> EstrReg; Ent16ns TamRegReal, TamReg_EstrReg, DesplInic; BlqMem Buffer; void Vaciar(); Ent16ns CalcTamReg(); Ent16ns FijarEstrRegInic( const DescrCampoConstABD * er ); Ent16 BorDes( Ent32 c, Ent16 bd ); virtual Ent16ns Control(); virtual Ent8 Tipo(); virtual Ent16ns LeerInfoCtrl(); virtual Ent16ns EscrInfoCtrl(); Ent16 Bloquear( Ent32 Desp, Ent32 Tam ); Ent16 Desbloquear( Ent32 Desp, Ent32 Tam ); Ent16 Escr( Ent8 ByteCtrlFijado ); public: ArchBD(); 225

226 Consultas a Archivos Base de Datos BCE virtual ~ArchBD(); Ent16 Abrir( const char * nombre, Ent8 e_s ); Ent16 Crear( const char * nombre, const DescrCampoConstABD * er ); Ent16 Crear( const char * nombre, Vector<DescrCampoABD> & er ); Ent16 Cerrar(); Ent32ns PA(); // Devuelve el valor del Puntero de Archivo (pos actual) Ent16 PosPA( Ent32ns NumReg ); // Posicionar el PA ArchBD & operator [] ( Ent32 pos ); Ent16 Comienzo(); // lleva el pa al comienzo del archivo Ent16 Final(); // lleva el pa al final del archivo Ent32ns Tam(); // devuelve el tamaño del archivo en bytes Ent16ns TamReg(); Ent32ns NumReg(); Ent16 Estado(); Ascii0 NomFis(); Ent16 VaciarBuffer() { return Arch.VaciarBuffer(); virtual Ent16 Leer(); // lee en Buffer. Luego se deben acceder a los campos del registro. virtual Ent16 Escr(); // escribe desde Buffer. Antes se deben acceder a los campos del registro. Ent16 Borrar( Ent32 CualReg ); Ent16 DesBorrar( Ent32 CualReg ); Ent16 Borrado(); // informa si el reg. leido está lógicamente borrado Ent16 BloquearReg( Ent32 Reg ); Ent16 DesbloquearReg( Ent32 Reg ); // Para PonerCampo y SacarCampo, NomCampo debe ser un campo de la // estructura del registro. Ent16 PonerCampo( const char * NomCampo, const void * blq_externo ); Ent16 SacarCampo( const char * NomCampo, void * blq_externo ) const; // PonerCampo no falla si TamCampo(NomCampo)!= TamTipo[Tipo]. // En particular, si Tipo es Ascii0 falla cuando! Cad.TamBlq(). Ent16 PonerCampo( const char * NomCampo, const Ascii0 & Cad ); Ent16 PonerCampo( const char * NomCampo, Ent8 n ); Ent16 PonerCampo( const char * NomCampo, Ent8ns n ); Ent16 PonerCampo( const char * NomCampo, Ent16 n ); Ent16 PonerCampo( const char * NomCampo, Ent16ns n ); Ent16 PonerCampo( const char * NomCampo, Ent32 n ); Ent16 PonerCampo( const char * NomCampo, Ent32ns n ); Ent16 PonerCampo( const char * NomCampo, float f ); Ent16 PonerCampo( const char * NomCampo, double d ); Ent16 PonerCampo( const char * NomCampo, long double ld ); Ent16 PonerCampoAutoConv( const char * NomCampo, const Ascii0 & Cad ); // SacarCampo falla si TamCampo(NomCampo)!= TamTipo[Tipo]. // En particular, si Tipo es Ascii0 sólo falla cuando no hay memoria. Ent16 SacarCampo( const char * NomCampo, Ascii0 & Cad ) const; Ent16 SacarCampo( const char * NomCampo, Ent8 & n ) const; Ent16 SacarCampo( const char * NomCampo, Ent8ns & n ) const; Ent16 SacarCampo( const char * NomCampo, Ent16 & n ) const; Ent16 SacarCampo( const char * NomCampo, Ent16ns & n ) const; Ent16 SacarCampo( const char * NomCampo, Ent32 & n ) const; Ent16 SacarCampo( const char * NomCampo, Ent32ns & n ) const; Ent16 SacarCampo( const char * NomCampo, float & f ) const; Ent16 SacarCampo( const char * NomCampo, double & d ) const; Ent16 SacarCampo( const char * NomCampo, long double & ld ) const; Ent16 SacarCampoAutoConv( const char * NomCampo, Ascii0 & Cad ); Ent16ns TamCampo( const char * NombreDelCampo ) const; Ent8ns Tipo( const char * NombreDelCampo ) const; 226

227 Consultas a Archivos Base de Datos BCE Ent16ns NumCampos() const; const DescrCampoABD * CampoNumero( Ent16ns i ) const; const char * CadInicial(); void CadInicial( const char * CadInic ); friend Ent16 Indexar( const char * NomArchBDFue, const DescrCampoABD * EstrRegInd, Ent16 (* fcmp)( const void * a, const void * b, const DescrCampoABD * EstrRegInd ), Ent8 AceptarRedundancia ); ; inline Ent16 ArchBD::Borrar( Ent32 cual ) { return BorDes(cual, 1); inline Ent16 ArchBD::DesBorrar( Ent32 cual ) { return BorDes(cual, 0); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, const void * blq_externo ) { return ::Acceder(NomCampo, Buffer.Blq(), EstrReg, EstrReg.Tam, (void *) blq_externo, 1); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, void * blq_externo ) const { return ::Acceder(NomCampo, Buffer.Blq(), EstrReg, EstrReg.Tam, blq_externo, 0); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, const Ascii0 & Cad ) { return (! Cad.TamBlq())? CodError = E_OPINV : PonerCampo(NomCampo, (const void *) Cad); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent8 n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent8ns n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent16 n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent16ns n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent32 n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, Ent32ns n ) { return PonerCampo(NomCampo, & n); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, float f ) { return PonerCampo(NomCampo, & f); 227

228 Consultas a Archivos Base de Datos BCE inline Ent16 ArchBD::PonerCampo( const char * NomCampo, double d ) { return PonerCampo(NomCampo, & d); inline Ent16 ArchBD::PonerCampo( const char * NomCampo, long double ld ) { return PonerCampo(NomCampo, & ld); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ascii0 & Cad ) const { register Ent16ns tc = TamCampo(NomCampo); if (! tc) return CodError = E_OPINV; if (! Cad.Reasignar(tc + 1)) return CodError; if (SacarCampo(NomCampo, (void *) Cad)) return CodError; register char * p = Cad; p[tc] = 0; // asegura terminación en 0 de la cadena Ascii. return CodError = E_SINERROR; inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent8 & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt8]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent8ns & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt8ns]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent16 & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt16]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent16ns & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt16ns]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent32 & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt32]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, Ent32ns & n ) const { if (TamCampo(NomCampo)!= TamTipo[tdEnt32ns]) return CodError = E_OPINV; return SacarCampo(NomCampo, & n); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, float & f ) const { if (TamCampo(NomCampo)!= TamTipo[tdfloat]) return CodError = E_OPINV; return SacarCampo(NomCampo, & f); inline Ent16 ArchBD::SacarCampo( const char * NomCampo, double & d ) const { if (TamCampo(NomCampo)!= TamTipo[tddouble]) return CodError = E_OPINV; return SacarCampo(NomCampo, & d); 228

229 Consultas a Archivos Base de Datos BCE inline Ent16 ArchBD::SacarCampo( const char * NomCampo, long double & ld ) const { if (TamCampo(NomCampo)!= TamTipo[tdlongdouble]) return CodError = E_OPINV; return SacarCampo(NomCampo, & ld); inline Ent16ns ArchBD::TamReg() { return TamReg_EstrReg; inline Ent16ns ArchBD::TamCampo( const char * NombreDelCampo ) const { return ::TamCampo(NombreDelCampo, EstrReg, EstrReg.Tam); inline Ent8ns ArchBD::Tipo( const char * NombreDelCampo ) const { return ::Tipo(NombreDelCampo, EstrReg, EstrReg.Tam); inline ArchBD & ArchBD::operator [] ( Ent32 pos ) { PosPA(pos); return *this; inline Ent16 ArchBD::Comienzo() { return PosPA(0); inline Ent16 ArchBD::Final() { return PosPA(NumReg()); inline Ent32ns ArchBD::Tam() { return Arch.Tam(); inline Ent16ns ArchBD::NumCampos() const { return EstrReg.Tam; inline const DescrCampoABD * ArchBD::CampoNumero( Ent16ns i ) const { return i < EstrReg.Tam? & EstrReg[i] : 0; inline Ent16 ArchBD::Bloquear( Ent32 desp, Ent32 tam ) { return Arch.Bloquear(desp, tam); inline Ent16 ArchBD::Desbloquear( Ent32 desp, Ent32 tam ) { return Arch.Desbloquear(desp, tam); inline Ent16 ArchBD::BloquearReg( Ent32 Reg ) { return Bloquear(Reg * TamRegReal + DesplInic, TamRegReal); inline Ent16 ArchBD::DesbloquearReg( Ent32 Reg ) { return Desbloquear(Reg * TamRegReal + DesplInic, TamRegReal); inline Ent16 ArchBD::Estado() { return Arch.Estado(); inline Ascii0 ArchBD::NomFis() { return Arch.NomFis(); 229

230 Consultas a Archivos Base de Datos BCE // Funciones globales... // Funciones Acceder() para const void * inline Ent16 Acceder( const char * NomCampo, const void * fuenodessi, void * desnofuesi, Ent16 Sentido, const DescrCampoABD * EstrReg ) { return (Sentido!= 0)? CodError = E_OPINV : Acceder(NomCampo, (void *) fuenodessi, desnofuesi, Sentido, EstrReg); # endif // # ifndef bd_h ABD01.CMM // abd01.cmm Jueves 11 de marzo de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" static char CadInicDef[] = "Archivo Base de Datos.\n\rBiblioteca de clases BCE."; ArchBD::ArchBD() { TamRegReal = TamReg_EstrReg = DesplInic = 0; ArchBD::~ArchBD() { Cerrar(); Ent16ns ArchBD::Control() { return 1; // 1 byte para verificar si se ha borrado lógicamente el registro Ent8 ArchBD::Tipo() { return _ARCHBD; void ArchBD::Vaciar() { Buffer.Vaciar(); EstrReg.Vaciar(); TamRegReal = TamReg_EstrReg = DesplInic = 0; Ent16ns ArchBD::CalcTamReg() { TamReg_EstrReg = ::TamReg(EstrReg, EstrReg.Tam); TamRegReal = TamReg_EstrReg + Control(); return TamReg_EstrReg; Ent16ns ArchBD::FijarEstrRegInic( const DescrCampoConstABD * er ) { if (er == 0) { CodError = E_ERNODEF; return 0; 230

231 Consultas a Archivos Base de Datos BCE // Contar cuantos campos hay: Ent16ns n; const DescrCampoConstABD * pdc = er; for (n = 0; pdc->tamcampo; ++n, ++pdc); // Pedir memoria: if (! EstrReg.Reasignar(n)) { CodError = E_NOHAYMEMORIA; return 0; // Asignar la estructura: for (n = 0, pdc = er; n < EstrReg.Tam; ++n, ++pdc) { EstrReg[n].Nombre = pdc->nombre; EstrReg[n].Tipo = pdc->tipo; EstrReg[n].TamCampo = pdc->tamcampo? pdc->tamcampo : TamTipo[pdc->Tipo]; CalcTamReg(); CodError = E_SINERROR; return n; Ent16ns ArchBD::EscrInfoCtrl() { Ent32 PAInic; Ascii0 cad; if (! EstrReg.Tam! TamReg_EstrReg) return CodError = E_ERNODEF, 0; PAInic = Arch.PA(); EscrCadCab(Arch, CadInicDef); // Para verificación Arch.ArchivoSO::operator << ( Tipo() ); // Para seguridad en acceso Arch << TamRegReal; // Ahora se escribe la descripción del registro EscrEstrReg(Arch, EstrReg); // comienza a escribir la descripción de los registros return Arch.PA() - PAInic; Ent16ns ArchBD::LeerInfoCtrl() { Ent32ns PAInic = Arch.PA(); if (CodError) return 0; SaltearCab(Arch); // Comprobar el tipo Ent8 t; Arch >> (t); if (CodError t!= Tipo()) return CodError = E_ARCHINV, 0; // Leer el tamaño del registro Ent16ns TamRegDisco; Arch >> TamRegDisco; // lee verdadero tamaño de registro // Leer descripción de los campos: LeerEstrReg(Arch, EstrReg); 231

232 Consultas a Archivos Base de Datos BCE if (! CalcTamReg() TamRegReal!= TamRegDisco) return CodError = E_ARCHINV, 0; return Arch.PA() - PAInic; Ent16 ArchBD::Abrir( const char * nom, Ent8 L_E ) { if (Arch.Estado()!= MA_C) return CodError = E_ABIERTO; if (L_E == MA_E L_E == MA_A) L_E = MA_L_E; if (ExisteArch(nom)) { if (! Arch.Abrir(nom, L_E)) DesplInic = LeerInfoCtrl(); else CodError = E_OPINV; if (! CodError) { if (! Buffer.Reasignar(TamRegReal)) { Cerrar(); CodError = E_NOHAYMEMORIA; return CodError; Ent16 ArchBD::Crear( const char * nombre, const DescrCampoConstABD * er ) { if (Arch.Estado()!= MA_C) return CodError = E_ABIERTO; if (! FijarEstrRegInic(er)) return CodError; if (Arch.AbrirExt(nombre, A_LEERESCR A_CREAR A_VACIAR A_BINARIO)) return CodError; DesplInic = EscrInfoCtrl(); if (! CodError) { if (! Buffer.Reasignar(TamRegReal)) { Cerrar(); CodError = E_NOHAYMEMORIA; return CodError; Ent16 ArchBD::Crear( const char * nombre, Vector<DescrCampoABD> & er ) { if (Arch.Estado()!= MA_C) return CodError = E_ABIERTO; EstrReg << er; if (! CalcTamReg()) return CodError = E_ERNODEF; if (Arch.AbrirExt(nombre, A_LEERESCR A_CREAR A_VACIAR A_BINARIO)) return CodError; DesplInic = EscrInfoCtrl(); if (! CodError) { if (! Buffer.Reasignar(TamRegReal)) { Cerrar(); CodError = E_NOHAYMEMORIA; 232

233 Consultas a Archivos Base de Datos BCE return CodError; Ent16 ArchBD::Cerrar() { Vaciar(); return Arch.Cerrar(); Ent16 ArchBD::Leer() { if (Arch.Estado() == MA_C) return CodError = E_CERRADO; Arch.Leer(Buffer.Blq(), TamRegReal); return CodError; Ent16 ArchBD::Escr( Ent8 ByteCtrlFijado ) { if (Arch.Estado() == MA_C) return CodError = E_CERRADO; if (! ByteCtrlFijado) { register char * preg = (char *) Buffer.Blq(); preg[tamreg_estrreg] = 0; // no está lógicamente borrado Arch.Escr(Buffer.Blq(), TamRegReal); return CodError; Ent16 ArchBD::Escr() { return Escr(0); // por defecto asume que no está logicamente borrado //Ent16 ArchBD::Rescr() { // if (Arch.Estado() == MA_C) return CodError = E_CERRADO; // return Arch.Rescr(Buffer.Blq(), TamRegReal); // Ent16 ArchBD::Borrado() { if (Arch.Estado() == MA_C) return CodError = E_CERRADO; register char * preg = (char *) Buffer.Blq(); return preg[tamreg_estrreg]; Ent16 ArchBD::BorDes( Ent32 cual, Ent16 bsidno ) { if (PosPA(cual)) return CodError; if (Leer()) return CodError; Ent8 borrado = Borrado(); if (bsidno && borrado! bsidno &&! borrado) return CodError = E_YAHECHO; register char * preg = (char *) Buffer.Blq(); preg[tamreg_estrreg] = bsidno; PosPA(cual); return Escr(1); Ent32ns ArchBD::PA() { Ent32ns pa = Arch.PA(); if (CodError) return 0; return (pa - DesplInic) / TamRegReal; Ent16 ArchBD::PosPA( Ent32ns nr ) { Ent32ns pa = nr * TamRegReal + DesplInic; return Arch.PosPA(pa); Ent32ns ArchBD::NumReg() { Ent32ns tamarch; tamarch = Tam(); 233

234 Consultas a Archivos Base de Datos BCE if (! tamarch! TamRegReal) return 0; return (tamarch - DesplInic) / TamRegReal; ABD02.CMM // abd02.cmm Miércoles 5 de junio de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" # include <string.h> Ent16 Acceder( const char * nomcampo, void * Reg, const DescrCampoABD * er, Ent16ns nc, void * BlqUsuario, Ent8 s ) { if (Reg == 0 BlqUsuario == 0) return CodError = E_OPINV; Ent16ns des; register const DescrCampoABD * pdc = BuscarDescr(nomcampo, er,nc, & des); if (pdc == 0) return CodError = des? E_CAMPOINEXIST : E_OPINV; if (! s) memmove(blqusuario, (char *) Reg + des, pdc->tamcampo); else memmove((char *) Reg + des, BlqUsuario, pdc->tamcampo); return CodError = E_SINERROR; ABD03.CMM // abd03.cmm Miércoles 5 de junio de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include <string.h> # include "bd.h" const DescrCampoABD * BuscarDescr( const char * nomcampo, const DescrCampoABD * er, Ent16ns nc, Ent16ns * Despl ) { if (Despl!= 0) *Despl = 0; // inicializar en 0 por las dudas if (! nc er == 0 nomcampo == 0) return 0; register const DescrCampoABD * paux; register Ent16ns i, des; for (paux = er, i = des = 0; i < nc; ++paux, ++i) if (! stricmp(nomcampo, paux->nombre)) break; // salir del for else des += paux->tamcampo; if (Despl!= 0) *Despl = des; // si pide Despl, devolver return i < nc? paux : 0; Ent16ns TamCampo( const char * NomCampo, const DescrCampoABD * EstrReg, Ent16ns NumCampos ) { 234

235 Consultas a Archivos Base de Datos BCE register const DescrCampoABD * p = BuscarDescr(NomCampo, EstrReg, NumCampos); return p!= 0? p->tamcampo : 0; Ent8ns Tipo( const char * NomCampo, const DescrCampoABD * EstrReg, Ent16ns NumCampos ) { register const DescrCampoABD * p = BuscarDescr(NomCampo, EstrReg, NumCampos); return p!= 0? p->tipo : 0; ABD04.CMM // abd04.cmm Viernes 7 de mayo de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" Ent16ns TamReg( const DescrCampoABD * EstrReg, Ent16ns NumCampos ) { if (! NumCampos EstrReg == 0) return 0; register const DescrCampoABD * er = EstrReg; register Ent16ns i, tamreg; for (tamreg = i = 0; i < NumCampos; ++er, ++i) tamreg += er->tamcampo; return tamreg; ABD05.CMM // abd05.cmm Jueves 11 de marzo de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" Ent16ns MoverReg( void * RegDes, const DescrCampoABD * dregdes, Ent16ns ncregdes, const void * RegFue, const DescrCampoABD * dregfue, Ent16ns ncregfue ) { if (RegDes == 0 dregdes == 0! ncregdes RegFue == 0 dregfue == 0! ncregfue ) return 0; register const DescrCampoABD * pdregdes; register char * pregdes = (char *) RegDes; register Ent16ns i, nc; for (i = nc = 0, pdregdes = dregdes; i < ncregdes; ++i, ++pdregdes) { if (! Acceder(pdRegDes->Nombre, (void *) RegFue, dregfue, ncregfue, pregdes, 0)) ++nc; // un campo más copiado pregdes += pdregdes->tamcampo; // avanza el puntero al campo destino siguiente ++pdregdes; return nc; 235

236 Consultas a Archivos Base de Datos BCE ABD06.CMM // abd06.cmm Viernes 7 de mayo de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" Ent16 EsVista( const DescrCampoABD * EstrReg, Ent16ns NumCamposReg, const DescrCampoABD * Vista, Ent16ns NumCamposVista ) { register const DescrCampoABD * pv = Vista; register Ent16ns i; for (i = 0; i < NumCamposVista; ++i, ++pv) if (pv->tamcampo!= TamCampo(pv->Nombre, EstrReg, NumCamposReg)) break; return! pv->tamcampo; ABD07.CMM // abd07.cmm Mar 09 Ene 96 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" Ent16 EscrEstrReg( ArchivoSO & arch, const Vector<DescrCampoABD> & dc ) { arch << dc.tam; for (Ent16ns i = 0; i < dc.tam; ++i) arch << dc[i]; return CodError; Ent16 LeerEstrReg( ArchivoSO & arch, Vector<DescrCampoABD> & dc ) { Ent16ns i,n; arch >> n; if (! dc.reasignar(n)) { CodError =! i? E_SINERROR : E_NOHAYMEMORIA; return CodError; for (i = 0; i < n; ++i) arch >> dc[i]; return CodError; ArchivoSO & operator << ( ArchivoSO & arch, const DescrCampoABD & dc ) { arch << dc.nombre << dc.tamcampo << dc.tipo; return arch; ArchivoSO & operator >> ( ArchivoSO & arch, DescrCampoABD & dc ) { arch >> dc.nombre >> dc.tamcampo >> dc.tipo; return arch; 236

237 Consultas a Archivos Base de Datos BCE ABD08.CMM // abd08.cmm Vie 19 Ene 96 // Copyright (c) 1996 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" const Ent16ns TamTipo[tdOtro] = { 0, sizeof(ent8), sizeof(ent8ns), sizeof(ent16), sizeof(ent16ns), sizeof(ent32), sizeof(ent32ns), sizeof(float), sizeof(double), sizeof(long double) ; ABD09.CMM // abd09.cmm Lun 22 Ene 96 // Copyright (c) 1996 by Domingo Eduardo Becker. // All rights reserved. # include "bd.h" Ent16 ArchBD::PonerCampoAutoConv( const char * NomCampo, const Ascii0 & Cad ) { Ent8ns TipoDelCampo = Tipo(NomCampo); Ent8 ent8; Ent8ns ent8ns; Ent16 ent16; Ent16ns ent16ns; Ent32 ent32; Ent32ns ent32ns; float f; double d; switch (TipoDelCampo) { case tdascii0: PonerCampo(NomCampo, Cad); break; case tdent8: ent8 = Cad; PonerCampo(NomCampo, ent8); break; case tdent8ns: ent8ns = Cad; PonerCampo(NomCampo, ent8ns); break; case tdent16: ent16 = Cad; PonerCampo(NomCampo, ent16); break; case tdent16ns: ent16ns = Cad; PonerCampo(NomCampo, ent16ns); break; 237

238 Consultas a Archivos Base de Datos BCE case tdent32: ent32 = Cad; PonerCampo(NomCampo, ent32); break; case tdent32ns: ent32ns = Cad; PonerCampo(NomCampo, ent32ns); break; case tdfloat: f = Cad; PonerCampo(NomCampo, f); break; case tddouble: d = Cad; PonerCampo(NomCampo, d); break; default: CodError = E_OPINV; return CodError; Ent16 ArchBD::SacarCampoAutoConv( const char * NomCampo, Ascii0 & Cad ) { Ent8ns TipoDelCampo = Tipo(NomCampo); Ent8 ent8; Ent8ns ent8ns; Ent16 ent16; Ent16ns ent16ns; Ent32 ent32; Ent32ns ent32ns; float f; double d; switch (TipoDelCampo) { case tdascii0: SacarCampo(NomCampo, Cad); break; case tdent8: if (! SacarCampo(NomCampo, ent8)) Cad = ent8; break; case tdent8ns: if (! SacarCampo(NomCampo, ent8ns)) Cad = ent8ns; break; case tdent16: if (! SacarCampo(NomCampo, ent16)) Cad = ent16; break; case tdent16ns: if (! SacarCampo(NomCampo, ent16ns)) Cad = ent16ns; break; case tdent32: if (! SacarCampo(NomCampo, ent32)) Cad = ent32; 238

239 Consultas a Archivos Base de Datos BCE break; case tdent32ns: if (! SacarCampo(NomCampo, ent32ns)) Cad = ent32ns; break; case tdfloat: if (! SacarCampo(NomCampo, f)) Cad = f; break; case tddouble: if (! SacarCampo(NomCampo, d)) Cad = d; break; default: CodError = E_OPINV; return CodError; 239

240 Un traductor sencillo para un lenguaje simple Capítulo 16: Un traductor sencillo para un lenguaje simple El traductor que se desarrollará como ejemplo en este capítulo, pretende mostrar el uso de los generadores SLR1 y AFD. No pretende ser un traductor profesional de un lenguaje sofisticado como los que hay actualmente en el mercado. No se pone énfasis en el desarrollo profesional de un traductor, sino en tratar de hacer entender cómo se debe proceder con las herramientas SLR1 y AFD en el desarrollo de un traductor. A los efectos de mostrar el correcto funcionamiento del código generado, se desarrolló un intérprete para ése código generado Objetivos y metas El objetivo es escribir un traductor de un lenguaje simple. Para poder probar el código generado, como no es código directamente ejecutable por la computadora, se escribirá también un intérprete para el código objeto. Las metas para llegar a esos dos objetivos son: 1. Describir el lenguaje. Se utilizará una gramática libre de contexto para describir la estructura del lenguaje. 2. Describir el lenguaje objeto, al que se traducirá cada oración del lenguaje fuente. 3. Describir el esquema de traducción, esto es, se escribirán las acciones semánticas que se encargarán de realizar la traducción. 4. Implementar el traductor. 5. Escribir el intérprete: diseñar e implementar el intérprete y su interfase. 6. Escribir la guía del usuario y manual de referencia del programador del lenguaje simple. 240

241 Un traductor sencillo para un lenguaje simple 16.2 Estructura del Lenguaje La estructura del lenguaje queda perfectamente definido por la siguiente gramática libre de contexto, dada en formato SLR1 v 2.x: Leng --> Decl Sents ; Decl --> 'Var' Idents ';' ; Idents --> MásIdents Ident ; MásIdents --> Idents ',' ; Sents ---> Sents SentPC SentPC ; // SentPC es sentencia y punto y coma SentPC --> Sent ';' ; Sent ---> Asignación Mientras Repetir ; Asignación --> Ident '=' Expresión ; Mientras --> 'Mientras' Expresión 'hacer' Sents 'Fin' ; Repetir ---> 'Repetir' Sents 'Hasta' Expresión ; Expresión --> ExprLogicaO ; ExprLogicaO -> ExprLogicaO ' ' ExprLogicaY ExprLogicaY ; ExprLogicaY -> ExprLogicaY '&&' ExprRelacional ExprRelacional ; ExprRelacional -> ExprRelacional '==' ExprArit ExprRelacional '!=' ExprArit ExprRelacional '<' ExprArit ExprRelacional '<=' ExprArit ExprRelacional '>' ExprArit ExprRelacional '>=' ExprArit ExprArit ; ExprArit ---> ExprArit '+' Producto ExprArit '-' Producto Producto ; Producto ---> Producto '*' Factor Producto '/' Factor Factor ; Factor -----> Ident Real '-' Factor '!' Factor '(' Expresión ')' ; Esta gramática será luego modificada para poder introducir acciones semánticas para realizar la traducción. Los símbolos terminales, cuya estructura no está especificada en la gramática precedente, serán tratados con AFDs generados a partir de las siguientes expresiones regulares (se usa formato AFD v 3.x): Ident // Exp reg para describir un identificador: # Letra [a-za-z_áéíóúññüü] # Digito [0-9] 241

242 Un traductor sencillo para un lenguaje simple Letra (Letra Digito)* Un identificador comienza con una Letra y continúa opcionalmente con una combinación finita de Letras y Digitos. Por lo menos tiene la longitud de un caracter. Real // Expresión regular para un número real en formato C++: # Díg [0-9] Díg * '.'? Díg + ( (e E) Díg + )? Los números reales definidos por la expresión regular precedente son similares a los de punto flotante del C++. El conjunto de números definido es: reales positivos con el 0. El signo '-' (menos) unario es tomado como operador. Para la escritura de una oración del lenguaje fuente se contempla la posibilidad de agregar secuencias de longitud variable de blancos entre símbolos terminales, así como también la posibilidad de insertar comentarios similares a los del C++. Blancos // Expresión regular para blancos: [ \n\r\f\t\v] + Comentarios // Exp reg para comentarios similares a éste: / /.* El programa siguiente es una oración ejemplo del lenguaje: // comentario Var a,b,c; a = ; b = a * 3 / 2; c = a + b - 4; // Bucles anidados: c = 10; a = 0; mientras c hacer b = 0; Repetir b = b + 1; a = a + 1; Hasta b == 20; c = c - 1; Fin; // a debe tener

243 Un traductor sencillo para un lenguaje simple 16.3 Descripción del Lenguaje Objeto Sea un intérprete de código que consta de los siguientes elementos: 1. Un puntero de instrucción. 2. Un registro. Tipo de dato del registro: real. 3. Una pila. 4. Una tabla de variables. El lenguaje objeto al que se traducirán los programas (oraciones) escritos en el lenguaje fuente, consta de instrucciones que modifican algunos de los elementos previamente descriptos. Las instrucciones pueden tener un operando. NoOp Las instrucciones y su correspondiente semántica son: No ejecuta ninguna operación. Sólo incrementa el puntero de instrucción. CrearVar Ident Agregar a Ident a la tabla de variables. La variable no debe estar previamente agregada. Luego, incrementar el puntero de instrucción. Asignar Ident Asignar a Ident el valor del registro del intérprete. La variable Ident debe ser previamente creada (debe existir en la tabla de variables). Luego incrementar el puntero de instrucción. MoverVar Ident Buscar Ident en la tabla de variables, leer su contenido y cargarlo en el registro del intérprete. La variable debe ser previamente creada por alguna instrucción que se ejecutó anteriormente. Luego incrementar el puntero de instrucción. 243

244 Un traductor sencillo para un lenguaje simple MoverCte Real Cargar Real en el registro de la máquina. Luego incrementar el puntero de instrucción. ApilarReg Apilar el contenido del registro de instrucción en la pila del intérprete. Luego incrementar el puntero de instrucción. O Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. A éstos números se los convierte a entero por truncado. Aplicar la operación O lógica a esos números y guardar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Y Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. A éstos números se los convierte a entero por truncado. Aplicar la operación Y lógica a esos números y guardar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Negar Convertir el contenido del registro del intérprete a entero y aplicarle la operación Negación lógica. El resultado se vuelve a almacenar en el registro del intérprete. Igual Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es igual a a esos números. Si son iguales, guardar 1 en el registro del intérprete, si son distintos, guardar 0. Luego incrementar el puntero de instrucción. 244

245 Un traductor sencillo para un lenguaje simple Distinto Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es distinto de a esos números. Si son iguales, guardar 0 en el registro del intérprete, si son distintos, guardar 1. Luego incrementar el puntero de instrucción. Menor Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es menor que a esos números. Si el operando izquierdo es menor que el segundo, guardar 1 en el registro del intérprete, sino guardar 0. Luego incrementar el puntero de instrucción. MenorOIgual Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es menor o igual a a esos números. Si el operando izquierdo es menor o igual al segundo, guardar 1 en el registro del intérprete, sino guardar 0. Luego incrementar el puntero de instrucción. Mayor Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es mayor que a esos números. Si el operando izquierdo es mayor que el segundo, guardar 1 en el registro del intérprete, sino guardar 0. Luego incrementar el puntero de instrucción. MayorOIgual Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación es mayor o igual a a esos números. Si el operando izquierdo es mayor o igual al segundo, guardar 1 245

246 Un traductor sencillo para un lenguaje simple en el registro del intérprete, sino guardar 0. Luego incrementar el puntero de instrucción. Sumar Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación Suma a esos números y almacenar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Restar Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación Resta a esos números y almacenar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Multiplicar Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación Multiplicación a esos números y almacenar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Dividir Desapilar 1 número de la pila del intérprete; éste número será el operando de la izquierda del operador. El operando de la derecha será el contenido del registro del intérprete. Aplicar la operación División a esos números y almacenar el resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. Menos El operando al que se aplicará esta operación se encuentra en el registro del intérprete. Aplicar la operación Menos a ése número y almacenar el 246

247 Un traductor sencillo para un lenguaje simple resultado en el registro del intérprete. Luego incrementar el puntero de instrucción. IrA Número_De_Instrucción Almacenar Número_De_Instrucción en el puntero de instrucción del intérprete. IrASiF Número_De_Instrucción Convertir a entero el contenido del registro del intérprete. Si el resultado es un número igual a 0, entonces almacenar Número_De_Instrucción en el puntero de instrucción del intérprete, sino incrementar el puntero de instrucción. IrASiV Número_De_Instrucción Convertir a entero el contenido del registro del intérprete. Si el resultado es un número distinto de 0, entonces almacenar Número_De_Instrucción en el puntero de instrucción del intérprete, sino incrementar el puntero de instrucción. El siguiente es un programa ejemplo, generado por este traductor a partir del programa en lenguaje fuente dado en la sección Las instrucciones se etiquetaron con números a efectos de que se observe hacia dónde saltan las instrucciones de salto. 1: CrearVar a 2: CrearVar b 3: CrearVar c 4: MoverCte 12 5: ApilarReg 6: MoverCte 4 7: Sumar 8: ApilarReg 9: MoverCte 5 10: Restar 11: Asignar a 12: MoverVar a 13: ApilarReg 14: MoverCte 3 15: Multiplicar 16: ApilarReg 17: MoverCte 2 18: Dividir 19: Asignar b 20: MoverVar a 21: ApilarReg 22: MoverVar b 23: Sumar 24: ApilarReg 25: MoverCte 4 247

248 Un traductor sencillo para un lenguaje simple 26: Restar 27: Asignar c 28: MoverCte 10 29: Asignar c 30: MoverCte 0 31: Asignar a 32: MoverVar c 33: IrASiF 56 34: MoverCte 0 35: Asignar b 36: MoverVar b 37: ApilarReg 38: MoverCte 1 39: Sumar 40: Asignar b 41: MoverVar a 42: ApilarReg 43: MoverCte 1 44: Sumar 45: Asignar a 46: MoverVar b 47: ApilarReg 48: MoverCte 20 49: Igual 50: IrASiF 35 51: MoverVar c 52: ApilarReg 53: MoverCte 1 54: Restar 55: Asignar c 56: IrA Descripción del Esquema de Traducción A la gramática dada en la sección 16.2 se la modificará a efectos de poder implementar las acciones semánticas que realizarán la traducción. Las modificaciones importantes se documentan en las secciones siguientes. A la gramática nueva resultante se la puede ver en el archivo LENG.GLC que se muestra al final de este capítulo, en la sección donde se listan los fuentes. La función AgregarSentCI agrega una instrucción de código objeto al programa que se está generado a partir de la traducción Generación del Código para las Operaciones Binarias Si la operación es binaria, de acuerdo a la forma general de regla siguiente: 248

249 Un traductor sencillo para un lenguaje simple Expr ---> Expr Oprd ExprMayorPrec ; la regla nueva será: Expr ---> Expr Oprd Apilar ExprMayorPrec ; Apilar ---> ; La segunda regla se agrega sólo una vez. Es una regla que deriva en lambda (no produce ningún efecto ni modifica el lenguaje). A este tipo de regla se las denomina marcadores. Las acciones semánticas serán: Expr ---> Expr Oprd Apilar ExprMayorPrec %{ AgregarSentCI(Oprd); % ; Apilar ---> %{ AgregarSentCI(ApilarReg); % ; Generación del Código para las Operaciones Unarias Si la operación era unaria, la acción semántica es: Expr ---> Oprd Expr %{ AgregarSentCI(Oprd); % ; Generación del Código para el "Repetir... hasta..." La regla original "Repetir... hasta..." se modificó, agregando un marcador. Las reglas semánticas quedaron así: Repetir ---> 'Repetir' Rotulo Sents 'Hasta' Expresión %{ // salto condicional a Rotulo AgregarSentCI(IrASiF, $2.CadTerm); % ; Rotulo --> %{ // asignamos a $$.CadTerm el número de instrucción actual Ent16ns NumInstAct = Programa.NumElem(); $$.CadTerm = NumInstAct; % ; El marcador asigna al campo CadTerm el número de instrucción actual (la que se está por generar) que se encuentra almacenado en la variable global NumInstAct. Luego, al reducir por la regla "Repetir... hasta" se utiliza ese valor para el salto condicional. 249

250 Un traductor sencillo para un lenguaje simple Generación del Código para el "Mientras... hacer..." La regla original "Mientras... hacer..." se modificó. Las acciones semánticas quedaron así: Mientras --> 'Mientras' Rotulo ExprSaltoCond 'hacer' Sents 'Fin' %{ // salto incondicional al primer rótulo AgregarSentCI(IrA, $2.CadTerm); // una vez agregada la sentencia, el rótulo del salto del no // terminal ExprSaltoCond debe referenciar a la posición actual: AsignarRotulo($3.CadTerm); // en CadTerm viene el nombre // del rótulo % ; ExprSaltoCond --> Expresión %{ // aquí hay un salto si es que el registro de la máquina es F GenerarRotulo($$.CadTerm); AgregarSentCI(IrASiF, $$.CadTerm); % ; La regla "Rotulo --> ;" fue dada en la sección anterior. Se utiliza esa acción semántica. Una vez evaluada la ExprSaltoCond se debe saltar a la instrucción que sigue después de la palabra clave Fin, entonces, luego de generar el código para Expresión se genera un rótulo que será resuelto al reducir por la regla del "Mientras... hacer..." (ver sentencia AsignarRotulo($3.CadTerm) y sentencia GenerarRotulo($$.CadTerm)) y se agrega el salto condicional. Al reducir por la regla "Mientras... hacer...", se genera un salto incondicional IrA a donde comienza la evaluación de la expresión condicional. Esa posición se encuentra en $2.CadTerm que corresponde a Rotulo Reglas Semánticas para la Recuperación de Operandos Actuales Los operandos actuales son alcanzados por las reglas siguientes, en donde se muestran también las acciones semánticas: Factor -----> Ident %{ // mover el valor de Ident al registro de la máquina if (BuscarVar($1.CadTerm)) AgregarSentCI(MoverVar, $1.CadTerm); % Real %{ // mover el valor del Real al registro dela máquina AgregarSentCI(MoverCte, $1.CadTerm); % Para el caso de que en el texto fuente se encuentre un identificador, antes de generar el código objeto se busca la variable en la tabla de variables 250

251 Un traductor sencillo para un lenguaje simple del traductor, para ver si la misma fue previamente declarada. Si es así, se genera el código MoverVar que extrae el valor de la tabla de variables del intérprete. Si era un número real, directamente se genera el código que coloca el real en el registro del intérprete Creación de las Variables La creación de las variables en tiempo de ejecución se realiza con la regla semántica siguiente: Idents --> MásIdents Ident %{ // nueva var declarada AgregarVar($2.CadTerm); AgregarSentCI(CrearVar, $2.CadTerm); % ; Se genera la sentencia CrearVar incondicionalmente. Además, se agrega el identificador que aparece en el texto fuente a la tabla de variables del traductor. Luego, esa tabla se usa para ver si hay variables previamente declaradas Resolución de Saltos hacia Adelante La resolución de saltos hacia adelante se realiza con la función global ResolverRotulos. La regla semántica es: Leng --> Decl Sents %{ // Resolver rótulos de saltos hacia adelante: ResolverRotulos(); % ; La función ResolverRotulos recorre el código generado en busca de saltos no resueltos. Para cada rótulo encontrado, se lo busca en la tabla de rótulos (la que hasta este momento ya tiene los rótulos resueltos) y se asigna la posición que le correspondió Implementación del Traductor El traductor está implementado por las acciones semánticas especificadas en LENG.GLC. 251

252 Un traductor sencillo para un lenguaje simple SLR1 v 2.x recibirá como entrada este archivo y generará el archivo LENG.CMM con el analizador sintáctico y el analizador lexicográfico. Los AFDs usados por el analizador lexicográficos son generados a partir de los respectivos archivos.er con las expresiones regulares Estructuras de Datos usadas Las estructuras de datos usadas por el traductor son las siguientes. 1. Código Objeto y Programa Objeto El código objeto es implementado por la clase CodInt que significa código intermedio (a partir de ese código se puede generar código de máquina). La declaración de esta clase se encuentra en INTERP.H, y las definiciones de las funciones miembro en INTERP.CMM. Los campos datos de la clase son: Codigo: contiene el código de operación. Operando: si la instrucción representada por este objeto tiene un operando, el mismo se encuentra en este campo. Se utiliza la clase Ascii0 para la conversión automática de tipo (recordar que hay operandos que son identificadores y otros que son números reales). Durante la traducción, el programa que se está generando se guarda en una lista simple encadenada de objetos CodInt. Para la lista simple encadenada se usa la clase template LSE de la biblioteca BCE. Durante la interpretación del código objeto, el programa se almacena en un vector de objetos CodInt. Se utiliza la clase template Vector de la biblioteca BCE. 2. Tabla de Variables La tabla de variables para el momento de la traducción es una lista simple encadenada de objetos Ascii0. Para el momento de la interpretación, la tabla de variables es una lista simple encadenada de objetos Variable. 252

253 Un traductor sencillo para un lenguaje simple La clase Variable tiene los siguientes campos dato: Ident: es el identificador usado en la declaración. Valor: es el valor actual de esta variable. La búsqueda en la tabla de variable es secuencial. 3. Rótulos y Tabla de Rótulos Los rótulos (para las referencias hacia adelante) se implementan con la clase DescrRotulo que tiene los siguientes campos dato: crotulo: es la cadena identificadora del rótulo. La cadena tiene la forma RXX donde XX es el número de éste rótulo. Se utilizan cadenas de esta forma puesto que al generar el programa se debe indicar de alguna manera que la instrucción de salto tiene una referencia no resuelta. Entonces, en el Operando de la instrucción de salto habrá una cadena que comienza con R, y esto significará que ese salto no está resuelto. nrotulo: es la posición a la apunta este rótulo. Al resolver el salto, se cambia crotulo por nrotulo. La tabla de rótulos sólo es usada durante la traducción, y se implementa con una lista simple encadenada de objetos DescrRotulo Descripción de las Funciones Globales Principales 1. BuscarVar Esta función busca una variable en la tabla de variables. Si la encuentra devuelve un valor distinto de 0 y sino AgregarVar Esta función agrega un identificador de variable a la lista de variables ya declaradas. Si ya fue agregada se devuelve 0. Sino un valor distinto de AgregarSentCI 253

254 Un traductor sencillo para un lenguaje simple Esta es una función polimórfica que agrega una sentencia de código objeto al programa que se está generando. 4. GenerarRotulo Esta función genera un rótulo (el campo crotulo de un objeto DescrRotulo). Lo agrega al final de la lista de rótulo e informa el nombre (crotulo) del rótulo. 5. AsignarRotulo A partir de la cadena del rótulo (crotulo) y un valor entero no negativo, se lo busca al rótulo en la lista de rótulos no resueltos y se asigna al campo nrotulo el valor entero no negativo recibido. Ese número será luego utilizado para resolver los saltos hacia adelante. 6. BuscarRotulo Dado un nombre de rótulo (crotulo), busca el descriptor del mismo en la tabla de rótulos y devuelve un puntero a ese descriptor. Si no lo encontró, devuelve ResolverRotulos Esta función recorre el programa en busca de instrucciones de salto en donde el Operando tenga una cadena de la forma RXX. Si encuentra una, busca un rótulo con ese nombre y reemplaza la cadena por el valor actual del rótulo. 8. GuardarPrograma Esta función guarda el programa que se encuentra en la lista simple encadenada que generó el traductor, en un archivo binario. Primero se guarda el número de instrucciones del programa y luego cada una de las instrucciones. Al cargar el programa en memoria (para la interpretación), se usará un vector, cuyo tamaño será el que se indica al comienzo del archivo. 254

255 Un traductor sencillo para un lenguaje simple El archivo binario con el código tendrá extensión.ci. Además, opcionalmente se genera un archivo de texto con extensión.txt que muestra las instrucciones en formato texto. 9. CargarPrograma Esta función carga el programa desde el archivo binario en un vector de objetos CodInt. 10.TraducirDeLengACodInt A partir de un nombre de archivo, esta función carga el texto que se encuentra en ese archivo, que se supone está en lenguaje fuente, lo traduce utilizando el analizador sintáctico y lexicográfico generados por SLR1 v 2.x, y guarda el programa que se genere en un archivo con el mismo nombre que el de entrada pero extensión.ci. En otro archivo con el mismo nombre pero con extensión.txt, se guarda el código generado pero en formato texto. Esta función se encuentra definida en el módulo TRADUCIR.CMM, y declarada en el módulo INTERP.H Implementación del Intérprete para el Código Generado Debido a que el intérprete se implementa con una clase llamada Interprete, es posible la coexistencia de varios intérpretes en un mismo programa Estructuras de Datos usadas Las estructuras siguientes fueron descriptas en : tablas de variables, variables, código objeto y programa. 1. Pila del Intérprete La pila del intérprete se implementa en una lista simple encadenada de números de punto flotante de doble precisión: double. Se utiliza la clase LSE de la biblioteca BCE. El primer elemento de la lista es el tope de pila. 2. Estructura de un Intérprete 255

256 Un traductor sencillo para un lenguaje simple Un intérprete se implementa con la clase Interprete, la que tiene los siguientes campos dato: Programa: es el programa a interpretar. Este intérprete se encarga de cargar el programa desde un archivo binario conteniendo las instrucciones. La función que actualmente realiza la carga es CargarPrograma. InstrAct: es el número de instrucción que se ejecutará en el paso siguiente. Inicialmente vale 0, que es el inicio del programa. Registro: es el registro del intérprete. Pila: es la pila del intérprete. La estructura se la explicó en el punto anterior. TablaDeVar: es la tabla de variables declaradas. Error: es el código de error, a saber: SinError, FinProg, FaltanOprnds, DirInval, VarNoDecl, VarDeclPrev, FaltaMem, PilaVacia, Div Descripción de las Funciones Principales Los códigos de errores devuelto por las funciones miembro de la clase Interprete son los que se enumeran en la descripción del campo Error de esa clase (ver sección anterior). Además de devolver el código de error, el campo Error retiene el código de error devuelto por la última función del intérprete que se ha ejecutado. Las definiciones de las funciones cortas las encontrará en el módulo INTERP.H, y las del resto están en INTERP.CMM. 1. Interprete::CargarPrograma Realiza la carga del Programa desde un archivo binario. Si no hay problemas, el intérprete apuntará a la primera instrucción del mismo. 2. Interprete::Ejecutar Ejecuta 1 instrucción, que es la apuntada por InstrAct. Devuelve el código del error, 0 si no hay error. 3. Interprete::TopeDePila 256

257 Un traductor sencillo para un lenguaje simple Devuelve 1 si hay elementos en la pila y en el parámetro pasado devuelve el contenido del tope de la pila. Devuelve 0 si la pila está vacía, y el parámetro no es usado. 4. Interprete::Vaciar Inicializa éste intérprete. Devuelve toda la memoria que eventualmente esté usando. 5. Interprete::InstrActual Devuelve 1 y la instrucción actual, apuntada por InstrAct, en formato cadena en la variable pasada como parámetro. Si no hay programa cargado, devuelve Interprete::CrearVar Agrega una variable a la tabla de variables. El contenido inicial de la variable es desconocido. Devuelve 0 si no hay problemas y distinto a 0 si ya fue creada o falta memoria. 7. Interprete::BuscarVar Busca una variable en la tabla de variables. Devuelve el puntero al descriptor de la misma o 0 si no fue encontrada. 8. Interprete::Apilar Apila un valor (double) en la pila de éste intérprete. Devuelve 0 si no hay problemas, distinto a 0 si falta memoria. 9. Interprete::Desapilar Desapila un valor (double) de la pila de éste intérprete. Devuelve 0 si no hay problemas, distinto a 0 si la pila estaba vacía. 257

258 Un traductor sencillo para un lenguaje simple 16.7 Diseño e Implementación de la Interfase en Pantalla del Intérprete El código para la interfase en pantalla fue generado por AppExpert, el generador de interfases de aplicaciones del Borland C La misma fue retocada con el Resource Workshop del Borland C++ y quedó visualmente así: Fig. 22 Interfase en pantalla del intérprete El campo Sentencia del código intermedio actual muestra la sentencia que se ejecutará en el paso siguiente. La Tabla de Variables muestra el contenido de la tabla de variables. Si hay muchas variables aparecerá una barra de desplazamiento que permitirá hacer scroll en la lista. El Registro de la Máquina muestra el contenido actual del registro del intérprete. Contenido del tope de la pila muestra el contenido del tope de la pila del intérprete. El botón Traza ejecuta la instrucción mostrada en el campo Sentencia del código intermedio actual. 258

259 Un traductor sencillo para un lenguaje simple El botón Animar realiza una ejecución animada de instrucciones. Se ejecuta una por una mostrando en cada paso el estado del intérprete. Se debe utilizar el botón Parar para detener la ejecución animada Guía del Usuario y Manual de Referencia del Programador del Lenguaje Fuente Introducción - Partes de un Programa El lenguaje sirve únicamente para el tratamiento de números reales en memoria. Un programa en este lenguaje consta de dos partes bien definidas, a saber: 1. Declaraciones de variables. 2. Sentencias. Las declaraciones de variables son opcionales y debe haber por lo menos una sentencia. No hay distinción entre mayúsculas y minúsculas, y se incluye la posibilidad de incluir comentarios dentro del código fuente. A continuación, las palabras claves se colocarán en mayúsculas Declaración de variables Las variables únicamente pueden ser de un tipo y se declaran de la siguiente forma: VAR var1 [, var2 [, var3 [... ] ] ] ; Se puede declarar una o más variables (lo que está entre corchetes es opcional). Los nombres de las variables deben ser identificadores que comienzan con una letra seguido de combinaciones de letras y dígitos. Se pueden usar letras con acentos. 259

260 Un traductor sencillo para un lenguaje simple Sentencias Las sentencias siempre finalizan con un ';' (punto y coma), y no hay problemas en cuanto a la cantidad de espacios que se coloquen entre símbolos. Existen tres tipos de sentencias: 1. Asignación: Una asignación tiene la forma general siguiente: IdentVar = Expresión ; donde IdentVar es el nombre de una variable y Expresión es una expresión válida del lenguaje. Ver sección "Expresiones válidas del Lenguaje" más adelante. 2. Sentencia iterativa Mientras... hacer... Esta sentencia tiene la forma general siguiente: MIENTRAS Expresión HACER Sentencias FIN ; donde Expresión es una expresión válida del lenguaje y Sentencias son una o más sentencias válidas del lenguaje. La semántica es: mientras Expresión se evalúe a 1, se ejecutan las sentencias. Esto es, si en la primera vez que se evalúa la expresión, la misma resulta 0 entonces el bloque de sentencias puede no ejecutarse. 3. Sentencia iterativa Repetir... hasta... Esta sentencia tiene la forma general siguiente: REPETIR Sentencias HASTA Expresión ; donde Expresión es una expresión válida del lenguaje y Sentencias son una o más sentencias válidas del lenguaje. 260

261 Un traductor sencillo para un lenguaje simple La semántica es: ejecutar las Sentencias, luego, si Expresión evalúa a 1, se vuelven a ejecutar las sentencias, sino se continúa con la sentencia siguiente. Esto es, el bloque de sentencias se ejecuta al menos una vez Expresiones válidas del Lenguaje Las expresiones válidas se describen mediante las siguientes reglas: 1. Si XXX es un identificador de variable o un número real, entonces XXX es una expresión válida. A este tipo de expresiones las denominaremos atómicas. 2. Si # es un operador unario y X es una expresión atómica, entonces # X es una expresión válida, la cual también es atómica. 3. Si # es un operador binario y X1 y X2 son expresiones válidas, entonces X1 # X2 es una expresión válida. 4. Si X es una expresión válida, ( X ) es una expresión válida, la cual también es atómica. 5. Son expresiones válidas las obtenidas por la aplicación reiterada de estas reglas. Los operadores unarios se evalúan de derecha a izquierda, y los operadores binarios de derecha a izquierda. La precedencia de los operadores es la siguiente: Precedencia Operador Observaciones 1. La más alta -! ( ) 2. Operadores multiplicativos 3. Operadores aditivos * / + - Menos unario Negador lógico Paréntesis Multiplicación División Suma Resta 261

262 Un traductor sencillo para un lenguaje simple Precedencia Operador Observaciones 4. Operadores Relacionales = =!= < <= > >= Igual a Distinto de Menor que Menor o igual que Mayor que Mayor o igual que 5. Operador lógico && Conjunción 6. Operador lógico Disyunción Fig. 23 Precedencia de operadores en las expresiones del lenguaje El resultado de una expresión donde el operador de menor precedencia sea el menos unario, un operador aditivo o un multiplicativo es un número real. El resultado de una expresión donde el operador de menor precedencia sea un operador lógico o un operador relacional es: 0 si es falso y distinto a 0 si es verdadero. Para verdadero se utilizará un número entero. El resultado de una expresión donde el operador de menor precedencia son los paréntesis, es del mismo tipo que la expresión que se encuentra dentro de ellos Módulos fuentes Independientes del Sistema Operativo LENG.GLC: gramática del lenguaje fuente // Gramática para un lenguaje simple donde se ilustra la implementación de // sentencias iterativas. // Versión 1 - Domingo E. Becker - 11 Dic 95 // Usar Leng como sufijo en SLR1 v 2.x. %{ # ifndef INTERP_H # include "interp.h" # endif # include <string.h> LSE<Ascii0> TablaDeVar; Ent8 BuscarVar( const Ascii0 & var ) { if (! var.long()) return 0; NodoLSE<Ascii0> * p = TablaDeVar.Entrada; while (p!= 0) { 262

263 Un traductor sencillo para un lenguaje simple if (! stricmp(var, p->dato)) break; p = p->psig; return p!= 0; Ent8 AgregarVar( const Ascii0 & var ) { if (! var.long()) return 0; // nombre nulo if (BuscarVar(var)) return 0; // ya está agregado return TablaDeVar.AgregFinal(var); LSE<CodInt> Programa; Ent8 AgregarSentCI( Ent16ns Codigo, Ascii0 & Operando ) { NodoLSE<CodInt> * p = new NodoLSE<CodInt>; if (p == 0) return 0; p->dato.codigo = Codigo; p->dato.operando = Operando; return Programa.AgregFinal(p); Ent8 AgregarSentCI( Ent16ns Codigo ) { Ascii0 Nulo; return AgregarSentCI(Codigo, Nulo); class DescrRotulo { public: Ent16ns nrotulo; Ascii0 crotulo; DescrRotulo() { nrotulo = 0; ~DescrRotulo() { ; LSE<DescrRotulo> ListaDeRotulos; // sin resolver. Ent8 GenerarRotulo( Ascii0 & Rotulo ) { Rotulo.printf("R%u", ListaDeRotulos.NumElem()); NodoLSE<DescrRotulo> * p = new NodoLSE<DescrRotulo>; if (p == 0) return 0; p->dato.crotulo = Rotulo; return ListaDeRotulos.AgregFinal(p); Ent8 AsignarRotulo( Ascii0 & Rotulo ) { NodoLSE<DescrRotulo> * p = ListaDeRotulos.Entrada; while (p!= 0) { if (! strcmp(p->dato.crotulo, Rotulo)) break; p = p->psig; if (p == 0) return 0; p->dato.nrotulo = Programa.NumElem(); return 1; NodoLSE<DescrRotulo> * BuscarRotulo( const Ascii0 & Rotulo ) { NodoLSE<DescrRotulo> * p = ListaDeRotulos.Entrada; while (p!= 0) { if (! strcmp(p->dato.crotulo, Rotulo)) break; p = p->psig; return p; 263

264 Un traductor sencillo para un lenguaje simple Ent16ns ResolverRotulos() { Ent16ns NumRotRes = 0; NodoLSE<CodInt> * p = Programa.Entrada; NodoLSE<DescrRotulo> * q; while (p!= 0) { if ((p->dato.codigo == CodInt::IrA p->dato.codigo == CodInt::IrASiF p->dato.codigo == CodInt::IrASiV) && * (char *) p->dato.operando[0] == 'R') { q = BuscarRotulo(p->Dato.Operando); if (q!= 0) { // si encuentra rótulo p->dato.operando = q->dato.nrotulo; // resolver referencia posterior ++NumRotRes; p = p->psig; return NumRotRes; Ent8 GuardarPrograma( const char * NomArchCI, const char * NomArchTXT ) { if (NomArchCI == 0) return 0; Ent16ns NumInst = Programa.NumElem(); if (! NumInst) return 0; ArchivoSO ArchCI, ArchTXT; Ent8 GenerarTXT = NomArchTXT!= 0; if (ArchCI.Abrir(NomArchCI, MA_E) GenerarTXT && ArchTXT.Abrir(NomArchTXT, MA_E, MD_TXT)) return 0; NodoLSE<CodInt> * p; Ent16ns NumLin; ArchCI << NumInst; for (p = Programa.Entrada, NumLin = 1; p!= 0; p = p->psig, ++NumLin) { ArchCI << p->dato; if (GenerarTXT) ArchTXT << NumLin << ": " << p->dato << "\n"; ArchCI.Cerrar(); if (GenerarTXT) ArchTXT.Cerrar(); return 1; % # TERMINAL Ident # TERMINAL Real # IGNORAR Blancos # IGNORAR Coment # MAYIGUALMIN Leng --> Decl Sents %{ // Resolver rótulos de saltos hacia adelante: ResolverRotulos(); % ; Decl --> 'Var' Idents ';' ; Idents --> MásIdents Ident %{ 264

265 Un traductor sencillo para un lenguaje simple // nueva var declarada AgregarVar($2.CadTerm); AgregarSentCI(CodInt::CrearVar, $2.CadTerm); % ; MásIdents --> Idents ',' ; Sents ---> Sents SentPC SentPC ; // SentPC es sentencia y punto y coma SentPC --> Sent ';' ; Sent ---> Asignación Mientras Repetir ; Asignación --> Ident '=' Expresión %{ // asignación a Ident. Ver si existe, si es así, generar código if (BuscarVar($1.CadTerm)) AgregarSentCI(CodInt::Asignar, $1.CadTerm); % ; Rotulo --> %{ // asignamos a $$.CadTerm el número de instrucción actual (a generar). Ent16ns NumInstAct = Programa.NumElem(); $$.CadTerm = NumInstAct; % ; Mientras --> 'Mientras' Rotulo ExprSaltoCond 'hacer' Sents 'Fin' %{ // salto incondicional al primer rótulo AgregarSentCI(CodInt::IrA, $2.CadTerm); // una vez agregada la sentencia, el rótulo del salto del no terminal // ExprSaltoCond debe referenciar a la posición actual: AsignarRotulo($3.CadTerm); // en CadTerm viene el nombre del rótulo % ; ExprSaltoCond --> Expresión %{ // aquí hay un salto si es que el registro de la máquina es falso GenerarRotulo($$.CadTerm); AgregarSentCI(CodInt::IrASiF, $$.CadTerm); % ; Repetir ---> 'Repetir' Rotulo Sents 'Hasta' Expresión %{ // salto condicional a Rotulo AgregarSentCI(CodInt::IrASiF, $2.CadTerm); % ; Expresión --> ExprLogicaO ; ExprLogicaO -> ExprLogicaO ' ' Apilar ExprLogicaY %{ // operar AgregarSentCI(CodInt::O); % ExprLogicaY ; ExprLogicaY -> ExprLogicaY '&&' Apilar ExprRelacional %{ // operar AgregarSentCI(CodInt::Y); % ExprRelacional ; ExprRelacional -> ExprRelacional '==' Apilar ExprArit %{ // operar AgregarSentCI(CodInt::Igual); % ExprRelacional '!=' Apilar ExprArit %{ // operar AgregarSentCI(CodInt::Distinto); % ExprRelacional '<' Apilar ExprArit %{ 265

266 Un traductor sencillo para un lenguaje simple // operar AgregarSentCI(CodInt::Menor); % ExprRelacional '<=' Apilar ExprArit %{ // operar AgregarSentCI(CodInt::MenorOIgual); % ExprRelacional '>' Apilar ExprArit %{ // operar AgregarSentCI(CodInt::Mayor); % ExprRelacional '>=' Apilar ExprArit %{ // operar AgregarSentCI(CodInt::MayorOIgual); % ExprArit ; ExprArit ---> ExprArit '+' Apilar Producto %{ // operar AgregarSentCI(CodInt::Sumar); % ExprArit '-' Apilar Producto %{ // operar AgregarSentCI(CodInt::Restar); % Producto ; Producto ---> Producto '*' Apilar Factor %{ // operar AgregarSentCI(CodInt::Multiplicar); % Producto '/' Apilar Factor %{ // operar AgregarSentCI(CodInt::Dividir); % Factor ; Apilar --> %{ // Se supone que está por buscar la parte derecha de un operador binario. // Apilar el valor del registro de la máquina. AgregarSentCI(CodInt::ApilarReg); % ; Factor -----> Ident %{ // mover el valor de Ident al registro de la máquina if (BuscarVar($1.CadTerm)) AgregarSentCI(CodInt::MoverVar, $1.CadTerm); % Real %{ // mover el valor del Real al registro dela máquina AgregarSentCI(CodInt::MoverCte, $1.CadTerm); % '-' Factor %{ // operar AgregarSentCI(CodInt::Menos); % '!' Factor %{ // operar AgregarSentCI(CodInt::Negar); % '(' Expresión ')' ; 266

267 Un traductor sencillo para un lenguaje simple IDENT.ER: expresión regular para un identificador // Exp reg para describir un identificador: # Letra [a-za-z_áéíóúññüü] # Digito [0-9] Letra (Letra Digito)* REAL.ER: expresión regular para un número real // Expresión regular para un número real en formato C++: # Díg [0-9] Díg * '.'? Díg + ( (e E) Díg + )? BLANCOS.ER: expresión regular para los blancos // Expresión regular para blancos: [ \n\r\f\t\v] COMENT.ER: expresión regular para los comentarios // Exp reg para comentarios similares a éste: / /.* INTERP.H: archivo cabecera con declaraciones # ifndef INTERP_H # define INTERP_H // interp.h: declaración de clases para implementar un intérprete de código // intermedio. # ifndef LSE_H # include "lse.h" # endif # ifndef VECTOR_H # include "vector.h" # endif # ifndef ARCHIVO_H # include <archivo.h> # endif // Clase CodInt: el código de la máquina hipotética (el intérprete) // consta de dos partes: el código de operación y el operando. // El código de operación se codifica con el enum dentro de la clase. // El operando es siempre una cadena de texto que contendrá un número real o // un identificador de variable a crear. class CodInt { public: Ent16ns Codigo; Ascii0 Operando; 267

268 Un traductor sencillo para un lenguaje simple enum { NoOp, CrearVar, Asignar, MoverVar, MoverCte, ApilarReg, O, Y, Negar, Igual, Distinto, Menor, MenorOIgual, Mayor, MayorOIgual, Sumar, Restar, Multiplicar, Dividir, Menos, IrA, IrASiF, IrASiV ; CodInt() { Codigo = NoOp; ~CodInt() { static const char * CadCodigo( Ent16ns Codigo ); const char * CadCodigo() const { return CadCodigo(Codigo); void Cadena( Ascii0 & Cad ) const; ; Archivo & operator << ( Archivo & arch, const CodInt & ci ); ArchivoSO & operator << ( ArchivoSO & arch, const CodInt & ci ); ArchivoSO & operator >> ( ArchivoSO & arch, CodInt & ci ); Ent16 TraducirDeLengACodInt( const char * NomArch, Ascii0 & CadPosErr ); Ent16 CargarPrograma( const char * NomArch, Vector<CodInt> & Programa ); class Variable { public: Ascii0 Ident; double Valor; Variable() { ~Variable() { ; class Interprete { public: Vector<CodInt> Programa; unsigned InstrAct; double Registro; LSE<double> Pila; // Pila implementada en lista simple encadenada LSE<Variable> TablaDeVar; Ent16ns Error; Interprete() { Registro = InstrAct = Error = 0; ~Interprete() { Vaciar(); Ent16 CargarPrograma( const char * NomArch ); Ent8 Ejecutar(); // Ejecuta 1 instrucción de código intermedio, solamente. Ent8 TopeDePila( double & valor ) const; // devuelve el valor del tope de la pila. void Vaciar(); // devuelve la mem dinámica usada por éste intérprete Ent8 InstrActual( Ascii0 & cad ); enum { SinError = 0, FinProg, FaltanOprnds, DirInval, VarNoDecl, VarDeclPrev, FaltaMem, PilaVacia, Div0 ; Ent8 CrearVar( const Ascii0 & Ident ); Variable * BuscarVar( const Ascii0 & Ident ); Ent8 Apilar( double Valor ); Ent8 Desapilar( double & Valor ); ; inline Ent16 Interprete::CargarPrograma( const char * NomArch ) { Vaciar(); return ::CargarPrograma(NomArch, Programa); 268

269 Un traductor sencillo para un lenguaje simple inline void Interprete::Vaciar() { Programa.Vaciar(); Registro = InstrAct = Error = 0; Pila.Vaciar(); TablaDeVar.Vaciar(); inline Ent8 Interprete::TopeDePila( double & valor ) const { if (Pila.Entrada!= 0) { valor = Pila.Entrada->Dato; return 1; return 0; inline Ent8 Interprete::InstrActual( Ascii0 & cad ) { if (InstrAct < Programa.Tam) Programa[InstrAct].Cadena(cad); return InstrAct < Programa.Tam; # endif // # ifndef INTERP_H TRADUCIR.CMM: función TraducirDeLengACodInt // traducir.cmm Mar 12 Dic 95 // Domingo E. Becker # include "leng.cmm" // generado por SLR1 v 2.x Ent16 TraducirDeLengACodInt( const char * NomArch, Ascii0 & CadPosErr ) { Ascii0 Fuente; ArchivoSO ArchFuente; if (ArchFuente.Abrir(NomArch, MA_L)) return 0; if (! Fuente.Reasignar(ArchFuente.Tam() + 1)) return 0; Ent16ns NumBytes = ArchFuente.Leer(Fuente, ArchFuente.Tam()); * Fuente[NumBytes] = 0; ArchFuente.Cerrar(); TablaDeVar.Vaciar(); Programa.Vaciar(); ListaDeRotulos.Vaciar(); Ascii0 NomArchCI, NomArchTXT; NomArchCI = NomExt(NomArch, ".ci"); NomArchTXT = NomExt(NomArch, ".txt"); alleng.reiniciar(fuente); Ent8 r = asleng.analizar(alleng, CadPosErr); if (r) r = GuardarPrograma(NomArchCI, NomArchTXT); TablaDeVar.Vaciar(); Programa.Vaciar(); ListaDeRotulos.Vaciar(); return r; 269

270 Un traductor sencillo para un lenguaje simple INTERP.CMM: funciones del intérprete // interp.cmm # include "interp.h" const char * CodInt::CadCodigo( Ent16ns Codigo ) { const char * p; switch (Codigo) { case NoOp: p = "NoOp"; break; case CrearVar: p = "CrearVar"; break; case Asignar: p = "Asignar"; break; case MoverVar: p = "MoverVar"; break; case MoverCte: p = "MoverCte"; break; case ApilarReg: p = "ApilarReg"; break; case O: p = "O"; break; case Y: p = "Y"; break; case Negar: p = "Negar"; break; case Igual: p = "Igual"; break; case Distinto: p = "Distinto"; break; case Menor: p = "Menor"; break; case MenorOIgual: p = "MenorOIgual"; break; case Mayor: p = "Mayor"; break; 270

271 Un traductor sencillo para un lenguaje simple case MayorOIgual: p = "MayorOIgual"; break; case Sumar: p = "Sumar"; break; case Restar: p = "Restar"; break; case Multiplicar: p = "Multiplicar"; break; case Dividir: p = "Dividir"; break; case Menos: p = "Menos"; break; case IrA: p = "IrA"; break; case IrASiF: p = "IrASiF"; break; case IrASiV: p = "IrASiV"; break; default: p = "Desconocido"; return p; void CodInt::Cadena( Ascii0 & Cad ) const { switch (Codigo) { case CrearVar: case Asignar: case MoverVar: case MoverCte: case IrA: case IrASiF: case IrASiV: Cad.printf("%s %s", CadCodigo(), (const char *) Operando); break; default: Cad = CadCodigo(); Archivo & operator << ( Archivo & arch, const CodInt & ci ) { Ascii0 cad; ci.cadena(cad); arch << (const char *) cad; return arch; 271

272 Un traductor sencillo para un lenguaje simple ArchivoSO & operator << ( ArchivoSO & arch, const CodInt & ci ) { if (! arch.txtbin()) operator << ((Archivo &) arch, ci); else arch << ci.codigo << ci.operando; return arch; ArchivoSO & operator >> ( ArchivoSO & arch, CodInt & ci ) { arch >> ci.codigo >> ci.operando; return arch; Ent16 CargarPrograma( const char * NomArch, Vector<CodInt> & Programa ) { Programa.Vaciar(); if (NomArch == 0) return 0; ArchivoSO arch; if (arch.abrir(nomarch, MA_L)) return 0; Ent16ns NumInst, i; arch >> NumInst; if (! NumInst! Programa.Reasignar(NumInst)) return 0; for (i = 0; i < NumInst; ++i) arch >> Programa[i]; arch.cerrar(); return 1; Ent8 Interprete::Ejecutar() { if (InstrAct >= Programa.Tam) return Error = FinProg; Variable * pvar; double OperandoIzq; Ent16ns CodOp = Programa[InstrAct].Codigo; Error = SinError; switch (CodOp) { case CodInt::CrearVar: CrearVar(Programa[InstrAct].Operando); break; case CodInt::Asignar: pvar = BuscarVar(Programa[InstrAct].Operando); if (pvar!= 0) pvar->valor = Registro; break; case CodInt::MoverVar: pvar = BuscarVar(Programa[InstrAct].Operando); if (pvar!= 0) Registro = pvar->valor; break; case CodInt::MoverCte: Registro = Programa[InstrAct].Operando; break; case CodInt::ApilarReg: Apilar(Registro); break; case CodInt::O: if (! Desapilar(OperandoIzq)) 272

273 Un traductor sencillo para un lenguaje simple Registro = (int) OperandoIzq (int) Registro; break; case CodInt::Y: if (! Desapilar(OperandoIzq)) Registro = (int) OperandoIzq && (int) Registro; break; case CodInt::Negar: Registro =! (int) Registro; break; case CodInt::Igual: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq == Registro; break; case CodInt::Distinto: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq!= Registro; break; case CodInt::Menor: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq < Registro; break; case CodInt::MenorOIgual: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq <= Registro; break; case CodInt::Mayor: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq > Registro; break; case CodInt::MayorOIgual: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq >= Registro; break; case CodInt::Sumar: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq + Registro; break; case CodInt::Restar: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq - Registro; break; case CodInt::Multiplicar: if (! Desapilar(OperandoIzq)) Registro = OperandoIzq * Registro; break; case CodInt::Dividir: if (! Desapilar(OperandoIzq)) if (Registro == 0) Error = Div0; else Registro = OperandoIzq / Registro; break; case CodInt::Menos: Registro = - Registro; break; 273

274 Un traductor sencillo para un lenguaje simple case CodInt::IrASiV: if (! (int) Registro) { InstrAct++; if (InstrAct >= Programa.Tam) Error = FinProg; break; case CodInt::IrA: InstrAct = (Ent16ns) Programa[InstrAct].Operando; if (InstrAct >= Programa.Tam) Error = FinProg; break; case CodInt::IrASiF: if ((int) Registro) { InstrAct++; if (InstrAct >= Programa.Tam) Error = FinProg; break; InstrAct = (Ent16ns) Programa[InstrAct].Operando; if (InstrAct >= Programa.Tam) Error = FinProg; break; if (Error == SinError && CodOp!= CodInt::IrASiF && CodOp!= CodInt::IrASiV && CodOp!= CodInt::IrA) { InstrAct++; if (InstrAct >= Programa.Tam) Error = FinProg; return Error; Ent8 Interprete::CrearVar( const Ascii0 & Ident ) { if (BuscarVar(Ident) == 0) { NodoLSE<Variable> * p = new NodoLSE<Variable>; if (p == 0) Error = FaltaMem; else { p->dato.ident = Ident; TablaDeVar.AgregFinal(p); Error = SinError; else Error = VarDeclPrev; return Error; Variable * Interprete::BuscarVar( const Ascii0 & Ident ) { NodoLSE<Variable> * p = TablaDeVar.Entrada; while (p!= 0) { if (! stricmp(p->dato.ident, Ident)) break; // encontrado p = p->psig; // sino seguir buscando Error = p == 0? VarNoDecl : SinError; return p!= 0? & p->dato : 0; Ent8 Interprete::Apilar( double Valor ) { return Error = Pila.AgregComienzo(Valor)? SinError : FaltaMem; 274

275 Un traductor sencillo para un lenguaje simple Ent8 Interprete::Desapilar( double & Valor ) { NodoLSE<double> * p = Pila.Entrada; if (p!= 0) { Valor = p->dato; Error = SinError; else Error = PilaVacia; Pila.Eliminar(p); return Error; Módulos fuentes dependientes del Sistema Operativo Se mostrarán solamente los módulos importantes. Los demás son fácilmente generables utilizando el AppExpert del Borland C DLGINTER.H: generado por AppExpert y modificado por el autor #if!defined( dlginter_h) not already included. #define dlginter_h // Sentry, use file only if it's /* Project leng Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: leng.exe Application dlginter.h Domingo Eduardo Becker. */ OVERVIEW ======== Class definition for DlgInterp (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include <owl\listbox.h> #include "lengapp.rh" // Definition of all resources. # include "interp.h" //{{TDialog = DlgInterp struct DlgInterpXfer { //{{DlgInterpXFER_DATA TListBoxData ListaVars; //{{DlgInterpXFER_DATA_END ; class DlgInterp : public TDialog { public: DlgInterp (TWindow *parent, TResId resid = IDD_INTERPRETE, TModule *module = 0); virtual ~DlgInterp (); 275

276 Un traductor sencillo para un lenguaje simple Interprete Interp; Ent8 Animando; void MostrarEstado(); void CargarListaVars(); //{{DlgInterpVIRTUAL_BEGIN public: virtual void SetupWindow (); //{{DlgInterpVIRTUAL_END //{{DlgInterpRSP_TBL_BEGIN protected: void Traza (); void EjecucionAnimada (); void PararAnimacion (); //{{DlgInterpRSP_TBL_END DECLARE_RESPONSE_TABLE(DlgInterp); //{{DlgInterpXFER_DEF protected: TListBox *ListaVars; //{{DlgInterpXFER_DEF_END ; //{{DlgInterp #endif // dlginter_h sentry DLGINTER.CMM: generado por AppExpert y modificado por el autor /* Project leng Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: leng.exe Application dlginter.cmm Domingo Eduardo Becker. */ OVERVIEW ======== Source file for implementation of DlgInterp (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "lengapp.h" #include "dlginter.h" // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(DlgInterp, TDialog) //{{DlgInterpRSP_TBL_BEGIN EV_BN_CLICKED(IDOK, Traza), EV_BN_CLICKED(IDC_ANIMAR, EjecucionAnimada), EV_BN_CLICKED(IDC_PARAR, PararAnimacion), //{{DlgInterpRSP_TBL_END 276

277 Un traductor sencillo para un lenguaje simple END_RESPONSE_TABLE; //{{DlgInterp Implementation ////////////////////////////////////////////////////////// // DlgInterp // ========== // Construction/Destruction handling. static DlgInterpXfer DlgInterpData; DlgInterp::DlgInterp (TWindow *parent, TResId resid, TModule *module) : TDialog(parent, resid, module) { //{{DlgInterpXFER_USE ListaVars = new TListBox(this, IDC_VARIABLES); SetTransferBuffer(&DlgInterpData); //{{DlgInterpXFER_USE_END // INSERT>> Your constructor code here. Animando = 0; DlgInterp::~DlgInterp () { Destroy(); // INSERT>> Your destructor code here. void DlgInterp::SetupWindow () { TDialog::SetupWindow(); // INSERT>> Your code here. MostrarEstado(); void DlgInterp::MostrarEstado() { Ascii0 cad; double real; if (Interp.Programa.Tam){ cad = Interp.Registro; SetDlgItemText(IDC_REGISTRO, cad); if (Interp.TopeDePila(real)) { cad = real; SetDlgItemText(IDC_PILA, cad); else SetDlgItemText(IDC_PILA, "Vacía"); Interp.InstrActual(cad); SetDlgItemText(IDC_CODIGO, cad); CargarListaVars(); else { SetDlgItemText(IDC_REGISTRO, "0"); SetDlgItemText(IDC_CODIGO, "No hay código"); SetDlgItemText(IDC_PILA, "Vacía"); ListaVars->ClearList(); void DlgInterp::Traza () 277

278 Un traductor sencillo para un lenguaje simple { // INSERT>> Your code here. Ent8 r = Interp.Ejecutar(); MostrarEstado(); if (r!= Interprete::SinError) { if (r == Interprete::FinProg) MessageBox("El programa ha finalizado.", "Observación", MB_OK MB_ICONINFORMATION); else MessageBox("Error de ejecución", "ERROR", MB_OK MB_ICONHAND); Animando = 0; // por las dudas CmOk(); void DlgInterp::CargarListaVars() { ListaVars->ClearList(); Ascii0 cad; NodoLSE<Variable> * p; for (p = Interp.TablaDeVar.Entrada; p!= 0; p = p->psig) { cad.printf("%s: %g", (char *) p->dato.ident, p->dato.valor); ListaVars->AddString(cad); void DlgInterp::EjecucionAnimada () { // INSERT>> Your code here. Animando = 1; while (Animando) { Traza(); GetApplication()->PumpWaitingMessages(); // pseudoparalelismo de Windows void DlgInterp::PararAnimacion () { // INSERT>> Your code here. Animando = 0; La función DlgInterp::MostrarEstado muestra el estado del intérprete en la pantalla. La función DlgInterp::Traza pide al objeto intérprete que ejecute una instrucción y muestra el estado en pantalla luego de ejecutarla. La función DlgInterp::EjecucionAnimada realiza la ejecución animada, mostrando el estado en pantalla para cada instrucción que se ejecuta. Esta función puede ser interrumpida por el usuario. Termina su ejecución cuando el usuario así lo pide o cuando no hay más instrucciones para ejecutar. 278

279 Un traductor sencillo para un lenguaje simple La función DlgInterp::PararAnimacion es automáticamente llamada por el sistema operativo cuando el usuario presiona el botón Parar de la ventana del intérprete. 279

280 Parte 5. Anexos, Codigos Fuentes y Conclusión. 280

281 Introducción a las Gramáticas Generativas Capítulo 17: Introducción a las Gramáticas Generativas Los conceptos aquí presentados se manejaban antes de que Chomsky presentara su trabajo sobre lingüística computacional. Lo que Chomsky hizo fue encontrar la explicación matemática del mecanismo del lenguaje, partiendo de estudios de lingüística que se habían realizado hasta ese momento. Hasta el día de la edición de este trabajo, no se presentaron objeciones valederas a los estudios realizados por Chomsky, aunque, a primera vista, pareciera ser que la Teoría de los Lenguajes Formales va por otro camino distinto al que tomó Chomsky. Este capítulo está basado principalmente en Nique [1985]. Christian Nique es de nacionalidad francesa. Aunque en Francia había una Escuela que seguía caminos distintos en la investigación a los que seguía la Escuela Americana, donde estaba Chomsky, fueron los estudios de éste último los que predominaron e hicieron historia, y los lingüistas franceses y alemanes los que más lo usaron en un principio. Al finalizar la lectura de este capítulo, el lector estará en mejores condiciones de iniciar el estudio de la Teoría de los Lenguajes Formales, tratada en el capítulo 2 de este trabajo Un Mecanismo Finito para Generar un Número Infinito de Oraciones No sólo el hablante posee implícitamente el mecanismo del lenguaje, sino que además es "en todo momento capaz de emitir espontáneamente, o de percibir o comprender, un número indefinido de oraciones que, en su mayor parte, no ha pronunciado nunca ni oído antes". Esto equivale a decir que, cuando se habla, se hace algo más que reproducir esquemas de oraciones que se escuchó antes, sino que también se crean oraciones nuevas. Entonces el mecanismo del lenguaje es un mecanismo creador. La repetición de oraciones es excepcional, y responde a características y costumbres de algunos hablantes, por lo que (la repetición) no tiene ninguna relación con la utilización del lenguaje. No se habla repitiendo lo que se ha oído, sino mediante un acto de creación cada vez, y esta característica es entonces la principal en el uso del lenguaje. 281

282 Introducción a las Gramáticas Generativas Existen dos clases de creatividad que es preciso no confundir. La primera, que se llama creatividad por cambio de reglas, consiste fundamentalmente en cambiar ciertas partes del mecanismo lenguaje. Es lo que hace variar la pronunciación de ciertas palabras, que crea otras nuevas, que acaba por admitir como gramatical lo que no era en un principio, nada más que una desviación en relación a las reglas. El segundo tipo es completamente diferente. Se le llama creatividad gobernada por las reglas. Es la que permite al hablante, por aplicación de las reglas de la gramática, crear una infinidad de oraciones. Esto no es posible más que por la misma naturaleza de las reglas del lenguaje, que poseen una propiedad muy particular, llamada en matemáticas recursividad, es decir, la posibilidad de reproducirse hasta el infinito. Se denomina competencia (Chomsky [1965]) al conocimiento que el emisor-receptor tiene de su lengua. La actuación se define como la utilización real en situaciones concretas de la competencia. A la competencia se la considera como un mecanismo finito, es decir, formado por un número limitado de reglas y capaz de generar un número infinito de oraciones. La gramática, que no es otra cosa que la explicación de la competencia, deberá responder a la misma definición para ser válida. Por eso deberá disponer de reglas estrictas, formuladas con una extremada precisión, y que podrán traducir esta propiedad de la competencia: la de ser un mecanismo finito para generar un número infinito de oraciones Teoría General y Gramáticas Particulares Normalmente se reserva el término gramática para el estudio de las lenguas particulares, y el de teoría general para el estudio de las particularidades en común que tienen todos los lenguajes, las diferencias entre ellos, y entonces, las condiciones sobre la forma que debe tener la gramática de cada lengua. Se ve así la importancia de la teoría general (TG en adelante) para la elaboración de las gramáticas particulares: éstas deben tener en cuenta la TG para ser adecuadas. Se denomina corpus a un conjunto de oraciones de un lenguaje. Para el estudio de lenguas que ya no existen, se trata de conseguir un corpus representativo de la misma. 282

283 Introducción a las Gramáticas Generativas La TG puede ser concebida de tres maneras diferentes incluyentes entre sí, y en cada una de ellas tendrá tareas distintas: 1. La TG puede proporcionar un conjunto de instrucciones, un mecanismo, que permita al lingüista construir la mejor gramática de una lengua, a partir de un corpus dado. El mecanismo sería de alguna manera "un procedimiento de descubrimiento" de la gramática adecuada para cada lengua. Gráficamente: Dato: Corpus TG Respuesta: gramática Fig. 24 La Teoría General como un mecanismo para construir gramática. 2. La TG puede proporcionar un método que, dados un corpus y una gramática, permita decir si la gramática es o no adecuada. Sería en este caso "un procedimiento de decisión". Datos: Corpus Gramática TG Respuesta: la gramática es o no es la adecuada Fig. 25 La Teoría General como un mecanismo de validación de gramáticas. 3. La TG puede, por último, ante dos gramáticas (o más) y un corpus, decir cuál de las dos es la más adecuada. Se le llama entonces "procedimiento de evaluación de las gramáticas". Datos: Gramática 1 Gramática 2 Corpus TG Respuesta: la mejor gramática es... Fig. 26 La Teoría General como un mecanismo de selección de gramáticas. 283

284 Introducción a las Gramáticas Generativas La primera es la más exigente, pero no la más difícil, viene a pedir a la teoría que diga qué forma debe tener la gramática de cada lengua. Esta pregunta es difícil de responder, por lo tanto no se está en condiciones de construir un procedimiento general de descubrimiento de las gramáticas. Pero lo que si se puede, es usar ciertos criterios para construir una gramática para un corpus dado. La segunda es un poco menos exigente, puesto que sólo se trata de verificar si las oraciones del corpus pueden ser generadas por la gramática, en otras palabras, que se las pueda analizar sintácticamente por medio de la gramática. La tercera es la más comprometida puesto que la selección se realiza en base a criterios formados con la experiencia del lingüista, y los criterios a usar pueden ser varios, todos ellos con fundamentos válidos. La Teoría General de la que se habló, recibió el nombre de Teoría de los Lenguajes Formales, los lingüistas la conocen también como Lingüística Computacional. Los temas tratados por esta teoría son mucho más profundos que los tratados en este capítulo Gramaticalidad e Interpretabilidad La Gramaticalidad es otro concepto importante. Para tener una visión más clara de lo que es la gramaticalidad, es preciso oponerla a la interpretabilidad. Sea la oración siguiente: El padre del primo del tío del abuelo del vecino de la hermana mayor de la segunda mujer del escribano vino ayer a verme. Sin duda, esta oración es ininterpretable cuando se accede a ella por primera vez, pero es gramaticalmente correcta, y obedece a una de las reglas que subyacen en la competencia de la que se ha hablado anteriormente. Es posible encontrarse frente a cuatro tipos de oraciones que son: 1. Gramaticales e interpretables. Ejemplo: A Pedro le gusta el chocolate. 284

285 Introducción a las Gramáticas Generativas 2. Gramaticales e ininterpretables. Ejemplo: Pedro, cuyo amigo cuyo hermano bebe está borracho ha comido chocolate. 3. Agramaticales e interpretables. Ejemplo: Mamá, da buen chocolate bebé. 4. Agramaticales e ininterpretables. Ejemplo: Chocolate la había ser lo canta árbol. Si se analiza sin mayor precisión la oración del ejemplo 2: Pablo ha comido el chocolate cuyo amigo está borracho cuyo hermano bebe se observará que la oración está gramaticalmente bien formada. En realidad, parece ser que la noción de interpretabilidad debería integrarse en el estudio de la actuación, ya que depende de factores como la limitación de la atención, comprensión, memoria, etc. Una teoría de la competencia, por el contrario, es una teoría del mecanismo del lenguaje, y debe, pues, dar cuenta de las oraciones gramaticales y excluir las agramaticales. La noción de gramaticalidad no puede ni debe confundirse con la aparición en un corpus, ni con la probabilidad estadística de aparición, ya que un buen número de oraciones que se pronuncian no son totalmente gramaticales, y las que son gramaticales en algunos casos nunca se pronuncian. En este sentido, la teoría de la competencia, es decir, la gramática generativa, aparece un poco como gramática normativa. Pero no lo es del mismo modo que lo eran las gramáticas tradicionales. No trata de preservar el "hermoso" lenguaje, y no se erige en el defensor de un pretendido "lenguaje correcto". Toma el lenguaje tal cual es, diferente según los individuos, según las clases sociales, según las situaciones, y trata sólo de dar cuenta de su funcionamiento. Pero no dicta ninguna regla del tipo: "No hay que decir... sino hay que decir...". Constata lo que se dice, lo que no se dice, o que no se dice ya tal o cual oración. No se pronuncia nunca sobre las nociones de "buen o mal lenguaje", "estilo pesado", "torpeza", etc. Le basta decir cuáles son las 285

286 Introducción a las Gramáticas Generativas oraciones gramaticales y cuáles las agramaticales, para dar cuenta de la estructura de las primeras y excluir las segundas La Gramática Generativa Noción de Gramática Generativa Apoyándose únicamente sobre las reflexiones precedentes, es posible considerar la gramática como una teoría que da cuenta de las oraciones gramaticales, y sólo de las gramaticales. Pero esta definición por si sola no es satisfactoria. La definición más correcta es: Una gramática es un modelo de la competencia, es decir, que debe hacer explícita la gramática implícita que poseen los sujetos hablantes. El término modelo es muy importante. La gramática, de alguna manera, es una máquina, un mecanismo que nos permite generar oraciones. Esquemáticamente: Entrada (instrucciones o reglas) Constitucion de Oraciones Salida (oraciones realizadas) Fig. 27 La gramática como mecanismo para generar oraciones. Este esquema muestra en qué sentido la gramática ha podido ser calificada de generativa. Permite generar el conjunto infinito de oraciones de la lengua. Pero no hay que confundirla con una máquina que permita la emisión real de las oraciones. Además, se hace la observación de que la gramática es "neutra" tanto frente al emisor como al receptor. Es una teoría de la estructura y del funcionamiento del código lingüístico, y no dice nada respecto al mecanismo físico-psicológico que permite hablar y comprender. La gramática generativa es tan sólo la explicitación del sistema de reglas que subyace a la competencia, y la competencia es común al hablante y al 286

287 Introducción a las Gramáticas Generativas oyente. En realidad, el estudio de la emisión y de la recepción cae dentro de una teoría de la actuación. De hecho, la gramática generativa no es una gramática en el sentido en el que habitualmente se da a esta palabra. Sin duda se ocupa también de la estructura de la lengua, pero si se distingue de las otras gramáticas no es sólo por el punto de vista que ha elegido, sino sobre todo por el fin que se ha impuesto. Las gramáticas tradicionales y estructurales eran modelos taxonómicos de la lengua, mientras que las generativas son un modelo explicativo. Desea no sólo formar un inventario de los elementos lingüísticos, sino también explicar su funcionamiento, la regularidad de cada lengua, las características comunes universales del lenguaje, y dar cuenta del fenómeno de la creatividad. En este sentido, las gramáticas taxonómicas son a la vez anteriores y necesarias para la gramática generativa: las primeras describen los hechos lingüísticos que la segunda explica. Al construir una gramática generativa, es decir, al dar cuenta de la competencia, se debe partir de un hablante-oyente ideal que pertenezca a una comunidad lingüística completamente homogénea, que conozca perfectamente su lengua y que, cuando aplique ese conocimiento en una actuación real, no esté influenciado por condiciones gramaticalmente irrelevantes como la limitación de memoria, distracciones de interés o atención, o errores. Es una teoría de la actuación la que deberá considerar estos diversos fenómenos Sincronía / Diacronía Por el mismo fin que se impone, la gramática generativa no puede estudiar la lengua más que en un cierto momento de su historia. Los términos lingüística sincrónica y lingüística diacrónica fueron introducidos por Saussure [1916] para distinguir los estudios que tienen por objeto un cierto estado de la lengua de aquellos que se interesan por su evolución, respectivamente. La gramática generativa no puede ser más que sincrónica, en el mismo sentido en que la competencia no puede estar situada más que en un momento determinado en el tiempo y en la evolución de la lengua Análisis en Constituyentes Inmediatos Se desarrollará un ejemplo simple para explicar cómo construir una gramática generativa. Lo que se está por hacer es descubrir los aspectos más 287

288 Introducción a las Gramáticas Generativas importantes de la teoría general antes mencionada. A tal efecto, se debe olvidar por un momento de la TG y concentras la atención en la tarea que ésta puede realizar: dado un corpus, obtener la gramática adecuada para el lenguaje al que pertenece el corpus. Se tiene un corpus cuyos componentes son oraciones que tienen la misma estructura gramatical, según una gramática tradicional. Las oraciones pertenecen al lenguaje utilizado en la escritura de este documento (Castellano). El corpus es el siguiente: El caballo salta el alambrado. El gato toma la leche. El perro come la carne. El reloj marca la hora. La niña saca la lengua. Fig. 28 Un corpus del Castellano. Si tomamos la primera oración, lo primero que se observa es una oración correcta. Esa oración tiene sujeto y predicado, que son los dos constituyentes inmediatos de la oración. Si se analiza el sujeto, se observa que tiene un núcleo y un modificador directo (MD); y éstos son sus componentes inmediatos. Lo mismo ocurre con el predicado, que tiene un verbo y un objeto directo (OD) como constituyentes. A su vez, el OD está formado por un núcleo y un MD. Gráficamente: El caballo salta el alambrado. El caballo salta el alambrado. El caballo salta el alambrado. El caballo salta el alambrado. Fig. 29 Separación de la oración en constituyentes inmediatos. 288

289 Introducción a las Gramáticas Generativas o bien: 1 O 2 S P 3 MD N V OD 4 MD N V MD N Fig. 30 Análisis de la oración en constituyentes inmediatos. El paso 1 toma la oración. El paso 2 separa la oración en sus componentes inmediatos y así sucesivamente. Se podría decir que del paso 4 sigue un quinto: 4 MD N V MD N 5 El caballo salta el alambrado. Fig. 31 Ultimo paso del análisis en constituyentes inmediatos. con el cual demostramos que el análisis en constituyentes inmediatos es válido. Si al esquema realizado se lo grafica de otra forma, se tendría lo siguiente: O S P V OD MD N MD N El caballo salta el alambrado 289

290 Introducción a las Gramáticas Generativas Fig. 32 Análisis en componentes inmediatos (análisis sintáctico) de la oración del corpus. Este esquema es similar al anterior, y difiere solamente en que se utilizan menos líneas. Como se observará, el gráfico es más claro que el anterior. A este tipo de esquema se lo conoce como árbol, y en lingüística computacional, como árbol de derivación o de análisis sintáctico. Para generar las oraciones, la gramática debe poseer un conjunto de instrucciones. Estas se presentan bajo la forma de reglas que permiten rescribir un símbolo en una secuencia de símbolos. El problema aquí ya no es la estructura de una oración particular, sino el de la estructura de toda oración potencial. En el corpus, las oraciones estaban formadas por sujetos (S) y predicados (P). Se puede, entonces, formular una regla que diga más o menos lo siguiente: si se está en presencia del símbolo O (oración), rescríbalo en S P (es decir, la concatenación de las nociones sujeto y predicado). Esta regla tendrá la forma: O ---> S P Se leerá "O se rescribe en S P". La regla dada indica cómo pasar del paso 1 al 2 en el análisis realizado arriba, es decir, dice cuáles son los constituyentes de la noción O (oración). Se puede así escribir el conjunto de reglas: O ---> S P S ---> MD N P ---> V OD OD ---> MD N (Se debe tener en cuenta que en este ejemplo no se consideran aspectos semánticos y morfológicos.) Estas reglas no son suficientes para generar verdaderamente las oraciones. Hay que añadirles otra serie de reglas llamadas reglas léxicas: MD ---> el la N ---> caballo alambrado gato leche perro carne reloj 290

291 Introducción a las Gramáticas Generativas hora niña lengua V ---> salta toma come marca saca Se leerá "MD se rescribe en el ó en la". El "ó" es en sentido excluyente. Las palabras que aparecen en las reglas lexicales son todas aquellas que formaban alguna oración en el corpus, no son palabras que no estaban en el corpus (es decir, no se las inventa). Se debe observar que las reglas lexicales se rescriben en nociones (o símbolos) que pertenecen al lenguaje. Dichas nociones o símbolos se denominan nociones terminales o símbolos terminales. Las nociones o símbolos que en este ejemplo se escribieron con mayúsculas, son utilizadas para describir al lenguaje que se está estudiando, y reciben el nombre de metanociones o símbolos no terminales. Al lenguaje utilizado para describir un lenguaje se lo denomina metalenguaje. Aplicando sucesivamente las diferentes reglas obtenidas, se puede obtener lo que se llama una derivación. Si para una oración dada se puede construir un árbol de derivación que contenga sólo símbolos terminales en sus hojas, se dirá que la oración es sintácticamente correcta (o gramaticalmente correcta), sin importar si es interpretable o no. Para mostrar más específicamente por qué se dice que la gramática es generativa, se partirá desde O, y se realizarán derivaciones sucesivas hasta llegar a obtener símbolos terminales en las hojas del árbol de derivación. Una posibilidad sería la siguiente: O S P V OD MD N MD N La niña toma la leche Fig. 33 Ejemplo de generación de una oración. La oración obtenida es gramaticalmente correcta, y no figura en el corpus. Es en este sentido en que se considera a la gramática como generativa, porque a partir de un número finito de reglas se puede generar un número muy 291

292 Introducción a las Gramáticas Generativas grandes (y a veces infinito) de oraciones. Por supuesto que si se hubieran elegido otras reglas léxicas se habría llegado a una oración como "El gato come el alambrado", lo cual no tiene sentido. La solución a este problema puede ser agregar más reglas que no permitan tal combinación. Igualmente, la concordancia en género y número del núcleo con el modificador directo se puede resolver agregando reglas que restrinjan el uso de ellos. Obsérvese que las reglas "S ---> N MD" y "OD ---> N MD" tienen la misma secuencia de símbolos en la parte derecha de la flecha. Debido a que la gramática sólo explica la estructura y no el significado, esas dos reglas se pueden resumir en una sola, obteniéndose así una gramática mas pequeña. Por último se puede decir que, si se tiene la descripción del lenguaje (corpus), es probable que se pueda encontrar una gramática generativa que describa la estructura del mismo. Lo de probable viene del hecho de que la teoría general no tiene un procedimiento para escribir las reglas de la gramática, por lo que el lingüista debe usar mucho su intuición. La explicación del concepto de recursividad y del por qué es posible generar infinitas oraciones con un número finito de reglas se da en el capítulo 2 de este trabajo. 292

293 Un poco de Historia Capítulo 18: Un poco de Historia 18.1 Los descubrimientos y las investigaciones Chomsky [1956] introdujo las gramáticas independientes del contexto como parte de un estudio sobre lenguajes naturales. La utilización de este tipo de gramáticas para la especificación de la sintaxis de los lenguajes de programación surgió independientemente. El lingüista Panini diseñó una notación sintáctica equivalente para especificar las reglas de la gramática del sánscrito de entre el 400 a.c. y el 200 a.c. (Ingerman [1967]). En una carta de Knuth [1964], está contenida la propuesta de que BNF, que comenzó como una abreviatura de Backus Normal Form (forma normal de Backus), se leyera Backus-Naur Form (forma de Backus-Naur), para reconocer las contribuciones de Naur como editor del informe de ALGOL 60 (Naur [1963]). Las definiciones dirigidas por la sintaxis son una forma de definición inductiva, en la cual la inducción se encuentra en la estructura sintáctica. Como tales, han sido muy utilizadas en matemática. Su aplicación a los lenguajes de programación se introdujo con el uso de una gramática para estructurar el informe de ALGOL 60. Poco tiempo después, Irons [1961] construyó un compilador dirigido por la sintaxis. El análisis sintáctico descendente recursivo se utiliza aproximadamente desde Bauer [1976] atribuye el método a Lucas [1961]. Hoare [1962, pág. 128] describe un compilador de ALGOL organizado como "un conjunto de procedimientos, cada uno de los cuales puede procesar una de las unidades sintácticas del informe de ALGOL 60". Foster [1968] analiza la eliminación de la recursividad por la izquierda de las producciones con acciones semánticas que no afecten a los valores de los atributos. McKarthy [1963] abogaba por que la traducción de un lenguaje se basara en una sintaxis abstracta. En el mismo artículo, McKarthy [1963, pág. 24] dejaba "que el lector se convenciera por sí mismo" de que una formulación recursiva por el final de la función factorial es equivalente a un programa iterativo. Las ventajas de dividir un compilador en una etapa inicial y otra final se analizaron en un informe del comité de Strong y colaboradores [1958]. El informe acuño el término UNCOL (del inglés universal computer oriented 293

294 Un poco de Historia language, lenguaje orientado a un computador universal) para un lenguaje intermedio universal. El concepto ha quedado como un ideal. Kernighan y Pike [1984] describen en detalle cómo construir un programa de calculadora de escritorio a partir de un esquema de traducción dirigida por la sintaxis, utilizando las herramientas para la construcción de compiladores disponibles en el sistema operativo UNIX. Las limitaciones impuestas a los aspectos léxicos de un lenguaje suelen estar determinadas por el ambiente en que se creó el lenguaje. Cuando se diseñó FORTRAN en 1954, las tarjetas perforadas eran un medio común de entrada. En FORTRAN se ignoraron los espacios en blanco debido en parte a que los perforistas, que preparaban las tarjetas a partir de notas escritas a mano, tendían a equivocarse al contar los espacios en blanco (Backus [1981]). La separación en ALGOL 58 de la representación en hardware a partir del lenguaje de referencia fue un acuerdo alcanzado debido a que un miembro del comité de diseño insistió: "No, nunca usaré un punto para el signo decimal". (Wegstein [1981]). Knuth [1973] presenta otras técnicas para manejar la entrada con buffers. Feldman [1979b] analiza las dificultades prácticas del reconocimiento de componentes léxicos en FORTRAN 77. Las expresiones regulares fueron estudiadas por primera vez por Kleene [1956], que estaba interesado en describir los acontecimientos que se podían representar con el modelo de autómata finito de actividad nerviosa de McCulloch y Pitts [1943]. La minimización de los autómatas finitos fue estudiada por primera vez por Huffman [1954] y Moore [1956]. La equivalencia entre autómatas determinísticos y no determinísticos en cuanto a su capacidad para reconocer lenguajes fue mostrada por Rabin y Scott [1959]. McNaughton y Yamada [1960] describen un algoritmo para construir un AFD directamente a partir de una expresión regular, que es el que se implementa en este trabajo. Pronto se comprendió que las herramientas para construir analizadores lexicográficos a partir de especificaciones en forma de expresiones regulares serían útiles en la implantación de compiladores. En Johnson y otros [1968] se analiza uno de estos primeros sistemas. LEX, se debe a Lesk [1975], y se ha utilizado para construir analizadores lexicográficos para muchos compiladores que trabajan bajo UNIX. Las expresiones regulares y los autómatas finitos se han utilizado para muchas aplicaciones, además de para la compilación. Muchos editores de texto 294

295 Un poco de Historia usan expresiones regulares para búsquedas dentro del texto. El sistema UNIX tiene tres programas de búsqueda de propósito general basados en expresiones regulares: grep, egrep y fgrep. El programa grep no permite unión o paréntesis para agrupar en sus expresiones regulares, pero si una forma limitada de referencia hacia atrás como en SNOBOL. Actualmente, grep se encuentra en casi todos los sistemas operativos de computadoras personales. También se utilizan las expresiones regulares en lenguajes de consultas de bases de datos y en lenguajes para procesamiento de archivos, como AWK (Aho, Kernighan y Weinberger [1979]). Jarvis [1976] utilizó expresiones regulares para describir rasgos distintivos en circuitos impresos. El muy influyente informe de ALGOL 60 (Naur [1963]) utilizó la forma de Backus Naur (BNF) para definir la sintaxis de un importante lenguaje de programación. Se descubrió la equivalencia entre BNF y las gramáticas independientes del contexto, y la teoría de los lenguajes formales fue objeto de una gran atención en la década de Los métodos de análisis sintáctico se hicieron mucho más sistemáticos tras el desarrollo de las gramáticas independientes del contexto. Se inventaron diversas técnicas generales para analizar cualquier gramática independiente del contexto. Una de las primeras es la técnica de programación dinámica descubierta independientemente por Younger [1967] y Kasami [1965]. Como tesis doctoral, Earley [1970] también desarrolló un algoritmo de análisis sintáctico universal para todas las gramáticas independientes del contexto. Se han empleado muchos métodos distintos de análisis sintáctico en los compiladores. Los análisis sintácticos por descenso recursivo y predictivo son muy utilizados en la práctica. Dada su flexibilidad, se utilizó el análisis sintáctico por descenso recursivo en muchos de los primeros generadores de compiladores como META (Schorre [1964]) y TMG (McClure [1965]). Pratt [1973] propone un método de análisis sintáctico descendente por precedencia de operadores. Las gramáticas LL fueron estudiadas por Lewis y Stearns [1968] y sus propiedades se desarrollaron en Rosenkrantz y Stearns [1970]. Los analizadores sintácticos predictivos fueron estudiados a fondo por Knuth [1971]. Lewis, Rosenkrantz y Stearns [1976] describen el uso de los analizadores sintácticos predictivos en los compiladores. En Tamagnini [1994] se presenta un generador de tablas para el análisis sintáctico LL(1). Los algoritmos para convertir gramáticas a la forma LL(1) se introducen en Foster [1968], Wood [1969], Stearns [1971] y Soisalon-Soininen y Ukkonen [1979]. 295

296 Un poco de Historia Las gramáticas y los analizadores sintácticos LR fueron introducidos por primera vez por Knuth [1965], quien describió la construcción de las tablas de análisis sintáctico LR canónico. El método LR no resultó práctico hasta que Korenjak [1969] mostró que con él se podrían producir analizadores sintácticos de tamaño razonable para gramáticas de los lenguajes de programación. Cuando DeRemer [1969, 1971] inventó los métodos SLR y LALR, que son más simples que el de Korenjak, la técnica LR se convirtió en el método elegido para los generadores automáticos de analizadores sintácticos. Hoy en día, los generadores de analizadores LR son habituales en los entornos de construcción de compiladores. La impresión de que el análisis sintáctico descendente permite una mayor flexibilidad en la traducción resultó ser falsa, ya que Brosgol [1974] demostró que un esquema de traducción basado en una gramática LL(1) se puede simular durante el análisis sintáctico LR(1). Gran parte de la investigación se dedicó a la construcción de analizadores sintácticos LR. El uso de gramáticas ambiguas en el análisis sintáctico LR se debe a Aho, Johnson y Ullman [1975] y a Earley [1975]. La eliminación de las reducciones por reglas simples ha sido estudiada en Anderson, Eve y Horning [1973], Aho y Ullman [1973b], Demers [1975], Backhouse [1976], Joliat [1976], Pager [1977], Soisalon-Soininen [1980] y Tokuda [1981]. Watt [1977] utilizó no terminales marcadores para garantizar que los valores de los atributos heredados aparezcan en una pila durante el análisis sintáctico ascendente. Las posiciones en los lados derechos de las reglas donde se pueden insertar los no terminales marcadores sin perder la propiedad LR(1) son estudiadas por Purdom y Brown [1980]. Aho y Johnson [1974] realizan un estudio general del análisis sintáctico LR y analizan algunos de los algoritmos en que se basa el generador de analizadores sintácticos YACC, incluido el uso de producciones de error para la recuperación de errores. Aho y Ullman [1972 y 1973a] dan un tratamiento bastante completo del análisis sintáctico LR y de sus fundamentos teóricos. La corrección de errores durante el análisis sintáctico es estudiada por Conway y Maxwell [1963], Moulton y Muller [1967], Conway y Wilcox [1973], Levy [1975], Tai [1978] y Röhrich [1980]. 296

297 Un poco de Historia 18.2 Historia del desarrollo de los Generadores Presentados en este trabajo SLR1 v 1 y su biblioteca de soporte A fines de noviembre de 1992 comienza el desarrollo de SLR1 v 1. El primer prototipo funcionando se obtuvo el 15 Dic 92; el ejecutable se llamó SLR1 y, desde ese momento el generador recibió ese nombre. Para la carga de la gramática en memoria se utiliza un analizador lexicográfico hecho a mano, y un analizador sintáctico SLR hecho a mano. La primera revisión de SLR1 v 1 fue en Set 93. La segunda revisión comenzó en Dic 94 y terminó el 21 Dic 94; al finalizar esta revisión nace la versión 2 de SLR1. Esta versión incluye el uso de templates de C++ para las estructuras de datos. La implementación del generador queda pendiente, sólo se creó la biblioteca de soporte del mismo AFD v 1 y la biblioteca ExpReg v 1 La primera versión de AFD, el generador de autómatas finitos determinísticos a partir de expresiones regulares, fue comenzado a desarrollar el 09 Ene 95, su análisis comenzó en Dic 94, durante la segunda revisión de SLR1. La obtención del primer prototipo fue el 13 Ene 95, y recibe el nombre de AFD v 1, el ejecutable tenía el nombre AFD.EXE y corría bajo DOS. Para cargar la expresión regular en memoria se utilizó un analizador sintáctico generado con SLR1 v 1. El analizador lexicográfico de esta versión devolvía símbolos léxicos de longitud igual a 1. El 13 Ene 95 también nace la biblioteca ExpReg v 1, que hasta el momento de la escritura de este capítulo no cambió AFD v 2 Utilizando AFD v 1 y la biblioteca ExpReg v 1 se generaron autómatas para el analizador lexicográfico de las versiones siguientes de AFD. AFD v 2 nace el 19 Ene 95, su analizador lexicográfico reconoce ahora símbolos de longitud mayor de un caracter, y se incluye la posibilidad de utilizar secuencias de escape similares a las del C++, a excepción de \0 y \xddd. 297

298 Un poco de Historia AFD v 3.1 y 3.2 AFD v 3.1 comienza a desarrollarse el 19 Ene 95 y finalizó el 24 Ene 95. La gramática de AFD v 2 se cambió por una nueva, y se mejora el analizador lexicográfico para que trabaje correctamente con la nueva gramática. Se cambiaron las precedencias de los operadores que pueden participar en una expresión regular. AFD v 3.2 incluye la posibilidad de especificar archivos de entrada y de salida SLR1 v 2 y su biblioteca de soporte SLR1 v 2 nace el 26 Ene 95. Se utilizan SLR1 v 1 y AFD v 3.2 para construir los analizadores sintáctico y lexicográfico. Los principales cambios fueron: 1. El analizador sintáctico es ahora un objeto construido a partir de las tablas generadas por SLR1 v 1 y, opcionalmente, se especifica la función de acciones semánticas y la función para ver el estado de la pila del analizador. El análisis sintáctico se realiza a través de la función Analizar de la clase que implementa el analizador (queda todo encapsulado en esa clase). El analizador lexicográfico pasa a ser un parámetro en el llamado a la función Analizar. 2. El objeto analizador es una instancia de la clase AnalSLR1<XX> donde AnalSLR1 es una clase template y XX es la clase que implementa el elemento de la pila del analizador. Por defecto, el elemento de la pila recibe el nombre de ElemPila y su declaración se provee al usuario de SLR1 v Se agrega la posibilidad de la "limpieza" de la pila, para los casos en que las clases que implementen su estructura sean complejas. El segundo prototipo fue desarrollado con la versión 2 de la metagramática para la especificación de gramáticas, en donde se incluyen acciones semánticas. A partir de ese momento es posible especificar directamente esquemas de traducción a SLR1 v 2. El analizador sintáctico del segundo prototipo fue generado a partir del primer prototipo de esta misma versión. El analizador lexicográfico fue generado por el primer prototipo de SLR1 v 2, mientras que las expresiones regulares y los AFDs no cambiaron. 298

299 Un poco de Historia 18.3 Historia del capítulo 17 El documento que da una introducción a las gramáticas generativas, presentado en el capítulo 17 de este trabajo, fue escrito por primera vez en Junio de 1991, con la colaboración de la profesora Rosa Díaz de Lopez, para servir de apoyo teórico a la cátedra Idioma I de la carrera de Ingeniería en Computación de la Universidad Católica de Santiago del Estero. La primera revisión del documento fue realizada por el autor y por la profesora al momento de terminar de escribirlo. La segunda revisión del mismo fue realizada en Junio de La tercera revisión en Junio de Y la última revisión en Febrero de 1996, durante su inclusión en este trabajo. 299

300 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x Capítulo 19: Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x La biblioteca de soporte de la versión 1.x está incluida en esta versión, por tal razón, su códifo fuente no es incluida y se incluye sólo los de la versión 2.x. Los módulos fuentes presentados en este capítulo son independientes del sistema operativo y del compilador con el cual se trabaje. No ocurre lo mismo con la implementación de SLR1 v 2.x. A los efectos de no repetir código fuente dentro de este trabajo, cuando sea aplicable, se introducirán referencias hacia otras partes dentro de este documento, en donde era oportuno incluirlos GRAMLC.H # ifndef GRAMLC_H # define GRAMLC_H // GRAMLC.H: definición de las clases necesarias para tratar con gramáticas // libres de contexto. // Creación: // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. class Archivo; // declarada en archabst.h (de la biblioteca de clases BCE) class ArchivoSO; // declarada en archivo.h (de BCE) # ifndef ASCII0_H # include <ascii0.h> # endif # ifndef FVARIAS_H # include <fvarias.h> // func Aceptar # endif class Simbolo { protected: Ascii0 * Cad; Ent8 ReasignarCadena( const char * cad ); public: Ent16ns Codigo : 15; // 15 bits para el código de símbolo (máx símbolos) Ent8ns NoTerminal : 1; // 1 bit para decir si es no terminal o terminal Simbolo() { Codigo = NoTerminal = 0; Cad = 0; Simbolo( const Simbolo & s ); Simbolo( const char * Cadena, Ent16ns Codigo, Ent8ns NoTerminal ); Simbolo( const Ascii0 & cadena, Ent16ns codigo, Ent8ns noterminal ); 300

301 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x virtual ~Simbolo(); const Ascii0 & Cadena() const; static Ascii0 ErrorCadenaNula; // por si Cad == 0 Ent8 Reasignar( const char * Cadena, Ent16ns Codigo, Ent8ns NoTerminal ); Ent8 Reasignar( const Ascii0 & Cadena, Ent16ns Codigo, Ent8ns NoTerminal ); Simbolo & operator = ( const Simbolo & s ); Ent8 operator == ( const char * cadena ) const; Ent8 operator == ( Ent16ns CodComp ) const; Ent16ns CodigoCompuesto() const; enum { CAD, CAD_COD, CAD_TER, CAD_COD_TER ; static Ent8 Mostrar( Ent8 que = CAD ); // // por defecto se muestra solo la cadena del símbolo ; inline Simbolo::Simbolo( const Simbolo & s ) { Codigo = NoTerminal = 0; Cad = 0; operator = (s); inline Simbolo::Simbolo( const char * Cadena, Ent16ns Codigo, Ent8ns NoTerminal ) { Cad = 0; Reasignar(Cadena,Codigo,NoTerminal); inline Simbolo::Simbolo( const Ascii0 & cadena, Ent16ns codigo, Ent8ns noterminal ) { Cad = 0; Reasignar(cadena,codigo,noterminal); inline Ent8 Simbolo::ReasignarCadena( const char * cadena ) { if (Cad!= 0) { delete Cad; Cad = 0; if (cadena == 0) return 1; // cadena fuente nula Cad = new Ascii0; if (Cad == 0) return 0; *Cad = cadena; Cad->Reasignar(); // hace que Cad tenga una copia de cadena en mem. din. return CodError == 0; inline Ent8 Simbolo::Reasignar( const char * cadena, Ent16ns cod, Ent8ns noterm ) { Codigo = cod; NoTerminal = noterm; return ReasignarCadena(cadena); inline Ent8 Simbolo::Reasignar( const Ascii0 & cadena, Ent16ns cod, Ent8ns noterm ) { Codigo = cod; NoTerminal = noterm; return ReasignarCadena(cadena); 301

302 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x inline Simbolo & Simbolo::operator = ( const Simbolo & s ) { Reasignar(s.Cadena(), s.codigo, s.noterminal); return *this; inline Ent16ns CodigoCompuesto( Ent16ns cod, Ent8ns noterminal ) { return noterminal? 0x8000 cod : cod; inline Ent16ns Simbolo::CodigoCompuesto() const { return ::CodigoCompuesto(Codigo, NoTerminal); inline const Ascii0 & Simbolo::Cadena() const { return Cad == 0? (ErrorCadenaNula.Vaciar(), ErrorCadenaNula) : *Cad; extern "C" int stricmp( const char *, const char *); inline Ent8 Simbolo::operator == ( const char * cadena ) const { return Cad == 0! Cad->Long()? 0 :! stricmp(cadena, *Cad); inline Ent8 Simbolo::operator == ( Ent16ns CodComp ) const { return CodComp == CodigoCompuesto(); Archivo & operator << ( Archivo & a, const Simbolo & s ); ArchivoSO & operator << ( ArchivoSO & a, const Simbolo & s ); ArchivoSO & operator >> ( ArchivoSO & a, Simbolo & s ); // Clase ConjSimb: define la forma que debe tener un conjunto de // símbolos. class ConjSimb { // Conjunto de Símbolos genérico public: ConjSimb() { virtual ~ConjSimb() { virtual void Vaciar() = 0; // vacía al conjuto de símbolos // Si la implementación lo permite, las funciones Agregar deben // agregar el símbolo al final de todos los anteriores. virtual Ent8 Agregar( const char * cadena, Ent16ns codigo, Ent8ns noterminal, Ent8 AceptRedundancia ) = 0; virtual Ent8 Agregar( const Simbolo & s, Ent8 AceptRedundancia ) = 0; virtual const Simbolo * Buscar( const char * Cadena ) const = 0; virtual const Simbolo * Buscar( Ent16ns CodigoCompSimb ) const = 0; virtual Ent16ns NumSimb() const = 0; // devuelve el número de símbolos Ent8 EsElemento( const char * Cadena ) const; Ent8 EsElemento( const Ascii0 & Cadena ) const; Ent8 EsElemento( Ent16ns CodigoCompSimb ) const; Ent8 EsElemento( const Simbolo & s ) const; ; inline Ent8 ConjSimb::EsElemento( const char * Cadena ) const { return Buscar(Cadena)!= 0; inline Ent8 ConjSimb::EsElemento( const Ascii0 & Cadena ) const { return ConjSimb::EsElemento((const char *) Cadena); inline Ent8 ConjSimb::EsElemento( Ent16ns CodigoCompSimb ) const { return Buscar(CodigoCompSimb)!= 0; 302

303 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x inline Ent8 ConjSimb::EsElemento( const Simbolo & s ) const { if (s.cadena().long()) return EsElemento(s.Cadena()) EsElemento(s.CodigoCompuesto()); else return EsElemento(s.CodigoCompuesto()); Ent8 CambAcepAmbig(); # ifndef LSE_H # include "lse.h" # endif // ConjSimbLSE: Conjunto de símbolos donde los símbolos se encuentran en una // lista simple encadenada. class ConjSimbLSE : public ConjSimb { protected: void Agregar( register NodoLSE<Simbolo> * p ); public: LSE<Simbolo> lse; NodoLSE<Simbolo> * pult; ConjSimbLSE() { pult = 0; ConjSimbLSE( const ConjSimbLSE & cs ); virtual ~ConjSimbLSE() { virtual void Vaciar() { lse.vaciar(); pult = 0; // vacia el conjunto virtual Ent16ns NumSimb() const { return lse.numelem(); virtual Ent8 Agregar( const char * cadena, Ent16ns codigo, Ent8ns noterminal, Ent8 AceptRedundancia = 0 ); virtual Ent8 Agregar( const Simbolo & s, Ent8 AceptRedundancia = 0 ); Ent8 Agregar( Ent16ns codigo, Ent8ns noterminal, Ent8 AceptRedundancia = 0 ); Ent8 AgregarSinCad( const Simbolo & s, Ent8 AceptRedundancia = 0 ); Ent16ns Agregar( const ConjSimbLSE & cs, Ent8 AceptRedundancia = 0 ); const Simbolo * Buscar( const char * Cadena ) const; const Simbolo * Buscar( const Ascii0 & Cadena ) const; const Simbolo * Buscar( Ent16ns CodigoCompSimb ) const; ; inline ConjSimbLSE::ConjSimbLSE( const ConjSimbLSE & cs ) { pult = 0; Agregar(cs); inline void ConjSimbLSE::Agregar( register NodoLSE<Simbolo> * p ) { if (lse.entrada == 0) lse.entrada = pult = p; else { pult->psig = p; pult = p; inline Ent8 ConjSimbLSE::Agregar( const Simbolo & s, Ent8 ar ) { register NodoLSE<Simbolo> * p; // Si no se acepta redundancia y el símbolo ya existe no se agrega // antes del 30Ene96: 303

304 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x // if (! ar && ( EsElemento(s.CodigoCompuesto()) // (const char *) s.cadena()!= 0 && EsElemento(s.Cadena()) ) ) // ahora: if (! ar && EsElemento(s) ) return 0; p = new NodoLSE<Simbolo>(s); if (p == 0) return 0; Agregar(p); return 1; inline Ent8 ConjSimbLSE::Agregar( const char * cadena, Ent16ns codigo, Ent8ns noterminal, Ent8 ar ) { register NodoLSE<Simbolo> * p; // Si no se acepta redundancia y el símbolo ya existe no se agrega if (! ar && (EsElemento(CodigoCompuesto(codigo, noterminal)) cadena!= 0 && EsElemento(cadena))) return 0; p = new NodoLSE<Simbolo>; if (p == 0) return 0; p->dato.reasignar(cadena, codigo, noterminal); Agregar(p); return 1; inline Ent8 ConjSimbLSE::Agregar( Ent16ns cod, Ent8ns noterm, Ent8 ar ) { return ConjSimbLSE::Agregar(0, cod, noterm, ar); inline Ent8 ConjSimbLSE::AgregarSinCad( const Simbolo & s, Ent8 ar ) { return ConjSimbLSE::Agregar(0, s.codigo, s.noterminal, ar); inline const Simbolo * ConjSimbLSE::Buscar( const Ascii0 & Cadena ) const { return ConjSimbLSE::Buscar((const char *) Cadena); Archivo & operator << ( Archivo & archivo, const ConjSimbLSE & conjunto ); class Alfabeto; class ReglaLC : public ConjSimbLSE { // Regla de una gramática Libre de Contexto public: Ascii0 AccSem; // acciones semánticas ReglaLC() { ReglaLC( const ReglaLC & r ); virtual ~ReglaLC() { Ent8 Agregar( const char * cadena, Ent16ns codigo = 0, Ent8ns noterminal = 0, Ent8 AceptRedundancia = 1 ); Ent8 Agregar( const Simbolo & s, Ent8 AceptRedundancia = 1 ); Ent8 Agregar( const ConjSimbLSE & cs, Ent8 AceptRedundancia = 1 ); Ent8 Agregar( Ent16ns codigo, Ent8ns noterminal, Ent8 AceptRedundancia = 1 ); Ent8 AgregarSinCad( const Simbolo & s, Ent8 AceptRedundancia = 1 ); Ent16ns LongParteDer() const; // longitud de la parte derecha de la regla const Simbolo * ParteIzq() const { return SimboloI(0); 304

305 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x const Simbolo * SimboloI( Ent16ns i ) const; void Vaciar() { ConjSimbLSE::Vaciar(); AccSem.Vaciar(); ; inline ReglaLC::ReglaLC( const ReglaLC & r ) { Agregar(r); AccSem = r.accsem; inline Ent8 ReglaLC::Agregar( const char * cad, Ent16ns cod, Ent8ns nt, Ent8 ar ) { return ConjSimbLSE::Agregar(cad,cod,nt,ar); inline Ent8 ReglaLC::Agregar( const Simbolo & s, Ent8 ar ) { return ConjSimbLSE::Agregar(s,ar); inline Ent8 ReglaLC::Agregar( const ConjSimbLSE & cs, Ent8 ar ) { return ConjSimbLSE::Agregar(cs,ar); inline Ent8 ReglaLC::Agregar( Ent16ns cod, Ent8ns noterm, Ent8 ar ) { return ConjSimbLSE::Agregar(cod, noterm, ar); inline Ent8 ReglaLC::AgregarSinCad( const Simbolo & s, Ent8 ar ) { return ConjSimbLSE::AgregarSinCad(s, ar); inline Ent16ns ReglaLC::LongParteDer() const { register Ent16ns c = ConjSimbLSE::NumSimb(); return c? c-1 : 0; inline const Simbolo * ReglaLC::SimboloI( Ent16ns i ) const { register ReglaLC * este = (ReglaLC *) this; // this es const ReglaLC * return este->lse.datonodoi(i); Archivo & operator << ( Archivo & a, const ReglaLC & r ); class Alfabeto : public ConjSimbLSE { public: Alfabeto() { Alfabeto( const Alfabeto & a ) : ConjSimbLSE(a) { virtual ~Alfabeto() { ; // Todas las funciones de ConjSimbLSE se heredan públicamente // por lo que están disponibles para su uso. class DescrTerminal { public: Ascii0 Cadena, NomArch; DescrTerminal() { ~DescrTerminal() { ; // Cadena: Ident que aparece en las directivas IGNORAR y TERMINAL. Si // Cadena no es hallado en el conj. de terminales, se usó IGNORAR. // NomArch: nombre del archivo donde está la expresión regular. Si //! NomArch.Long() entonces el nombre es la Cadena del símbolo. 305

306 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x // DescrAccSem: clase que describe una acción semántica común a dos // o más reglas. class DescrAccSem { public: Ascii0 Ident, TxtAccSem; DescrAccSem() { ~DescrAccSem() { ; // Ident: identificador usado en la directiva ACCSEM. // TxtAccSem: texto correspondiente al BlqCod especificado en la directiva. class Gramatica { public: Alfabeto Term, NoTerm; LSE<ReglaLC> Reglas; Ascii0 BlqCodInic, ClaseElemPila; LSE<DescrAccSem> TablaAccSem; // Acciones semánticas comunes a dos o // más reglas. LSE<DescrTerminal> TablaTerminal; // si hay terminales descriptos por // expresiones regulares, aquí están los // nombres de archivo. Ent8 MayIgualMin; static Ent16ns CodError; Gramatica() { MayIgualMin = 0; Gramatica( Alfabeto & terminal, Alfabeto & no_terminal ); virtual ~Gramatica() { Ent8 Reasignar( const char * TxtGram, Ascii0 & CadPosErr ); // Reasignar carga gramática en formato texto de TxtGram. CadPosErr tiene // la posición del error si se produce alguno. En Gramatica::CodError está // el código de error luego de reasignar. Evalúe con los códigos de la // clase AnalSLR1 declarada en slr1.h. Ent8 AgregRegla( const ReglaLC & regla ); const Simbolo * SimbInic() const; Ent16ns NumNoTerm() const { return NoTerm.NumSimb(); Ent16ns NumTerm() const { return Term.NumSimb(); Ent16ns ArmarAlfabetos(); // ArmarAlfabetos construye Term y NoTerm a partir de Reglas. Asigna // códigos a los símbolos que aparecen en Reglas de acuerdo al orden de // aparición en las mismas. Ent8 AnulableSimple( Ent16ns CodCompSimbNoTerminal ) const; Ent8 Anulable( Ent8 * pvbandanulable ) const; Ent16ns CalcPrimeroSinLambda( Alfabeto * pvaprim, Ent8 * pvanulable = 0 ) const; // CalcPrimeroSinLambda calcula Primero() para todos los símbolos no // terminales de la gramática, pero sin agregar lambda a ninguno de los // conjuntos. pvaprim es un puntero a un vector de alfabetos cuyo tamaño // es el número de símbolos no terminales de la gramática actual, cada // elemento del vector contiene Primero() de cada símbolo respectivamente, // por orden de aparición de los símbolos en NoTerm. Ent16ns CalcSiguiente( Alfabeto * pvasig, Alfabeto * pvaprim = 0, 306

307 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x Ent8 * pvanulable = 0 ) const; // CalcSiguiente() calcula Siguiente() para todos los no terminales de la // gramática. pvasig y pvaprim apuntan cada uno a un vector de Alfabetos // cuyo tamaño es el número de simb. no terminales de la gramática actual // (NumNoTerm()). En pvasig se guardan los conjuntos siguientes y en // pvaprim, si no es nulo, se guardan los conjuntos primero. Ent8 ImprAccSem( Archivo & arch, const char * sufijo = 0 ) const; // Imprime en formato C++ las acciones semánticas especificadas para esta // gramática. Ent8 ImprAnaLex( Archivo & arch, const char * sufijo = 0 ) const; // Imprime un analizador lexicográfico en formato C++ para la gramática, // implementado con un objeto de la clase AnaLex (de expreg.h). Ent16 ImprAnulPrimSig( Archivo & arch ); void Vaciar(); ; inline Ent8 Gramatica::AgregRegla( const ReglaLC & regla ) { return Reglas.AgregFinal(regla); inline const Simbolo * Gramatica::SimbInic() const { return Reglas.NumElem()? Reglas.Entrada->Dato.ParteIzq() : 0; inline void Gramatica::Vaciar() { Term.Vaciar(); NoTerm.Vaciar(); Reglas.Vaciar(); BlqCodInic.Vaciar(); TablaAccSem.Vaciar(); TablaTerminal.Vaciar(); MayIgualMin = 0; // SimbFA es el nombre lógico del símbolo que representa al fin de archivo extern Simbolo SimbFA; // ("FA", 0, 0) tiene código 0 y es terminal Archivo & operator << ( Archivo & arch, const Gramatica & gram ); //ArchivoSO & operator << ( ArchivoSO & arch, const Gramatica & gram ); //ArchivoSO & operator >> ( ArchivoSO & arch, Gramatica & gram ); # endif // # ifndef GRAMLC_H 19.2 SLR1.H El código fuente de este módulo se presenta en la sección GENSLR.H # ifndef GENSLR_H # define GENSLR_H 307

308 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x // genslr.h: clases para el programa generador de tablas de análisis // sintáctico SLR(1). // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # ifndef GRAMLC_H # include "gramlc.h" # endif class Archivo; // declarada en archabst.h ( de la biblioteca de clases BCE) class ElemCanonLR0 { // elemento del análisis sintáctico LR(0) public: const NodoLSE<ReglaLC> * pr; // puntero a la regla const NodoLSE<Simbolo> * punto; // punto en la parte derecha de la regla ElemCanonLR0() { pr = 0; punto = 0; ElemCanonLR0( const NodoLSE<ReglaLC> * Pr, const NodoLSE<Simbolo> * ps ); Ent8 operator == ( const ElemCanonLR0 & oe ) const; friend Archivo & operator << ( Archivo & a, const ElemCanonLR0 & eclr0 ); ; inline ElemCanonLR0::ElemCanonLR0( const NodoLSE<ReglaLC> * Pr, const NodoLSE<Simbolo> * ps ) { pr = Pr; punto = ps; inline Ent8 ElemCanonLR0::operator == ( const ElemCanonLR0 & oe ) const { return pr == oe.pr && punto == oe.punto; Archivo & operator << ( Archivo & a, const ElemCanonLR0 & eclr0 ); class ConjElem { // Conjunto de elementos LR(0) public: LSE<ElemCanonLR0> lse; // conjunto de elementos Ent16ns numelem; ConjElem() { numelem = 0; ~ConjElem() { lse.vaciar(); numelem = 0; Ent8 Agregar( const NodoLSE<ReglaLC> * Pr, const NodoLSE<Simbolo> * ps ); Ent16ns AgregCerradura( const Gramatica & g ); void Vaciar() { lse.vaciar(); numelem = 0; friend Archivo & operator << ( Archivo & a, const ConjElem & ce ); Ent8 operator == ( const ConjElem & ce ) const; ; Archivo & operator << ( Archivo & a, const ConjElem & ce ); class Estado { // un estado es un conjunto de elementos LR(0) public: ConjElem * pce; // conjunto de elementos LR(0) (estado) Ent16ns n; // número de estado (o de conj. de elementos) 308

309 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x Ent16ns * ves; // vector de estados siguientes Estado() { pce = 0; n = 0; ves = 0; Estado( ConjElem * Pce, Ent16ns N ) { pce = Pce; n = N; ves = 0; ; class ConjEst { // conjunto de estados public: LSE<Estado> lse; // Conjunto de estados Ent16ns numest; // número de estados ConjEst() { numest = 0; ~ConjEst() { Vaciar(); Ent8 Armar( Gramatica & g, Ent8 (* FuncError) ( Ent16ns estado, Ent16ns val_previo, Ent16ns num_regla, const Simbolo & s ) = 0 ); void Vaciar() { lse.vaciar(); numest = 0; Ent8 ImprTabla( Archivo & a, const Gramatica & g ) const; Ent8 ImprTabla( const char * NomArch, const Gramatica & g ) const; Ent8 ImprTablaC( const char * NomArch, const Gramatica & g, const char * sufijo = 0 ) const; friend Archivo & operator << ( Archivo & a, const ConjEst & ce ); ; # endif // # ifndef GENSLR_H 19.4 SIMB01.CMM // simb01.cmm Miércoles 20 de Octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include "archabst.h" static Ent8 b = Simbolo::CAD; Ent8 Simbolo::Mostrar( Ent8 que ) { if (que > CAD_COD_TER) return 0; b = que; return 1; Archivo & operator << ( Archivo & a, const Simbolo & s ) { if (b!= Simbolo::CAD) a << "("; a << (const char *) s.cadena(); if (b == Simbolo::CAD_COD b == Simbolo::CAD_COD_TER) a << ", " << s.codigo; if (b == Simbolo::CAD_TER b == Simbolo::CAD_COD_TER) a << ", " << (s.noterminal? 'N' : 'T'); if (b!= Simbolo::CAD) a << ")"; return a; 309

310 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x 19.5 SIMB02.CMM // simb02.cmm viernes 22 de octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Simbolo SimbFA("FA",0,0); 19.6 SIMB03.CMM // simb03.cmm Domingo 11 de diciembre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include "archivo.h" ArchivoSO & operator << ( ArchivoSO & a, const Simbolo & s ) { register Ent8 bin = a.txtbin(); if (bin) { a << s.cadena(); a << s.codigo; a << s.noterminal; else (Archivo &) a << s; return a; ArchivoSO & operator >> ( ArchivoSO & a, Simbolo & s ) { register Ent8 bin = a.txtbin(); Ascii0 cad; Ent16ns cod; Ent8ns noterm; if (bin) { a >> cad; a >> cod; a >> noterm; s.reasignar(cad, cod, noterm); return a; 19.7 SIMB04.CMM // simb04.cmm // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Simbolo::~Simbolo() { 310

311 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x ReasignarCadena(0); Ascii0 Simbolo::ErrorCadenaNula; 19.8 CSLSE01.CMM // cslse01.cmm Viernes 22 de Octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" const Simbolo * ConjSimbLSE::Buscar( const char * Cadena ) const { IterLSE<Simbolo> c; // register const Ascii0 * pa; for (c = lse; c!= 0; ++c) { // pa = & c.cursor->dato.cadena(); if (c.cursor->dato == Cadena) break; return c!= 0? & c.cursor->dato : 0; 19.9 CSLSE02.CMM // cslse02.cmm Viernes 22 de Octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" const Simbolo * ConjSimbLSE::Buscar( Ent16ns CodComp ) const { IterLSE<Simbolo> c; for (c = lse; c!= 0; ++c) if (c.cursor->dato == CodComp) break; return c!= 0? & c.cursor->dato : 0; CSLSE03.CMM // cslse03.cmm Miércoles 20 de Octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include "archabst.h" Archivo & operator << ( Archivo & a, const ConjSimbLSE & cs ) { IterLSE<Simbolo> c = cs.lse; register Ent8 coma = 0; register Simbolo * ps; 311

312 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x Ent16ns n; a << "{ "; n = 0; while (c!= 0) { if (coma) a << ", "; else coma = 1; a << c.cursor->dato; ++c; if (++n == 5) { a << "\n "; n = 0; return a << " "; CSLSE04.CMM // cslse04.cmm Viernes 22 de octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Ent16ns ConjSimbLSE::Agregar( const ConjSimbLSE & cs, Ent8 ar ) { if (! ar && this == & cs) return 0; IterLSE<Simbolo> c; register Ent16ns i; for (c = cs.lse, i = 0; c!= 0; ++c) if (Agregar(c.Cursor->Dato, ar)) ++i; return i; RLC01.CMM // rlc01.cmm Vie 27 Ene 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include <archivo.h> Archivo & operator << ( Archivo & a, const ReglaLC & r ) { register const NodoLSE<Simbolo> * pns; Ent8 b; if ( (pns = r.lse.entrada)!= 0 ) { b = Simbolo::Mostrar(); a << pns->dato << " ---> "; while (pns->psig!= 0) { pns = pns->psig; a << pns->dato << " "; Simbolo::Mostrar(b); 312

313 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x return a; GRAM01.CMM // gram01.cmm Viernes 20 de octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Ent16ns Gramatica::ArmarAlfabetos() { register Ent16ns cod; register const Simbolo * psimbenalf; register NodoLSE<ReglaLC> * pregla; NodoLSE<Simbolo> * pnsimbenregla; register Simbolo * psimbenregla; Ent16ns cant_simb; // Armar alfabeto no terminal asignando los códigos en el mismo. // A los símbolos de la izq. de la regla también se le asignan los códigos. pregla = Reglas.Entrada; cod = 1; while (pregla!= 0) { psimbenregla = & pregla->dato.lse.entrada->dato; if ( (psimbenalf= NoTerm.Buscar(pSimbEnRegla->Cadena())) == 0) { // El símbolo no existe en el alfabeto de símbolos no terminales. // En el símbolo que reside en la regla se asigna el código psimbenregla->codigo = cod++; psimbenregla->noterminal = 1; // no terminal // Se agrega al alfabeto NoTerm.Agregar(*pSimbEnRegla); else { // El símbolo está en el alfabeto no terminal, // asignar código ya asignado al símbolo de la regla psimbenregla->codigo = psimbenalf->codigo; psimbenregla->noterminal = 1; // no terminal (no hay dudas) pregla = pregla->psig; // Avanzar al sig. elemento de la lista. cant_simb = cod - 1; // Luego armar alfabeto terminal y asignar los códigos. // Si algún símbolo de la parte derecha de las reglas existe en algún // alfabeto entonces se copia el código asignado. pregla = Reglas.Entrada; cod = 1; // el 0 se reserva para "fin de archivo" while (pregla!= 0) { // Para todas las reglas ya se ejecutó la función Primero() pnsimbenregla = pregla->dato.lse.entrada->psig; while ( pnsimbenregla!= 0 ) { psimbenregla = & pnsimbenregla->dato; if ( (psimbenalf = NoTerm.Buscar(pSimbEnRegla->Cadena())) == 0 && (psimbenalf = Term.Buscar(pSimbEnRegla->Cadena())) == 0 ) { // No está en ninguno de los alfabetos // En el símbolo que reside en la regla se asigna el código psimbenregla->codigo = cod++; psimbenregla->noterminal = 0; // Se agrega al alfabeto Term.Agregar(*pSimbEnRegla); 313

314 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x else { // El símbolo ya está en alguno de los alfabetos y // psimbenalf apunta a él psimbenregla->codigo = psimbenalf->codigo; psimbenregla->noterminal = psimbenalf->noterminal; pnsimbenregla = pnsimbenregla->psig; pregla = pregla->psig; return cant_simb + cod - 1; GRAM02.CMM // gram02.cmm Viernes 22 de octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Ent8 Gramatica::AnulableSimple( Ent16ns CodSimb ) const { if (! Reglas.NumElem()! (CodSimb & 0x8000)) return 0; IterLSE<ReglaLC> i; register const NodoLSE<Simbolo> * pr; CodSimb &= 0x7fff; for (i = Reglas; i!= 0; ++i) { pr = i.cursor->dato.lse.entrada; if (pr->dato.codigo == CodSimb && pr->psig == 0) break; return i!= 0; // si encuentra una regla 'CodSimb --> ' CodSimb es anulable Ent8 Gramatica::Anulable( Ent8 * vanul ) const { if (! Reglas.NumElem() vanul == 0) return 0; Ent8 iterar; Ent16ns i, CodNTAct; NodoLSE<ReglaLC> * pr; NodoLSE<Simbolo> * ps; for (ps = NoTerm.lse.Entrada; ps!= 0; ps = ps->psig) { i = ps->dato.codigo - 1; vanul[i] = AnulableSimple( ps->dato.codigocompuesto() ); do { // el proceso es iterativo hasta que no se cambia ninguna bandera iterar = 0; for (pr = Reglas.Entrada; pr!= 0; pr = pr->psig) { CodNTAct = pr->dato.lse.entrada->dato.codigo; for (ps = pr->dato.lse.entrada->psig; ps!= 0; ps = ps->psig) if (! ps->dato.noterminal! vanul[ ps->dato.codigo - 1]) break; // salir si alguno de la derecha no es anulable if (ps == 0) { // todos son no terminales y anulables if (! vanul[codntact - 1]) { vanul[codntact - 1] = 1; if (! iterar) iterar = 1; // se cambió al menos un elemento // de vanul 314

315 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x while (iterar); return 1; Ent16ns Gramatica::CalcPrimeroSinLambda( Alfabeto * prim, Ent8 * vanul ) const { // prim debe ser un vector de NoTerm.NumElem() elementos. if (! Reglas.NumElem() prim == 0) return 0; Ent8 * Anul; Anul = vanul!= 0? vanul : new Ent8[ NumNoTerm() ]; if (Anul == 0) return 0; if (vanul == 0) Anulable(Anul); IterLSE<Simbolo> isa, isr; // isa: iterador de símbolos en alfabeto // isr: iterador de símbolos en regla IterLSE<ReglaLC> ir; // iterador de reglas (para el conjunto de reglas) const register Simbolo * psa, * psr; // psa: puntero a un símbolo en alfabeto // psr: puntero a un símbolo en regla Ent8 agreg, iterar = 1; Ent16ns i, numpas = 0; // agreg: == 0 si agregó algún símbolo,!= 0 si no agregó // iterar: == 0 si se debe salir. // numpas: contabiliza el número de pasadas (el cálculo de PRIMERO() es // iterativo). while (iterar) { // se itera si se agrega algún símbolo, // se para cuando en una iteración no se agregó ninguno iterar = 0; for (isa = NoTerm.lse, i = 0; isa!= 0; ++isa, ++i) { // para cada símbolo // no terminal de la gramática psa = & isa.cursor->dato; for (ir = Reglas; ir!= 0; ++ir) { // para cada regla del conj de reglas lambda // si la regla es 'A --> Y' con A símbolo actual y Y' distinto a if (ir.cursor->dato.parteizq()->codigo == psa->codigo && (isr = ir.cursor->dato.lse, ++isr)!= 0) { // isr apunta al primer símbolo de la parte derecha de la regla terminal) iterar if (isr.cursor->dato.noterminal) { // Símbolo no terminal Ent8 cont = 1; // cont: continuar while (cont) { psr = & isr.cursor->dato; // para A --> B C agrega Primero(B) a Primero(A) (B es no agreg = prim[i].agregar( prim[psr->codigo - 1] ); if (! iterar) iterar = agreg; // si se agregó se vuelve a anulable se // Si el símbolo actual de la derecha (B en el ejemplo) es // avanza al siguiente (C en el ejemplo) para agregar // Primero(símbolo siguiente) a Primero(símbolo actual) // ( Primero(C) en el ejemplo ) y se continua iterando if ( Anul[ psr->codigo - 1 ] ) { if (++isr == 0) cont = 0; else if (! isr.cursor->dato.noterminal) { agreg = prim[i].agregarsincad(isr.cursor->dato); if (! iterar) iterar = agreg; cont = 0; // símbolo terminal, fin del while (cont) 315

316 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x else cont = 0; // símbolo no anulable, fin del while (cont) // while (cont) else { // Es un símbolo terminal if (! numpas) { // si es la primera pasada agreg = prim[i].agregarsincad(isr.cursor->dato); if (! iterar) iterar = agreg; // segundo for // primer for ++numpas; if (vanul == 0) delete [] Anul; return numpas; GRAM03.CMM // gram03.cmm viernes 22 de octubre de 1993 // Copyright (c) 1993 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" Ent16ns Gramatica::CalcSiguiente( Alfabeto * sig, Alfabeto * primero, Ent8 * vanul ) const { if (! Reglas.NumElem() sig == 0) return 0; Ent16ns nt; if (! (nt = NoTerm.NumSimb()) ) return 0; // siguiente de quién? // Primero se calcula el vector de banderas Anul (anulable) Ent8 * Anul; Anul = vanul!= 0? vanul : new Ent8[nt]; if (Anul == 0) return 0; if (vanul == 0) Anulable(Anul); // calcular sólo si no viene dado // Luego se calculan los conjuntos primero Alfabeto * prim = primero!= 0? primero : new Alfabeto[nt]; if (prim == 0) { if (vanul == 0) delete [] Anul; return 0; // falta memoria if (! CalcPrimeroSinLambda(prim, Anul)) { if (primero == 0) delete [] prim; if (vanul == 0) delete [] Anul; return 0; IterLSE<ReglaLC> ir; ReglaLC * pr; Ent8 iterar = 1, agreg; Ent16ns numpas = 0; while (iterar) { 316

317 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x del iterar = 0; // se agrega FA a Siguiente(O) donde O es el símbolo inicial if (! numpas) iterar = sig[0].agregar(simbfa); for (ir = Reglas; ir!= 0; ++ir) { // En adelante, el código es dependiente de las estructuras de datos // que se usan. if ( ir.cursor->dato.simboloi(1)!= 0 ) { // si la regla no deriva en lambda ('A -> ') register NodoLSE<Simbolo> * ps1, * ps2; ps1 = ir.cursor->dato.lse.entrada->psig; while (ps1!= 0) { if (ps1->dato.noterminal) { // si A --> B Y con B no terminal if (ps1->psig == 0) { // si Y de la regla de arriba era lambda Ent16ns cod = ir.cursor->dato.lse.entrada->dato.codigo; // // símbolo de la izquierda de la regla if (cod) { agreg = sig[ps1->dato.codigo - 1].Agregar( sig[cod - 1] ); if (! iterar) iterar = agreg; else { ps2 = ps1->psig; // Se agrega Primero de los no terminales que siguen hasta que // haya un símbolo terminal o un no terminal no anulable. while (ps2!= 0) { if (ps2->dato.noterminal) { agreg = sig[ps1->dato.codigo - 1].Agregar( prim[ps2->dato.codigo - 1] ); if (! iterar) iterar = agreg; if ( Anul[ps2->Dato.Codigo - 1] ) { ps2 = ps2->psig; if (ps2 == 0) { // todos los de la derecha son anulables // Agregar siguiente del de la izquierda de la regla actual agreg = sig[ps1->dato.codigo - 1].Agregar( sig[ir.cursor->dato.lse.entrada->dato.codigo - 1] ); if (! iterar) iterar = agreg; else ps2 = 0; // para salir del while else { // es terminal if (! numpas) { // si estamos en la primera pasada agreg = sig[ps1->dato.codigo - 1].AgregarSinCad( ps2->dato ); if (! iterar) iterar = agreg; ps2 = 0; // se llegó a un terminal, salir del while // while // else // if (ps1->dato.noterminal) ps1 = ps1->psig; // avanzar al siguiente símbolo de la regla // while // if la regla no deriva en lambda // Fin del código dependiente de las estr. de datos que se usan. // for 317

318 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x ++numpas; // while (iterar) if (primero == 0) delete[] prim; if (vanul == 0) delete [] Anul; return numpas; GRAM04.CMM // gram04.cmm Lun 30 Ene 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "gram.cmm" // generado por SLR1 v 2, son las acciones semánticas Ent16ns Gramatica::CodError; Ent8 Gramatica::Reasignar( const char * TxtGram, Ascii0 & CadPosErr ) { if (TxtGram == 0! *TxtGram) return 0; Vaciar(); InicVarGlob(); pgram = this; ClaseElemPila = "ElemPila"; // AnalSLR1<ElemPila> analizador(ntlpdgram, acciongram, ir_agram, AccSemGram); algram.reiniciar(txtgram); if (! asgram.analizar(algram, CadPosErr)) { CodError = asgram.coderror; return 0; InicVarGlob(); return 1; GRAM05.CMM // gram05.cmm Vie 27 Ene 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include <archivo.h> Archivo & operator << ( Archivo & arch, const Gramatica & gram ) { if (! gram.numnoterm()) return arch; arch << "NoTerm = " << gram.noterm << "\nterm = " << gram.term << "\ngramática:\n"; IterLSE<ReglaLC> i; for (i = gram.reglas; i!= 0; ++i) arch << i.cursor->dato << "\n"; return arch; 318

319 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x GRAM06.CMM // gram06.cmm Lun 30 Ene 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "gramlc.h" # include <archivo.h> # include <string.h> void ImprBlqAccSem( Archivo & arch, const char * pblqcod ) { register const char * p; if ((p = pblqcod) == 0) return; ++p; while (*p && *p!= '%') { if (*p == '$') { arch << "Pila[PP+"; ++p; if (*p == '$') { arch << "1"; ++p; else while ( Aceptar(*p, "0-9") ) arch << *p++; arch << "]"; else arch << *p++; arch << " break;\n"; Ent8 Gramatica::ImprAccSem( Archivo & arch, const char * sufijo ) const { int est; est = arch.estado(); if (est!= MA_E && est!= MA_L_E! Reglas.NumElem() ) return 0; arch << "\n// Código de acciones semánticas.\n" "// Código generado por Gramatica::ImprAccSem versión 2.\n" "// Domingo Eduardo Becker.\n\n"; // La siguiente línea es necesario para compilar arch << "# ifndef SLR1_H\n# include \"slr1.h\"\n# endif\n\n"; if (sufijo == 0) sufijo = ""; // Impresión del código previo a las acciones semánticas unsigned n; if ((n = BlqCodInic.Long())!= 0) { char * p; p = (char *) (const char *) BlqCodInic; p += n - 2; *p = *(p+1) = ' '; p = (char *) (const char *) BlqCodInic; p += 2; // saltea los primeros %{ arch << "\n// Código fuente previo a las acciones semánticas.\n" << p << "\n// Fin del código fuente previo a las acciones semánticas.\n\n"; // Impresión de las acciones semánticas. arch << "void AccSem" << sufijo << "( Ent16ns NumRegla, " 319

320 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x << (const char *) ClaseElemPila << " * Pila, Ent16ns PP ) {\n switch (NumRegla) { "; IterLSE<ReglaLC> ir; IterLSE<DescrAccSem> ias; register const char * pas, * pident; register Ent16ns numregla; Ent8 imprimir; Ascii0 cad; // Primero las acciones semánticas no repetidas for (ir = Reglas, numregla = 1; ir!= 0; ++ir, ++numregla) { pas = ir.cursor->dato.accsem; if (pas == 0 *pas!= '%') continue; cad = numregla; arch << "\n case " << (char *) cad << ": // " << ir.cursor->dato << "\n "; ImprBlqAccSem(arch, pas); // Luego las acciones semánticas comunes a una o más reglas: for (ias = TablaAccSem; ias!= 0; ++ias) { imprimir = 0; pident = ias.cursor->dato.ident; for (ir = Reglas, numregla = 1; ir!= 0; ++ir, ++numregla) { pas = ir.cursor->dato.accsem; if (pas == 0 *pas == '%') continue; if (! strcmp(pas, pident)) { imprimir = 1; cad = numregla; arch << "\n case " << (char *) cad << ": // " << ir.cursor->dato; if (imprimir) { arch << "\n "; ImprBlqAccSem(arch, ias.cursor->dato.txtaccsem); else arch << "// Acción semántica referenciada por " << pident << " ignorada.\n"; arch << " // switch (NumRegla)\n // Fin de AccSem" << sufijo << "\n\n// Fin del código de acciones semánticas.\n"; return 1; Ent8 Gramatica::ImprAnaLex( Archivo & arch, const char * sufijo ) const { int est; est = arch.estado(); if (est!= MA_E && est!= MA_L_E! Term.NumSimb()) return 0; arch << "// Analizador lexicográfico para la gramática.\n" "// Generado por Gramatica::ImprAnaLex versión 3.\n" "// Domingo Eduardo Becker.\n\n"; // La siguiente línea es necesario para compilar arch << "# ifndef EXPREG_H\n# include \"expreg.h\"\n# endif\n"; if (sufijo == 0) sufijo = ""; IterLSE<DescrTerminal> it; IterLSE<Simbolo> is; register const Simbolo * ps; 320

321 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x register const char * nomarch; register char * cad; char aux, primero; Ent16ns numrs; Ascii0 NumCad; // Primero imprimir los includes for (it = TablaTerminal; it!= 0; ++it) { nomarch = it.cursor->dato.nomarch.long()? it.cursor->dato.nomarch : it.cursor->dato.cadena; arch << "\n# include \"" << NomExt(nomarch, ".cmm") << "\" // afd" << (const char *) it.cursor->dato.cadena; // Después imprimir el vector de reconocedores de símbolos: arch << "\n\nstatic RecSimb vrs[] = {"; numrs = 0; for (is = Term.lse, primero = 1; is!= 0; ++is) { if (primero) { arch << "\n "; primero = 0; else arch << ",\n "; NumCad = is.cursor->dato.codigo; arch << (char *) NumCad << ", "; cad = (char *) (const char *) is.cursor->dato.cadena(); if (*cad == '\'' *cad == '\"') { // es una cadena (literal) aux = *cad; *cad = *(cad + strlen(cad) - 1) = '\"'; arch << "0, 0, " << cad; *cad = *(cad + strlen(cad) - 1) = aux; else { for (it = TablaTerminal; it!= 0; ++it) if (! strcmp(it.cursor->dato.cadena, cad)) break; // terminal encontrado. if (it!= 0) // terminal descripto por una expresión regular. arch << "1, & afd" << cad << ", 0"; else arch << "0, 0, \"" << cad << "\" /* Debe armar un descriptor" " adecuado para este terminal. */"; ++numrs; // A continuación se imprimen los reconocedores para los símbolos // que serán ignorados por el analizador lexicográfico: for (it = TablaTerminal; it!= 0; ++it) { ps = Term.Buscar( cad = (char *) (const char *) it.cursor->dato.cadena ); if (ps!= 0) continue; if (primero) { arch << "\n "; primero = 0; else arch << ",\n "; arch << "0, 1, & afd" << cad << ", 0"; ++numrs; 321

322 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x NumCad = numrs; arch << "\n;\n\n# define NUMRECSIMB " << (char *) NumCad << "\n\nanalex al" << sufijo << "(vrs, NUMRECSIMB"; if (MayIgualMin) arch << ", 1"; arch << ");\n"; return 1; GRAM07.CMM // gram07.cmm Vie 02 Feb 96 // Copyright (c) 1996 by Domingo Eduardo Becker. // All rights reserved. # include <vector.h> # include <archivo.h> # include "gramlc.h" Ent16 Gramatica::ImprAnulPrimSig( Archivo & arch ) { Ent16ns nnoterm = NumNoTerm(); Vector<Alfabeto> Prim(nNoTerm), Sig(nNoTerm); Vector<Ent8> Anul(nNoTerm); if (! Sig.Tam! Prim.Tam! Anul.Tam) return CodError = E_NOHAYMEMORIA; Anulable(Anul); CalcSiguiente(Sig, Prim, Anul); Ent8 b = Simbolo::Mostrar(Simbolo::CAD); // Impresión de Anulable IterLSE<Simbolo> nt; Ent16ns i; for (nt = NoTerm.lse, i = 0; nt!= 0; ++nt, ++i) arch << "Anulable(" << nt.cursor->dato << "): " << (Anul[i]? "Si" : "No") << "\n"; // Impresión de Primero IterLSE<Simbolo> e; const Simbolo * ps; for (nt = NoTerm.lse, i = 0; nt!= 0; ++nt, ++i) { arch << "Primero(" << nt.cursor->dato << ") = {"; for (e = Prim[i].lse; e!= 0; ++e) { ps = Term.Buscar(e.Cursor->Dato.CodigoCompuesto()); if (ps!= 0) arch << " " << *ps; arch << " \n"; // Impresión de Siguiente: for (nt = NoTerm.lse, i = 0; nt!= 0; ++nt, ++i) { arch << "Siguiente(" << nt.cursor->dato << ") = {"; for (e = Sig[i].lse; e!= 0; ++e) { ps = Term.Buscar(e.Cursor->Dato.CodigoCompuesto()); if (ps!= 0) 322

323 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x arch << " " << *ps; arch << " \n"; Simbolo::Mostrar(b); return CodError; CELEM01.CMM // celem01.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" Ent8 ConjElem::Agregar( const NodoLSE<ReglaLC> * Pr, const NodoLSE<Simbolo> * ps ) { register NodoLSE<ElemCanonLR0> * p; register Ent8 b; p = lse.entrada; b = 1; // por si lse está vacía if (p!= 0) { while ( (b = p->dato.pr!= Pr p->dato.punto!= ps)!= 0 && p->psig!= 0) p = p->psig; // Si no se repite el nodo y se pueda avanzar se avanza. // Al salir p apuntará al último nodo si el elemento no existe. if (b) { NodoLSE<ElemCanonLR0> * nn = new NodoLSE<ElemCanonLR0>; if (nn == 0) return 0; nn->dato.pr = Pr; nn->dato.punto = ps; if (p!= 0) p->psig = nn; else lse.entrada = nn; ++numelem; // se agregó un Elemento Canónico LR0 return b; // b!= 0 si agrega CELEM02.CMM // celem02.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" Ent16ns ConjElem::AgregCerradura( const Gramatica & g ) { register NodoLSE<ElemCanonLR0> * q; register const NodoLSE<ReglaLC> * pr; Ent16ns n = 0; q = lse.entrada; while (q!= 0) { 323

324 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x if (q->dato.punto!= 0 && // si el punto no está al final de la parte derecha de la regla y q->dato.punto->dato.noterminal ) { // el símbolo apuntado es un no terminal pr = g.reglas.entrada; while (pr!= 0) { // Se busca en la gramática alguna regla que empiece con tal símbolo register const NodoLSE<Simbolo> * pns; // n de nodo const Simbolo * ps; pns = pr->dato.lse.entrada; ps = & q->dato.punto->dato; if (ps->codigocompuesto() == pns->dato.codigocompuesto() ) { // Si la regla empieza con el símbolo en cuestión if ( Agregar(pr, pns->psig) ) // dir. de la regla y posición del punto ++n; // se acaba de agregar un elemento (si Agregar() no falló) pr = pr->psig; // avanzar a la siguiente regla q = q->psig; // avanzar al siguiente elemento (nodo de la lista) return n; CELEM03.CMM // celem03.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" Ent8 ConjElem::operator == ( const ConjElem & ce ) const { if (numelem!= ce.numelem) return 0; register NodoLSE<ElemCanonLR0> * q1, * q2; Ent8 b; q1 = lse.entrada; while (q1!= 0) { // buscar si los elementos de *this están en ce q2 = ce.lse.entrada; while (q2!= 0) { if (q1->dato == q2->dato) break; q2 = q2->psig; if (q2 == 0) break; // elemento q1->dato no encontrado en ce q1 = q1->psig; return q1 == 0; // son iguales si q1 vale CELEM04.CMM // celem04.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" 324

325 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x # include <archabst.h> Archivo & operator << ( Archivo & a, const ElemCanonLR0 & eclr0 ) { register const NodoLSE<Simbolo> * pns; if ( (pns = eclr0.pr->dato.lse.entrada)!= 0 ) { a << (const char *) pns->dato.cadena() << " ---> "; while (pns->psig!= 0) { pns = pns->psig; if (eclr0.punto == pns) a << " "; // caracter 249 a << (const char *) pns->dato.cadena() << " "; if (eclr0.punto == 0) a << " "; return a; Archivo & operator << ( Archivo & a, const ConjElem & ce ) { register const NodoLSE<ElemCanonLR0> * q = ce.lse.entrada; while (q!= 0) { a << q->dato << "\n"; if (CodError) break; // si hay error se sale. q = q->psig; return a; CEST01.CMM // cest01.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include <string.h> # include "genslr.h" static void limpiar( Ent16ns * v, register Ent16ns tam ) { if (v == 0) return; register Ent16ns * p = v; while (tam--) *p++ = 0; Ent8 ConjEst::Armar( Gramatica & g, Ent8 (* funcerror) ( Ent16ns e, Ent16ns vp, Ent16ns nr, const Simbolo & s) ) { if (g.reglas.entrada == 0) return 0; if (numest) Vaciar(); // Calcular los conjuntos Siguiente de cada no terminal. Ent16ns n = g.numnoterm(); Alfabeto * sig = new Alfabeto[n]; if (sig == 0) return 0; g.calcsiguiente(sig); // calcula los conj. Siguiente para todos los no term. NodoLSE<ReglaLC> * rn; const Simbolo * ps; char * cad1; // Buscar el símbolo inicial y generar el nuevo inicial. ps = & g.reglas.entrada->dato.lse.entrada->dato; 325

326 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x if (ps!= 0) { // ps apunta al símbolo inicial Ent16ns lon = ps->cadena().long() + 2; cad1 = new char [lon]; if (cad1 == 0) return 0; strncpy(cad1, ps->cadena(), lon); cad1[lon-2] = '\''; // le agrega la ' cad1[lon-1] = 0; // terminación en 0 else return 0; // Una vez individualizado el símbolo inicial, se crea una regla que // aumentará la gramática. Si O es el símbolo, la regla a agregar será: // O' ---> O rn = new NodoLSE<ReglaLC>; if (rn == 0) return 0; rn->dato.agregar(cad1, 0, 1); // símbolo parte izq de la regla (O', cod 0, no terminal) rn->dato.agregar(*ps); rn->psig = g.reglas.entrada; // se engancha la regla nueva al comienzo del g.reglas.entrada = rn; // conjunto de reglas de la gramática // devolver memoria usada por cad1. Ya no se usará más. delete cad1; // Hasta ahora tenemos la gramática aumentada. Hay que generar el estado 0 // e iterar armando los estados siguientes (función ir_a(estado, símbolo)) // para cada estado y para todos los símbolos de la gramática, hasta que no // se generen nuevos estados. (Recordar que un estado es un conjunto de // elementos canónicos LR(0). ) ConjElem * estn, *esta; // estado nuevo, estado actual estn = new ConjElem; if (estn == 0) return 0; estn->agregar( rn, rn->dato.lse.entrada->psig); // se agrega O' --> O al estado 0 estn->agregcerradura(g); // se agrega la cerradura del conjunto { O' --> O NodoLSE<Estado> * pestn, * pesta, // puntero al estado nuevo, punt. al est. actual * pestaux; // puntero auxiliar pestn = new NodoLSE<Estado>; // crea nodo del estado 0 if (pestn == 0) return 0; pestn->dato.pce = estn; pestn->dato.n = 0; Ent16ns numsimbg = g.numnoterm() + g.numterm(); pestn->dato.ves = new Ent16ns [numsimbg + 1]; if (pestn->dato.ves == 0) return 0; limpiar(pestn->dato.ves, numsimbg + 1); // ves[numsimbg] se reserva para reducciones y aceptar con FA (fin de // archivo) en ves[0] a ves[numsimbg-1] se colocan los valores de // ir_a(est, simb) para cada símbolo de la gramática, primero los // terminales y luego los no terminales. lse.entrada = pesta = pestn; // pone el estado 0 al comienzo de la lista de estados NodoLSE<ElemCanonLR0> * pelem; NodoLSE<Simbolo> * psimb; Ent16ns * pves; // para los vectores de estados siguientes (función ir_a() ) 326

327 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x numest = 1; // campo numest del nodo actual while (pesta!= 0) { // para cada estado (conj. de elementos) // El vector de estados siguientes (ves) tiene m + 1 entradas con // m = g.numterm() + g.numnoterm(). Las primeras g.numterm() entradas // corresponden a la operación desplazar para cada terminal en el estado // actual, en particular la entrada ves[m] (elemento m+1) corresponde al // símb FA, y no tiene operación de desplazamiento (sólo reducción ó // aceptar). Las g.numnoterm() entradas que están a partir del elemento // ves[g.numterm()] corresponden a la operación ir_a para cada no // terminal desde el estado actual. pves = pesta->dato.ves; // A continuación se procede a construir las operaciones desplazar // para el estado actual. psimb = g.term.lse.entrada; while (psimb!= 0) { // para cada símbolo terminal estn = new ConjElem; if (estn == 0) return 0; pelem = pesta->dato.pce->lse.entrada; while (pelem!= 0) { // para cada elemento del estado actual if (pelem->dato.punto!= 0 && pelem->dato.punto->dato.codigocompuesto() == psimb->dato.codigocompuesto()) { // si el está antes del // terminal actual agregar elemento al conj. de est. nuevo estn->agregar( pelem->dato.pr, pelem->dato.punto->psig); // si era A --> a A agrega A --> a A // la regla es la misma y el punto se corre al siguiente símbolo pelem = pelem->psig; // Avanzar al siguiente elemento del estado actual if (! estn->numelem) delete estn; // estado nuevo no usado, no se agregaron elementos else { // se agregaron elementos estn->agregcerradura(g); // agregar la cerradura del conjunto pestn = new NodoLSE<Estado>; if (pestn == 0) return 0; pestn->dato.pce = estn; pestn->dato.n = numest; pestn->dato.ves = new Ent16ns[numsimbG + 1]; if (pestn->dato.ves == 0) return 0; limpiar(pestn->dato.ves, numsimbg + 1); pestaux = lse.entrada; while (pestaux!= 0) { // buscar si el estado nuevo no estaba, si es así agregarlo al final if ( *(pestaux->dato.pce) == *estn) break; // salir del while, estado repetido else if (pestaux->psig == 0) { // pestaux apunta al último nodo, que tiene un conj. de elem. distinto al nuevo pestaux->psig = pestn; // agregar al final *pves = numest++; // del actual se transiciona al estado nuevo con el símbolo terminal actual pestaux = 0; // para salir del while else pestaux = pestaux->psig; if (pestaux!= 0) { // si el nodo ya existía delete pestn->dato.ves; delete pestn; delete estn; *pves = pestaux->dato.n; 327

328 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x ++pves; // *pves vale 0 por defecto, o bien puede valer numest psimb = psimb->psig; // avanzar al siguiente símbolo terminal // while que itera en el alfabeto terminal // Luego se construyen las operaciones ir_a para el estado actual. psimb = g.noterm.lse.entrada; while (psimb!= 0) { // para cada símbolo no terminal estn = new ConjElem; if (estn == 0) return 0; pelem = pesta->dato.pce->lse.entrada; while (pelem!= 0) { // para cada elemento del estado actual if (pelem->dato.punto!= 0 && pelem->dato.punto->dato.codigocompuesto() == psimb->dato.codigocompuesto() ) { // agregar elemento al conj. de est. nuevo estn->agregar( pelem->dato.pr, pelem->dato.punto->psig); // si era A --> a A agrega A --> a A // la regla es la misma y el punto se corre al siguiente símbolo pelem = pelem->psig; // Avanzar al siguiente elemento del estado actual if (! estn->numelem) delete estn; // estado nuevo no usado else { estn->agregcerradura(g); pestn = new NodoLSE<Estado>; if (pestn == 0) return 0; pestn->dato.pce = estn; pestn->dato.n = numest; pestn->dato.ves = new Ent16ns[numsimbG + 1]; if (pestn->dato.ves == 0) return 0; limpiar(pestn->dato.ves, numsimbg + 1); pestaux = lse.entrada; while (pestaux!= 0) { // buscar si el estado nuevo no estaba, si es así agregarlo al final if ( *(pestaux->dato.pce) == *estn) break; // salir del while else if (pestaux->psig == 0) { // pestaux apunta al último nodo, que tiene un conj. de elem. distinto al nuevo pestaux->psig = pestn; // agregar al final *pves = numest++; // del actual se transiciona al estado nuevo con el símbolo terminal actual pestaux = 0; // para salir del while else pestaux = pestaux->psig; if (pestaux!= 0) { // si el nodo (estado nuevo) ya existía delete pestn->dato.ves; delete pestn; delete estn; *pves = pestaux->dato.n; // enganchar con la primera copia ++pves; // *pves vale 0 por defecto, o bien puede valer numest psimb = psimb->psig; // avanzar al siguiente símbolo terminal // while que itera en el alfabeto no terminal pesta = pesta->psig; // avanzar al siguiente conjunto de elementos // Hasta aquí está armado el conjunto de estado con las operaciones de // desplazamiento e ir_a ya colocados. Ahora hay que colocar las // reducciones y la aceptación. En los lugares donde en ves quede 0 son // de error. pesta = lse.entrada; while (pesta!= 0) { pelem = pesta->dato.pce->lse.entrada; 328

329 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x todo while (pelem!= 0) { if (pelem->dato.punto == 0) { // si el elemento es " A -> X Y " // Buscar la regla en la gramática Ent16ns numregla; for (rn = g.reglas.entrada, numregla = 0; rn!= 0; rn = rn->psig, ++numregla) if (rn == pelem->dato.pr) break; // salir cuando la encuentre if ( numregla ) { // Si no es la regla 0, A' -> A // se agrega "reducir por regla numregla" a pesta->ves[i] para // símb i en Siguiente(A) donde A está en la parte izq. de la regla. Ent8ns cnt = pelem->dato.pr->dato.lse.entrada->dato.codigo; // cnt código del no terminal de la parte izq. de la regla --cnt; // y ahora tiene cod. del no term. menos 1 puesto que no // existe reducir por regla 0 (A' -> A). cnt indexa a sig[] psimb = sig[cnt].lse.entrada; char i; while (psimb!= 0) { // Mientras hayan símbolos en Siguiente(cnt) i = psimb->dato.codigo; if (i) --i; else i = numsimbg; // Si la entrada no está definida o bien si funcerror dice hacer la // reducción, se asigna reducción por regla numregla a pesta->ves[i] if (! pesta->dato.ves[i] funcerror!= 0 && (*funcerror)(pesta->dato.n, pesta->dato.ves[i], numregla, *g.term.buscar(psimb->dato.codigo) ) ) pesta->dato.ves[i] = 0x8000 numregla; // 10 en los dos últimos bits es reducir psimb = psimb->psig; else { // La regla es A' -> A, la número 0 pesta->dato.ves[numsimbg] = 0x4000; // 01 en los dos últ bits es aceptar // y se coloca para el símbolo terminal FA (entrada numsimbg) pelem = pelem->psig; pesta = pesta->psig; // Llamar explícitamente a los destructores de los alfabetos Siguiente() // for (int c = 0; c < n; ++c ) sig[c].alfabeto::~alfabeto(); delete [] sig; // En los dos últimos bits en el vector de estados siguientes (ves) de cada // estado se da la acción. La estructura puede representarse por la clase // siguiente, la que sirve para las tablas ir_a y acción: // class ElemTabla { // Ent16ns n : 14; // 14 bits // Ent16ns a : 2; // 2 bits para indicar acción. // // En a pueden haber los siguientes valores: // 0 (00): si n == 0 entonces hay error de sintaxis. // si n!= 0 entonces acción = desplazar y pasar al estado n. // 1 (01): n valdrá 0 y no es usado. Acción = aceptar. // 2 (10): acción = reducir por regla n. Siempre n!=

330 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x // Nunca se presenta el valor 3 (11). return numest; // se devuelve el número de estados generados CEST02.CMM // cest02.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" # include <archabst.h> Archivo & operator << ( Archivo & a, const ConjEst & ce ) { if (! ce.numest) return a; register const NodoLSE<Estado> * q = ce.lse.entrada; while (q!= 0) { a << "Estado " << q->dato.n << ":\n" << *(q->dato.pce) << "\n"; q = q->psig; return a; CEST03.CMM // cest02.cmm // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "genslr.h" # include <archabst.h> Archivo & operator << ( Archivo & a, const ConjEst & ce ) { if (! ce.numest) return a; register const NodoLSE<Estado> * q = ce.lse.entrada; while (q!= 0) { a << "Estado " << q->dato.n << ":\n" << *(q->dato.pce) << "\n"; q = q->psig; return a; CEST04.CMM // cest04.cmm // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "genslr.h" # include <archivo.h> 330

331 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x // Ent8 ConjEst::ImprTablaC( const char *, const Gramatica &, // const char * ) const // Imprime la tabla en formato de declaración del C Ent8 ConjEst::ImprTablaC( const char * nom, const Gramatica & g, const char * sufijo ) const { if (! numest g.reglas.entrada == 0) return 0; ArchivoSO a; if ( a.abrir(nom, MA_E, MD_TXT) ) return 0; if (sufijo == 0) sufijo = ""; Ent16ns numnt = g.numnoterm(), numt = g.numterm(), c; // noterm_long_pd tiene en [0] número de term y no term en la parte // baja y alta respectivamente. // en [1] en adelante las longitudes de las partes derechas de las reglas // en la parte baja y el código del no terminal en la parte alta. a << "// Tablas generadas por ConjEst::ImprTablaC versión 2 (Nov 95).\n" "// Domingo Eduardo Becker.\n\n" "# ifndef TIPOS_H\n# include <tipos.h>\n# endif\n" "\nent16ns ntlpd" << sufijo << "[] = {\n"; a.printf(" 0x%x,", numnt << 8 numt ); const NodoLSE<ReglaLC> * pr = g.reglas.entrada->psig; Ent16ns n = 0, x; const NodoLSE<Simbolo> * ps; Ascii0 cad; while (pr!= 0) { if (! n) a << "\n "; x = pr->dato.lse.entrada->dato.codigo << 8 pr->dato.longparteder(); cad.printf("0x%x", x); a.printf(" %6s%c", (char *) cad, pr->psig!= 0? ',' : '\n' ); n += 8; if (n > 70) n = 0; pr = pr->psig; a << ";\n\n"; // accion[] tiene las acciones. en las posiciones 0 de la matriz tiene // la acción para FA. a << "Ent16ns accion" << sufijo << "[] = {\n "; const NodoLSE<Estado> * q = lse.entrada; Ent16ns * ves; while (q!= 0) { ves = q->dato.ves; a.printf(" %5u,", ves[numt + numnt]); n = 7; for (c = 0; c < numt; ++c) { a.printf(" %5u", ves[c]); if (c < numt - 1) a << ","; n += 7; if (n > 70) n = 0; if (! n) a << "\n "; q = q->psig; if (q!= 0) a << ",\n "; a << "\n;\n\n"; // ir_a[] tiene la función ir_a para cada no terminal a << "Ent16ns ir_a" << sufijo << "[] = {\n "; q = lse.entrada; while (q!= 0) { ves = q->dato.ves + numt; // saltea los primeros numt elementos for (c = 0, n = 0; c < numnt; ++c) { a.printf(" %5u", ves[c]); 331

332 Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x if (c < numnt - 1) a << ","; n += 7; if (n > 70) n = 0; if (! n) a << "\n "; q = q->psig; if (q!= 0) a << ",\n "; a << "\n;\n\n"; a.cerrar(); return 1; CEST05.CMM // cest05.cmm // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "genslr.h" # include <archivo.h> // Ent8 ConjEst::ImprTabla( const char *, const Gramatica & g ) const // Imprime la tabla en formato texto en un archivo de texto. Ent8 ConjEst::ImprTabla( const char * nom, const Gramatica & g ) const { if (! numest g.reglas.entrada == 0) return 0; ArchivoSO a; if ( a.abrir(nom, MA_E, MD_TXT) ) return 0; return ImprTabla(a, g); 332

333 Códigos Fuentes de SLR1 v 2.x Capítulo 20: Códigos Fuentes de SLR1 v 2.x SLR1 v 2.x, al igual que la primera versión de SLR1, ha sido diseñado como un programa del usuario que hace uso de la biblioteca de soporte para la generación de analizadores sintácticos SLR. De esta manera es posible la reutilización del generador de analizadores sintácticos en cualquier programa del usuario. A diferencia de la biblioteca de soporte, esta implementación es dependiente del sistema operativo. Los módulos fuente aquí presentados pueden ser compilados con Borland C++ para Windows (4.02 o posterior) o para OS/2 (2.0 o posterior). Para el modo gráfico se utiliza la biblioteca OWL v 2.x que viene incluida en esos compiladores, ganando así portabilidad entre Windows y Presentation Manager. Para el modo caracter se utiliza la biblioteca BCE, ganando así la portabilidad a cualquier sistema operativo que disponga de una versión de BCE. La versión 2.x de SLR1 incluye todas las opciones de SLR1 v 1.x, por tal razón no se incluyen los fuentes de SLR1 v 1.x puesto que son muy similares y sería redundante. A los efectos de no repetir código fuente dentro de este trabajo, cuando sea aplicable, se introducirán referencias hacia otras partes dentro de este documento, en donde era oportuno incluirlos GENSLR1D.CMM El código fuente de este módulo es una implementación basada en lo propuesto en la sección 7.4. Se puede generar una aplicación a partir de éste, compilándolo y encadenandolo con la biblioteca BCE, la biblioteca de soporte de SLR1 v 2.x y las bibliotecas estándar. Este módulo no debe ser incluido junto con GENSLR1.CMM. // genslr1d.cmm Lun 27 Nov 95 // Creado a partir de genslr1.cmm y modificado el 23 Feb 96 para que // funcione bajo DOS y OS/2 modo caracter. // Copyright (c) 1996 by Domingo Eduardo Becker. // All rights reserved. # include <dir.h> // fnsplit y fnmerge # include <string.h> 333

334 Códigos Fuentes de SLR1 v 2.x # include <archivo.h> # include "genslr.h" static Ent8 FuncError ( Ent16ns estado, Ent16ns val_previo, Ent16ns num_regla, const Simbolo & s ) { Ent16ns n = val_previo & 0x2fff, AntesRed = val_previo & 0xc000; Ascii0 antes, ahora; const char * CadTipoConf = AntesRed? "Conflicto de reduccion/reduccion" : "Conflicto de desplazamiento/reduccion"; antes.printf("%c%u", AntesRed? 'r' : 'd', n); ahora.printf("r%u", num_regla); salida << "\n" << CadTipoConf << " en (" << estado << ", " << s << "): " << antes << " / " << ahora; return 0; // siempre dejar acción previa. const char * NomExtSinDir( const char * NomArchCompleto, const char * ExtNueva ) { char disco[maxdrive], dir[maxdir], nomarch[maxfile], extfue[maxext], nulo[] = ""; static char NombreNuevo[MAXPATH]; fnsplit(nomarchcompleto, disco,dir,nomarch,extfue); fnmerge(nombrenuevo, nulo, nulo, nomarch, ExtNueva); return NombreNuevo; Ent8 GenerarSLR1( const char * NomArch, const char * Sufijo ) { salida << "SLR1 v 2.3 para modo caracter - Feb 96\n" "D.E.Becker Tel (085) / \n\n"; if (NomArch == 0! strlen(nomarch)) { salida << "ERROR: Nombre no especificado o falta memoria."; return 1; Gramatica G; ConjEst AutomataSLR1; Ascii0 CadPosErr, GramTxt; const char * nom, cmm[] = ".cmm", est[] = ".est", tab[] = ".tab"; ArchivoSO a; // Cargar la gramática en la memoria: salida << "Cargando gramatica en memoria... "; if ( a.abrir(nomarch, MA_L) ) { salida << "\nno se puede abrir '" << NomArch << "' para lectura."; return 4; if (! GramTxt.Reasignar(a.Tam()+1)) { salida << "Falta memoria para cargar archivo."; return 5; if (! a.leer( (char *) GramTxt, a.tam() ) ) { salida << "ERROR: No se pudo leer el archivo."; return 6; a.cerrar(); // Realizar análisis sintáctico y reasignar a objeto Gramatica salida << "\nrealizando analisis sintactico... "; if (! G.Reasignar(GramTxt, CadPosErr)) { 334

335 Códigos Fuentes de SLR1 v 2.x salida << "ERROR en la posicion: \n" << CadPosErr; return 7; G.ArmarAlfabetos(); // terminal y no terminal // Generar el código fuente con las acciones semánticas: salida << "\ngenerando codigo C++ de acciones semanticas... "; nom = NomExt(NomArch, cmm); if (a.abrir(nom, MA_E)) { salida << "ERROR: No se puede abrir '" << nom << "' para escritura."; return 8; a << "// Codigo fuente generado por SLR1 version 2.3 para modo caracter (Feb 96)\n" "// Domingo Eduardo Becker, Sgo. del Estero, Tel (085) \n"; G.ImprAccSem(a, Sufijo); a << "\n"; // Generar codigo para el analizador lexico: salida << "\ngenerando codigo C++ de los analizadores lexico y sintactico... "; G.ImprAnaLex(a, Sufijo); // Imprimir analizador sintactico: a << "\n// Definicion del analizador sintactico (SLR1 v 2.3).\n" "// Cambiar de lugar la definicion si es necesario.\n\n" "# include \"" << NomExtSinDir(NomArch, tab) << "\" // tablas del analizador sintactico\n\nanalslr1<" << (const char *) G.ClaseElemPila << "> as" << Sufijo << "(ntlpd" << Sufijo << ", accion" << Sufijo << ", ir_a" << Sufijo << ", AccSem" << Sufijo << ");"; a.cerrar(); // Imprimir la gramatica en archivo.est salida << "\nimprimiendo la gramatica en archivo.est... "; nom = NomExt(NomArch, est); if ( a.abrir(nom, MA_E, MD_TXT) ) { salida << "ERROR: No se puede abrir '" << nom << "' para escritura"; return 9; a << G; // Imprimir Anulable, Primero y Siguiente: G.ImprAnulPrimSig(a); // Armar las tablas de analisis sintactico salida << "\ngenerando las tablas SLR(1)... "; AutomataSLR1.Armar(G, FuncError); // Imprimir las tablas en archivo.est salida << "\nimprimiendo las tablas SLR(1) en archivo.est... "; a << "\n\nestados: \n\n" << AutomataSLR1; AutomataSLR1.ImprTabla(a, G); a.cerrar(); // Imprimir las tablas en archivo.tab salida << "\nimprimiendo las tablas SLR(1) en archivo.tab... "; AutomataSLR1.ImprTablaC(NomExt(NomArch, tab), G, Sufijo); salida << "\n\nanalizador sintactico y lexicografico generados sin problemas." << "\ntablas en C++ en: " << NomExt(NomArch, tab); 335

336 Códigos Fuentes de SLR1 v 2.x salida << "\ntablas y estados en texto en: " << NomExt(NomArch, est); salida << "\nacciones semanticas y analizadores: " << NomExt(NomArch, cmm); return 0; int main(int narg, char * carg[]) { if (narg < 2) { salida << "Falta el nombre del archivo con la gramatica."; return 1; return GenerarSLR1(cArg[1], carg[2]); 20.2 GENSLR1.CMM El código fuente de este módulo es una implementación basada en lo propuesto en la sección 7.4. Se agregó un diálogo en donde se presenta el progreso de la generación y otro en donde se informan los conflictos y se da posibilidad de corregirlos. Si hay error de sintaxis, se muestra la posición en donde se produjo el error en otro diálogo. // genslr1.cmm Lun 27 Nov 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. # include "progreso.h" # include "dlgerror.h" # include "dlgconf.h" # include <archivo.h> # include "genslr.h" # include <dir.h> // fnsplit y fnmerge static TProgreso * pdlgprogreso = 0; static Ent8 FuncError ( Ent16ns estado, Ent16ns val_previo, Ent16ns num_regla, const Simbolo & s ) { Ent16ns n = val_previo & 0x2fff, AntesRed = val_previo & 0xc000; Ascii0 cad; const char * CadTipoConf = AntesRed? "Conflicto de reducción/reducción" : "Conflicto de desplazamiento/reducción"; TDlgConflicto dlg(pdlgprogreso); dlg.titulo = CadTipoConf; dlg.estado = estado; dlg.terminal = s.cadena(); dlg.accprevia.printf("%c%u", AntesRed? 'r' : 'd', n); dlg.accnueva.printf("r%u", num_regla); return dlg.execute()!= IDOK; // si ==IDOK se elige acción previa. const char * NomExtSinDir( const char * NomArchCompleto, const char * ExtNueva ) { 336

337 Códigos Fuentes de SLR1 v 2.x char disco[maxdrive], dir[maxdir], nomarch[maxfile], extfue[maxext], nulo[] = ""; static char NombreNuevo[MAXPATH]; fnsplit(nomarchcompleto, disco,dir,nomarch,extfue); fnmerge(nombrenuevo, nulo, nulo, nomarch, ExtNueva); return NombreNuevo; Ent8 GenerarSLR1( const char * NomArch, const char * Sufijo, TWindow * VentPadre ) { // Crear ventana diálogo de progreso pdlgprogreso = new TProgreso(VentPadre); if (pdlgprogreso == 0) return 1; if (! pdlgprogreso->create()) { delete pdlgprogreso; return 2; pdlgprogreso->setcaption("slr1 v Dic 95"); if (NomArch == 0! strlen(nomarch)) { pdlgprogreso->messagebox("nombre no especificado o falta memoria.", "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 1; Gramatica G; ConjEst AutomataSLR1; Ascii0 CadPosErr, GramTxt; const char * nom, cmm[] = ".cmm", est[] = ".est", tab[] = ".tab"; ArchivoSO a; // Cargar la gramática en la memoria: pdlgprogreso->setdlgitemtext(txt_1, "Cargando gramática en memoria..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) if ( a.abrir(nomarch, MA_L) ) { CadPosErr.printf("No se puede abrir '%s'\npara lectura.", NomArch); pdlgprogreso->messagebox(cadposerr, "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 4; if (! GramTxt.Reasignar(a.Tam()+1)) { pdlgprogreso->messagebox("falta memoria para cargar archivo.", "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 5; if (! a.leer( (char *) GramTxt, a.tam() ) ) { pdlgprogreso->messagebox("no se pudo leer el archivo.", "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 6; a.cerrar(); 337

338 Códigos Fuentes de SLR1 v 2.x // Realizar análisis sintáctico y reasignar a objeto Gramatica pdlgprogreso->setdlgitemtext(txt_1, "Gramática cargada en memoria."); pdlgprogreso->setdlgitemtext(txt_2, "Realizando análisis sintáctico..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) if (! G.Reasignar(GramTxt, CadPosErr)) { TDlgError * pdlgerror = new TDlgError(pDlgProgreso); pdlgerror->cad = CadPosErr; pdlgerror->execute(); delete pdlgerror; delete pdlgprogreso; return 7; G.ArmarAlfabetos(); // terminal y no terminal // Generar el código fuente con las acciones semánticas: pdlgprogreso->setdlgitemtext(txt_2, "No hay problemas de sintaxis."); pdlgprogreso->setdlgitemtext(txt_3, "Generando código C++ de acciones semánticas..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) nom = NomExt(NomArch, cmm); if (a.abrir(nom, MA_E/*, MD_TXT*/)) { CadPosErr.printf("No se puede abrir '%s'\npara escritura.", nom); pdlgprogreso->messagebox(cadposerr, "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 8; a << "// Código fuente generado por SLR1 versión 2.3 (Dic 95)\n" "// Domingo Eduardo Becker, Sgo. del Estero, Tel (085) \n"; G.ImprAccSem(a, Sufijo); a << "\n"; // Generar código para el analizador léxico: pdlgprogreso->setdlgitemtext(txt_3, "Código C++ de acciones semánticas generado."); pdlgprogreso->setdlgitemtext(txt_4, "Generando código C++ de los analizadores léxico y sintáctico..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) G.ImprAnaLex(a, Sufijo); // Imprimir analizador sintáctico: a << "\n// Definición del analizador sintáctico (SLR1 v 2.3).\n" "// Cambiar de lugar la definición si es necesario.\n\n" "# include \"" << NomExtSinDir(NomArch, tab) << "\" // tablas del analizador sintáctico\n\nanalslr1<" << (const char *) G.ClaseElemPila << "> as" << Sufijo << "(ntlpd" << Sufijo << ", accion" << Sufijo << ", ir_a" << Sufijo << ", AccSem" << Sufijo << ");"; a.cerrar(); // Imprimir la gramática en archivo.est pdlgprogreso->setdlgitemtext(txt_4, "Código C++ de los analizadores léxico y sintáctico generado."); 338

339 Códigos Fuentes de SLR1 v 2.x pdlgprogreso->setdlgitemtext(txt_5, "Imprimiendo la gramática en archivo.est..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) nom = NomExt(NomArch, est); if ( a.abrir(nom, MA_E, MD_TXT) ) { CadPosErr.printf("No se puede abrir '%s'\npara escritura.", nom); pdlgprogreso->messagebox(cadposerr, "ERROR", MB_OK MB_ICONHAND); delete pdlgprogreso; return 9; a << G; // Imprimir Anulable, Primero y Siguiente: G.ImprAnulPrimSig(a); // Armar las tablas de análisis sintáctico pdlgprogreso->setdlgitemtext(txt_5, "Gramática impresa en archivo.est."); pdlgprogreso->setdlgitemtext(txt_6, "Generando las tablas SLR(1)..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) AutomataSLR1.Armar(G, FuncError); // Imprimir las tablas en archivo.est pdlgprogreso->setdlgitemtext(txt_6, "Tablas de análisis SLR(1) generadas."); pdlgprogreso->setdlgitemtext(txt_7, "Imprimiendo las tablas SLR(1) en archivo.est..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) a << "\n\nestados: \n\n" << AutomataSLR1; AutomataSLR1.ImprTabla(a, G); a.cerrar(); // Imprimir las tablas en archivo.tab pdlgprogreso->setdlgitemtext(txt_7, "Tablas de análisis SLR(1) impresas en archivo.est."); pdlgprogreso->setdlgitemtext(txt_8, "Imprimiendo las tablas SLR(1) en archivo.tab..."); pdlgprogreso->getapplication()->pumpwaitingmessages(); // (pseudo multitarea) AutomataSLR1.ImprTablaC(NomExt(NomArch, tab), G, Sufijo); delete pdlgprogreso; return 0; 20.3 PROGRESO.CMM El diseño de la ventana donde se muestra el progreso es el siguiente: 339

340 Códigos Fuentes de SLR1 v 2.x /* Project slr1 Fig. 34 Diseño de la ventana donde se muestra el progreso de la generación con SLR1 v 2.x Copyright 1995 by Domingo Eduardo Becker. All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: slr1.apx Application progreso.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of TProgreso (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "progreso.h" //{{TProgreso Implementation TProgreso::TProgreso (TWindow* parent, TResId resid, TModule* module): TDialog(parent, resid, module) { // INSERT>> Your constructor code here. TProgreso::~TProgreso () { Destroy(); // INSERT>> Your destructor code here. 340

341 Códigos Fuentes de SLR1 v 2.x 20.4 DLGCONF.CMM Diseño del diálogo donde se muestra un conflicto y donde se da la posibilidad de corregirlo: /* Project slr1 Fig. 35 Diseño del diálogo donde se muestran los conflictos. Copyright 1995 by Domingo Eduardo Becker. All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: slr1.apx Application dlgconf.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of TDlgConflicto (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "dlgconf.h" //{{TDlgConflicto Implementation TDlgConflicto::TDlgConflicto (TWindow* parent, TResId resid, TModule* module): TDialog(parent, resid, module) { // INSERT>> Your constructor code here. 341

342 Códigos Fuentes de SLR1 v 2.x TDlgConflicto::~TDlgConflicto () { Destroy(); // INSERT>> Your destructor code here. void TDlgConflicto::SetupWindow () { TDialog::SetupWindow(); // INSERT>> Your code here. if (Titulo.Long()) SetCaption(Titulo); if (Estado.Long()) SetDlgItemText(TXT_1, Estado); if (Terminal.Long()) SetDlgItemText(TXT_2, Terminal); if (AccPrevia.Long()) SetDlgItemText(TXT_3, AccPrevia); if (AccNueva.Long()) SetDlgItemText(TXT_4, AccNueva); 20.5 DLGERROR.CMM El diseño del diálogo donde se muestran los errores de sintaxis es el siguiente: /* Project afd Fig. 36 Diseño del diálogo donde se muestran los errores de sintaxis. Copyright All Rights Reserved. SUBSYSTEM: afd.apx Application 342

343 Códigos Fuentes de SLR1 v 2.x FILE: dlgerror.cmm AUTHOR: Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of TDlgError (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "dlgerror.h" //{{TDlgError Implementation TDlgError::TDlgError (TWindow* parent, TResId resid, TModule* module): TDialog(parent, resid, module) { // INSERT>> Your constructor code here. TDlgError::~TDlgError () { Destroy(); // INSERT>> Your destructor code here. void TDlgError::SetupWindow () { TDialog::SetupWindow(); // INSERT>> Your code here. if (Cad.Long()) SetDlgItemText(IDC_CADENADLGERROR, Cad); 20.6 SL1TDLGC.CMM El diseño de la ventana principal es el siguiente: 343

344 Códigos Fuentes de SLR1 v 2.x /* Project slr1 Fig. 37 Diseño de la ventana principal de SLR1 v 2.x para modo gráfico. Copyright 1995 by Domingo Eduardo Becker. All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: slr1.exe Application sl1tdlgc.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of slr1tdlgclient (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include "slr1app.h" #include "sl1tdlgc.h" # include <owl\opensave.h> # include <ascii0.h> // // Build a response table for all messages/commands handled // by the application. // DEFINE_RESPONSE_TABLE1(slr1TDLGClient, TDialog) //{{slr1tdlgclientrsp_tbl_begin EV_BN_CLICKED(IDOK, Procesar), EV_BN_CLICKED(IDC_BUSCAR, BuscarArchivo), //{{slr1tdlgclientrsp_tbl_end END_RESPONSE_TABLE; //{{slr1tdlgclient Implementation ////////////////////////////////////////////////////////// // slr1tdlgclient // ========== // Construction/Destruction handling. 344

345 Códigos Fuentes de SLR1 v 2.x slr1tdlgclient::slr1tdlgclient (TWindow *parent, TResId resid, TModule *module) : TDialog(parent, resid, module) { // INSERT>> Your constructor code here. slr1tdlgclient::~slr1tdlgclient () { Destroy(); // INSERT>> Your destructor code here. Ent8 GenerarSLR1( const char * NomArch, const char * Sufijo, TWindow * VentPadre ); void slr1tdlgclient::procesar () { // INSERT>> Your code here. Ascii0 NomArch(100), Sufijo(50); GetDlgItemText(IDC_NOMARCH, NomArch, NomArch.TamBlq()); GetDlgItemText(IDC_SUFIJO, Sufijo, Sufijo.TamBlq()); if (GenerarSLR1(NomArch, Sufijo, this)) MessageBox("Error", "ERROR", MB_OK MB_ICONHAND); else MessageBox("No hay problemas", "Observación", MB_OK MB_ICONINFORMATION); void slr1tdlgclient::buscararchivo () { // INSERT>> Your code here. TOpenSaveDialog::TData DatosDlg(OFN_HIDEREADONLY OFN_FILEMUSTEXIST OFN_NOCHANGEDIR, "Gramáticas (*.GLC) *.glc ", 0, 0,"glc"); TFileOpenDialog dlg(this, DatosDlg); if (dlg.execute() == IDOK) SetDlgItemText(IDC_NOMARCH, DatosDlg.FileName); 345

346 Códigos Fuentes de la Biblioteca ExpReg v 1.x Capítulo 21: Códigos Fuentes de la Biblioteca ExpReg v 1.x Los módulos fuentes presentados en este capítulo son independientes del sistema operativo y del compilador con el cual se trabaje. A los efectos de no repetir código fuente dentro de este trabajo, cuando sea aplicable, se introducirán referencias hacia otras partes dentro de este documento, en donde era oportuno incluirlos EXPREG.H Este módulo se presenta en la sección GENAFD.H # ifndef GENAFD_H # define GENAFD_H // genafd.h: clases para el generador de autómatas finitos determinísticos // a partir de expresiones regulares. // Creación: Lun 09 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # ifndef TIPOS_H # include <tipos.h> // independencia de la implementación de los tipos # endif // de datos fundamentales como por ejemplo el int. # ifndef LSE_H # include "lse.h" # endif class NodoArbol { public: Ent16ns Tipo; Ent8 PrimUltCalc; NodoArbol * pizq, * pder; LSE<Ent16ns> Prim, Ult; NodoArbol() { Tipo = PrimUltCalc = 0; pizq = pder = 0; ~NodoArbol() { Vaciar(); Ent8 Anulable() const; // para el árbol cuya raíz es éste nodo void CalcPrimUltSig( LSE<Ent16ns> * Sig ); Ent16ns AgregPrim( const LSE<Ent16ns> & prim ); Ent16ns AgregUlt( const LSE<Ent16ns> & ult ); void BorrarSubarboles(); void Vaciar(); ; 346

347 Códigos Fuentes de la Biblioteca ExpReg v 1.x // Tipo: indica el tipo de nodo. Para las hojas, este campo tiene el número // de hoja (no puede haber 2 hojas con el mismo número), valor que // est entre 0 y N_HOJA ambos inclusive. Para este campo se definen // constantes más adelante. // IdCar: identificador del caracter, es un índice para acceder a un vector // donde está la descripción de la expresión monocaracter. // PrimUltCalc: tiene 0 si PrimeraPos (Prim) y UltimaPos (Ult) no han sido // calculados,!= 0 si ya se calcularon. // pizq: subárbol de la izquierda del actual. Para los nodos '*', '+' y '?' // el subárbol se engancha en este campo. Para las hojas == 0. // pder: subárbol de la derecha del actual. Se usa solamente para nodos del // tipo '.' y ' '. Para los nodos '*', '+', '?' y hojas vale 0. // Prim y Ult: son listas de identificadores de las hojas (valor del campo // Tipo de las mismas) que son PrimeraPos y UltimaPos respecti- // vamente, del nodo actual. // Para el campo Tipo: // N_DIS: nodo disyunción, ' ' // N_CON: nodo concatenación, '.' // N_AST: nodo asterisco, '*' // N_MAS: nodo más, '+' // N_OPC: nodo opcional, '?' // N_HOJA: nodo hoja, las hojas se numeran desde 0 hasta N_HOJA. # define N_DIS 0xffff # define N_CON 0xfffe # define N_AST 0xfffd # define N_MAS 0xfffc # define N_OPC 0xfffb # define N_HOJA 0xfffa class DescrHoja { public: Ent16ns Id; Ent8ns IdCar; DescrHoja() { Id = IdCar = 0; ; // Id: número de hoja en el árbol sintáctico. // IdCar: número de elemento en la lista de caracteres. // La tabla de descripciones de hoja es una LSE<DescrHoja>. // Con el campo IdCar se accede a una lista de cadenas donde se encuentra la // descripción del caracter o rango de caracteres aceptables como unidad. // La lista de cadenas se implementar como LSE<Ascii0> Ent16ns AgregLSE( LSE<Ent16ns> & des, const LSE<Ent16ns> & fue ); // agrega a des una copia de los elementos de fue class Estado { public: Ent16ns Id, * Tran; LSE<Ent16ns> * Pos; Estado() { Id = 0; Tran = 0; Pos = 0; ; // Id: identificador del estado (0..n-1 si hay n estados). 347

348 Códigos Fuentes de la Biblioteca ExpReg v 1.x // Tran: vector de transiciones a estados siguientes. Este vector es de // NumHojas elementos donde NumHojas es el número de hojas del árbol // sintáctico de la expresión regular. // Pos: lista de las hojas del árbol sintáctico que se agrupan en este // estado. # endif // # ifndef GENAFD_H 21.3 AFD01.CMM // afd01.cmm Vie 13 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <archivo.h> int ImprimirAFD( Archivo & arch, const AutomFinDet & afd ) { arch << "Expresión regular: " << (afd.expregular!= 0? afd.expregular : "Desconocida"); if (afd.numcar > 1) arch << "\ncaracteres y/o rangos: "; else if (afd.numcar == 1) arch << "\ncaracter o rango: "; if (! afd.numcar) { arch << "ERROR en el autómata, campo NumCar == 0.\n"; return 0; Ent16ns i, j, estmayor, numcol, t; if (afd.caracter!= 0) for (i = 0; i < afd.numcar; ++i) { if (i) arch << ", "; arch << "(" << i << ", \""; if (afd.caracter[i] == 0) arch << "?#@!"; else ImprCadena(arch, afd.caracter[i]); arch << "\")"; arch << "\ntabla de transiciones:\n"; if (afd.transicion == 0) { arch << "ERROR en el autómata, campo Transicion == 0.\n"; return 0; estmayor = 1; arch << " "; numcol = afd.numcar; for (i = 0; i < numcol; ++i) arch.printf("%3d ", i); arch << "\n"; for (i = 0; i < estmayor; ++i) { arch.printf("%3d ", i); for (j = 0; j < numcol; ++j) { t = afd.transicion[i * numcol + j]; if (t == TNDEF) arch << " - "; else { arch.printf("%3d ", t); if (t >= estmayor) estmayor = t + 1; arch << "\n"; if (afd.estfinales == 0) { arch << "ERROR en el autómata, campo EstFinales == 0.\n"; return 0; 348

349 Códigos Fuentes de la Biblioteca ExpReg v 1.x if (afd.estfinales[0] > 1) arch << "Estados finales: "; else arch << "Estado final: "; if (afd.estfinales[0]) { for (i = 1; i <= afd.estfinales[0]; ++i) { if (i > 1) arch << ", "; arch << (Ent16ns) afd.estfinales[i]; arch << "\n"; else arch << "ERROR: EstFinales[0] == 0, no hay estados finales.\n"; return 1; 21.4 AFD02.CMM // afd02.cmm Lun 16 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <archivo.h> # include <string.h> int ImprFuenteAFD( Archivo & arch, const AutomFinDet & afd, const char * NomObj, const char * NomSubobj ) { if (NomObj == 0) NomObj = ""; if (NomSubobj == 0) NomSubobj = NomObj; arch << "// AFD generado por la versión 3.4 de ExpReg::Reasignar (Nov 95)\n" "// Domingo Eduardo Becker, Sgo. del Estero, (085) /1088.\n" "\n# ifndef EXPREG_H\n# include \"expreg.h\"\n# endif\n\n"; arch << "static const char er" << NomSubobj << "[] = \""; if (afd.expregular!= 0) ImprCadena(arch, afd.expregular); arch << "\";\n"; if (! afd.numcar) { arch << "ERROR en el autómata, campo NumCar == 0.\n"; return 0; unsigned i, j, estmayor, numcol, t, lon; if (afd.caracter!= 0) { arch << "static const char * vc" << NomSubobj << "[] = {\n "; for (i = lon = 0; i < afd.numcar; ++i) { if (i) { arch << ", "; lon += 2; if (lon >= 80) { arch << "\n "; lon = 0; arch << "\""; if (afd.caracter[i]!= 0) ImprCadena(arch, afd.caracter[i]); arch << "\""; lon += 2 + strlen(afd.caracter[i]); arch << "\n;\n"; if (afd.transicion == 0) { arch << "ERROR en el autómata, campo Transicion == 0.\n"; 349

350 Códigos Fuentes de la Biblioteca ExpReg v 1.x return 0; arch << "static const Ent16ns t" << NomSubobj << "[] = {\n"; estmayor = 1; numcol = afd.numcar; for (i = 0; i < estmayor; ++i) { if (i) arch << ",\n"; arch << " "; for (j = 0; j < numcol; ++j) { t = afd.transicion[i * numcol + j]; if (j) arch << ", "; if (t == TNDEF) arch << "TNDEF"; else { arch.printf("%5d", t); if (t >= estmayor) estmayor = t + 1; arch << "\n;\n"; if (afd.estfinales == 0) { arch << "ERROR en el autómata, campo EstFinales == 0.\n"; return 0; if (afd.estfinales[0]) { arch << "static const Ent16ns ef" << NomSubobj << "[] = {\n "; for (i = 0; i <= afd.estfinales[0]; ++i) { if (i) arch << ", "; arch << (Ent16ns) afd.estfinales[i]; arch << "\n;\n"; else { arch << "ERROR: EstFinales[0] == 0, no hay estados finales.\n"; return 0; arch << "AutomFinDet afd" << NomObj << " = { er" << NomSubobj << ", vc" << NomSubobj << ", " << (Ent16ns) afd.numcar << ", t" << NomSubobj << ", ef" << NomSubobj << " ;\n"; return 1; 21.5 ANALEX01.CMM // analex01.cmm Mar 17 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <ascii0.h> # include <string.h> unsigned AnaLex::Examinar( Ascii0 & CadSimb, int may ) { Ent8 aux = MayIgualMin; MayIgualMin = may; unsigned r = Examinar(CadSimb); MayIgualMin = aux; return r; 350

351 Códigos Fuentes de la Biblioteca ExpReg v 1.x unsigned AnaLex::Examinar( Ascii0 & CadSimb ) { unsigned i, tama, tamafd, iafd, tamcad, icad, Cod; ExpReg er; char Ignorar, HayUnAFD, HayUnLiteral; Ignorar = 1; CadSimb.Vaciar(); do { if (Tama) { register const char * p; // ver si en el símbolo anterior habían '\n' for (i = 0, p = ptxt + Inic; i < Tama; ++i) if (*p++ == '\n') { ++NumLin; ComLin = Inic + i + 1; // ahora avanzar Inic Inic += Tama; Tama = 0; // Si no hay más que analizar, salir. if (! ptxt[inic]) { CodError = 0; return 0; iafd = icad = tvrs; tamafd = tamcad = 0; // Buscar si hay algún AFD que reconozca el símbolo siguiente for (i = 0; i < tvrs; ++i) if (vrs[i].esafd) { er.reasignar( * vrs[i].pafd ); if (! er.examinar(ptxt, Inic, tama) && tama > tamafd) { tamafd = tama; iafd = i; && // Sea o no que haya un AFD que reconozca al símbolo, verificar // si corresponde a una constante. for (i = 0; i < tvrs; ++i) if (! vrs[i].esafd) { tama = strlen(vrs[i].pcad); if ((! MayIgualMin &&! strncmp(vrs[i].pcad, ptxt + Inic, tama) MayIgualMin &&! strnicmp(vrs[i].pcad, ptxt + Inic, tama) ) tama > tamcad) { tamcad = tama; icad = i; HayUnLiteral = icad < tvrs; HayUnAFD = iafd < tvrs; // Si hay una cadena literal y un AFD que reconocen a la cadena de // caracteres siguiente, elegir entre los dos el más largo. Si ambos tienen // igual longitud o el literal es más largo que la cadena reconocida por el // AFD entonces elegir el literal. if (HayUnLiteral && HayUnAFD) { if (tamafd > tamcad) { Cod = vrs[iafd].id; Ignorar =! Cod; CodError = 0; Tama = tamafd; 351

352 Códigos Fuentes de la Biblioteca ExpReg v 1.x if (! Ignorar) Simbolo(CadSimb); else { Cod = vrs[icad].id; Ignorar =! Cod; CodError = 0; Tama = tamcad; if (! Ignorar) Simbolo(CadSimb); else if (HayUnLiteral) { Cod = vrs[icad].id; Ignorar =! Cod; CodError = 0; Tama = tamcad; if (! Ignorar) Simbolo(CadSimb); else if (HayUnAFD) { Cod = vrs[iafd].id; Ignorar =! Cod; CodError = 0; Tama = tamafd; if (! Ignorar) Simbolo(CadSimb); else { Cod = ferrorlex!= 0? (*ferrorlex)(mayigualmin, *this, CadSimb) : 0xffff; CodError = Cod == 0xffff? 1 : 0; Ignorar =! Cod; while (Ignorar); return Cod; 21.6 ANALEX02.CMM // analex02.cmm Mié 18 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <ascii0.h> # include <string.h> unsigned AnaLex::Simbolo( Ascii0 & Simb ) const { if (! Tama) return 0; if (! Simb.Reasignar(Tama + 1)) return 0; strncpy(simb, ptxt + Inic, Tama); return Tama; 21.7 ANALEX03.CMM // analex03.cmm Sáb 21 Ene 95 // Copyright (c) 1995 by Domingo Eduardo Becker. // All rights reserved. 352

353 Códigos Fuentes de la Biblioteca ExpReg v 1.x # include "blanco.cmm" # include "coment.cmm" # include "caract.cmm" # include "rango.cmm" # include "idafd.cmm" static RecSimb vrs[] = { 0, 1, & afdblanco, 0, // ignora los blancos (Id == 0) 0, 1, & afdcoment, 0, // ignora los comentarios (Id == 0) _id_ctecar, 1, & afdcaract, 0, _id_rango, 1, & afdrango, 0, _id_ident, 1, & afdident, 0, _id_disyuncion, 0, 0, " ", _id_uno_o_mas, 0, 0, "+", _id_cero_o_mas, 0, 0, "*", _id_opcional, 0, 0, "?", _id_abre_paren, 0, 0, "(", _id_cierra_paren, 0, 0, ")", _id_numeral, 0, 0, "#", _id_ctecar, 0, 0, ".", ; # ifndef ASCII0_H # include <ascii0.h> # endif static unsigned CorregirErrLex( int, AnaLex & analex, Ascii0 & cad ) { register char car, * p; if (! (car = analex.saltear()) ) return 0; // 0 == Fin de archivo if (! cad.reasignar(2)) return 0; //?? falta memoria p = (char *) cad; *p++ = car; *p = 0; // terminaci n en 0 return _id_ctecar; // El analizador léxico es el objeto AnaLexExpReg siguiente: AnaLex AnaLexExpReg(vrs, 13, 0, CorregirErrLex); // 13 elementos RecSimb en vrs ANALEX04.CMM // analex04.cmm Jue 26 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" unsigned AnaLex::Saltear( unsigned tam ) { if (! tam Tama ptxt == 0! ptxt[inic]) return 0; register unsigned t; register const char * p; for (t = 0, p = ptxt + Inic; t < tam; ++t, ++p) if (! *p) break; return Tama = t; 353

354 Códigos Fuentes de la Biblioteca ExpReg v 1.x 21.9 ANALEX05.CMM // analex05.cmm Jue 26 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <ascii0.h> # include <string.h> unsigned AnaLex::CadError( Ascii0 & Cad ) const { register char * p; register unsigned t, n; // Contar la cantidad de caracteres hasta el siguiente '\n' o fin de archivo for (t = 0, p = (char *) ptxt + ComLin; *p && *p!= '\n'; ++t, ++p); Cad.Vaciar(); if (! t) return 0; if (! Cad.Reasignar(t * 2 + 2)) return 0; strncpy(cad, ptxt + ComLin, t); p = (char *) Cad + t; *p++ = '\n'; for (n = ComLin; t; --t, ++n) *p++ = n!= Inic? ' ' : '^'; *p = 0; return Cad.Long(); ER01.CMM // er01.cmm Lun 16 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" void ExpReg::Vaciar() { if (afd == 0) return; if (! apu) { if (afd->transicion!= 0) delete (void *) afd->transicion; if (afd->estfinales!= 0) delete (void *) afd->estfinales; delete afd; afd = 0; apu = 0; 354

355 Códigos Fuentes de la Biblioteca ExpReg v 1.x ER02.CMM // er02.cmm Lun 16 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <fvarias.h> # include <string.h> int ExpReg::Examinar( const char * Cadena, unsigned Inicio, unsigned & Tam ) const { Ent16ns Estado, NumCar, Car, EstadoNuevo, i; const char * * Caracter, * pcad; const Ent16ns * Transicion, * EstFinales; Tam = 0; if (afd == 0) return AFDNDEF; Caracter = afd->caracter; Transicion = afd->transicion; EstFinales = afd->estfinales; NumCar = afd->numcar; Estado = 0; if (Caracter == 0 Transicion == 0 EstFinales == 0! NumCar) return AFDERR; if (Cadena == 0 Inicio >= strlen(cadena)) return CADNULA; pcad = Cadena + Inicio; do { // Primero buscar qué caracter es for (Car = 0; Car < NumCar; ++Car) if (Aceptar(*pCad, Caracter[Car])) break; if (Car == NumCar) break; // salir del bucle, posible parada en // estado final // Luego ver si hay transición para ese caracter y el estado actual EstadoNuevo = Transicion[Estado * NumCar + Car]; if (EstadoNuevo!= TNDEF) { // si hay, avanzar un caracter ++Tam; ++pcad; Estado = EstadoNuevo; while (EstadoNuevo!= TNDEF! *pcad); // Finalmente ver si el estado en el que se qued es estado final for (i = 1; i <= EstFinales[0]; ++i) if (Estado == EstFinales[i]) break; if (i > EstFinales[0]) if (Car == NumCar) return CARINESP; // caracter inesperado else return TRANSNDEF; // es caracter esperado pero no hay transici n return 0; ER03.CMM // er03.cmm Mar 17 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. 355

356 Códigos Fuentes de la Biblioteca ExpReg v 1.x # include "expreg.h" # include <string.h> int ExpReg::Buscar( const char * Cadena, unsigned & Inicio, unsigned & Tam, unsigned Desde ) const { unsigned inicio, loncad; loncad = Cadena!= 0 && *Cadena? strlen(cadena) : 0; if (Desde >= loncad) return CADNULA; inicio = Desde; Tam = 0; while (inicio < loncad && Examinar(Cadena, inicio, Tam)) ++inicio; Inicio = inicio < loncad? inicio : Desde; return 0; ER04.CMM // er04.cmm Mié 25 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" # include <archivo.h> int ExpReg::ImprFuente( const char * NomArch, const char * NomObj, const char * NomSubobj ) const { register int r; ArchivoSO arch; if ( arch.abrir(nomarch, MA_E, MD_TXT) ) return 0; r = ImprFuente(arch, NomObj, NomSubobj); arch.cerrar(); return r; NARBOL01.CMM // narbol01.cmm Mié 11 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "genafd.h" Ent8 NodoArbol::Anulable() const { register char r; switch (Tipo) { case N_AST: // '*' case N_OPC: // '?' r = 1; break; case N_DIS: // ' ' r = pizq->anulable() pder->anulable(); break; case N_CON: // '.' r = pizq->anulable() && pder->anulable(); break; 356

357 Códigos Fuentes de la Biblioteca ExpReg v 1.x default: // Tipo <= N_HOJA Tipo == N_MAS '+' r = 0; return r; Ent16ns AgregLSE( LSE<Ent16ns> & des, const LSE<Ent16ns> & fue ) { register NodoLSE<Ent16ns> * p, * q; Ent16ns c; for (p = fue.entrada, c = 0; p!= 0; p = p->psig) { for (q = des.entrada; q!= 0; q = q->psig) if (q->dato == p->dato) break; // si lo encuentra salir if (q!= 0) continue; // nodo existente, no agregar des.agregcomienzo(p->dato); // lo agrega ++c; return c; Ent16ns NodoArbol::AgregPrim( const LSE<Ent16ns> & primfue ) { return AgregLSE(Prim, primfue); Ent16ns NodoArbol::AgregUlt( const LSE<Ent16ns> & ultfue ) { return AgregLSE(Ult, ultfue); void AgregSig( LSE<Ent16ns> * Sig, const LSE<Ent16ns> & Desde, const LSE<Ent16ns> & Hacia ) { // Todas las hojas que hay en Hacia son SiguientePos de todas las hojas // que hay en Desde. register NodoLSE<Ent16ns> * p; for (p = Desde.Entrada; p!= 0; p = p->psig) AgregLSE(Sig[p->Dato], Hacia); void NodoArbol::CalcPrimUltSig( LSE<Ent16ns> * Sig ) { if (PrimUltCalc) return; // ya fue realizado switch (Tipo) { case N_AST: // '*' case N_OPC: // '?' case N_MAS: // '+' pizq->calcprimultsig(sig); AgregPrim(pIzq->Prim); AgregUlt(pIzq->Ult); PrimUltCalc = 1; break; case N_DIS: // ' ' pizq->calcprimultsig(sig); pder->calcprimultsig(sig); AgregPrim(pIzq->Prim); AgregPrim(pDer->Prim); AgregUlt(pIzq->Ult); AgregUlt(pDer->Ult); PrimUltCalc = 1; break; case N_CON: // '.' pizq->calcprimultsig(sig); pder->calcprimultsig(sig); // PrimeraPos AgregPrim(pIzq->Prim); if (pizq->anulable()) AgregPrim(pDer->Prim); 357

358 Códigos Fuentes de la Biblioteca ExpReg v 1.x // UltimaPos AgregUlt(pDer->Ult); if (pder->anulable()) AgregUlt(pIzq->Ult); // Ahora se calcula SiguientePos para nodos '.' AgregSig(Sig, pizq->ult, pder->prim); // todas las posiciones dentro de // Prim del hijo derecho están en SiguientePos de todas las posiciones // dentro Ult del hijo izquierdo del actual. break; default: // Tipo <= N_HOJA Prim.AgregComienzo(Tipo); Ult.AgregComienzo(Tipo); PrimUltCalc = 1; // Se calcula SiguientePos para nodos '*' y nodos '+' if (Tipo == N_AST Tipo == N_MAS) AgregSig(Sig, Ult, Prim); // todas las posiciones de Prim son // siguientes a las posiciones de Ult void NodoArbol::BorrarSubarboles() { if (pizq!= 0) { delete pizq; pizq = 0; if (pder!= 0) { delete pder; pder = 0; void NodoArbol::Vaciar() { BorrarSubarboles(); Prim.Vaciar(); Ult.Vaciar(); // El conjunto T de terminales fue generado por SLR1.EXE versión 1. // El analizador lexicográfico debe devolver los códigos cuyos números // se especifican para que funcione correctamente el analizador // sintáctico que aquí se utiliza (generado por SLR1 v1). // T = { ('#', 1), (Ident, 2), (CteCar, 3), (Rango, 4), (' ', 5), // ('?', 6), ('+', 7), ('*', 8), ('(', 9), (')', 10) // Ver definición de las constantes en expreg.h. // A continuación, el código para cargar la expresión regular en memoria // y armar el árbol sintáctico, previo análisis sintáctico de la misma. // Se utiliza el generador de analizadores sintácticos SLR1 v 1. // Para realizar el análisis sintáctico se utiliza la implementación de // analizadores sintácticos SLR(1) preparada para SLR1 v 2. # include "expreg.cmm" // tablas de análisis sintáctico de una exp reg. # include "slr1.h" // analizador SLR(1). class ElemPilaER : public ElemPila { // elemento de la pila para exp reg public: NodoArbol * pnodo; ElemPilaER() { pnodo = 0; ~ElemPilaER(); ; 358

359 Códigos Fuentes de la Biblioteca ExpReg v 1.x ElemPilaER::~ElemPilaER() { if (pnodo!= 0) { delete pnodo; pnodo = 0; // AccSem implementa las acciones semánticas. // Si la regla es Simb0 --> Simb1 Simb2, las posiciones en pila son: // Simb0 y Simb1 están ambos en pila[pp+1], por lo que primero se debe // trabajar sobre Simb1 para luego dejar el resultado al final en Simb0. // Simb2 se encuentra en la posición pila[pp+2], y así sucesivamente. // pila y pp son variables globales. class DescrIdent { public: Ascii0 Cad; Ent8ns Id; DescrIdent() { Id = 0; ; // Variables globales: static LSE<DescrHoja> Hoja; // lista de descripciones de hojas del árbol static LSE<Ascii0> Caracter; // lista de cadenas descriptoras de caracteres static Ent8ns MaxCar; static Ent16ns NumHoja; // número actual de hojas static LSE<DescrIdent> TablaIdent; static NodoArbol * RaizAnalSint; static void InicVariablesGlob() { Hoja.Vaciar(); Caracter.Vaciar(); MaxCar = 0; NumHoja = 0; TablaIdent.Vaciar(); RaizAnalSint = 0; static void ElimComillaCambSecEsc( char * pcad ) { register char * p; if ((p = pcad) == 0! *p) return; while (*p) { *p = *(p+1); // desplazar uno a la izquierda if (*p) ++p; *(p-1) = 0; // elimina último caracter CambSecEsc(pCad); # include <string.h> static void AccSem( Ent16ns numregla, ElemPilaER * pila, Ent16ns pp ) { NodoArbol * pn; NodoLSE<Ascii0> * pcar, * pfinal; Ent8ns c; DescrHoja dh; NodoLSE<DescrIdent> * pdi; if (numregla == 1) { // 1: ExpReg ----> Defs Disy RaizAnalSint = pila[pp+2].pnodo; pila[pp+2].pnodo = 0; 359

360 Códigos Fuentes de la Biblioteca ExpReg v 1.x else if (numregla == 2) { // 2: ExpReg ----> Disy RaizAnalSint = pila[pp+1].pnodo; pila[pp+1].pnodo = 0; else if (numregla == 5 numregla == 6) { // 5: Def > '#' Ident CteCar // 6: Def > '#' Ident Rango for (pdi = TablaIdent.Entrada; pdi!= 0; pdi = pdi->psig) if (! strcmp(pila[pp+2].cadterm, pdi->dato.cad)) break; if (pdi == 0) { pdi = new NodoLSE<DescrIdent>; if (pdi!= 0) { pdi->dato.cad << pila[pp+2].cadterm; if (pila[pp+3].cadterm.long() >= 3) ElimComillaCambSecEsc(pila[pp+3].CadTerm); // Agregar CteCar o Rango a la lista Caracter, si no existe. // Obtener el correspondiente Id del mismo. for (c = 0, pcar = pfinal = Caracter.Entrada; pcar!= 0; pcar = pcar->psig, ++c) { if (! strcmp(pila[pp+3].cadterm, pcar->dato)) break; if (pcar->psig!= 0) pfinal = pcar->psig; if (pcar == 0) { // agregar si no existe pcar = new NodoLSE<Ascii0>; if (pcar!= 0) { pcar->dato << pila[pp+3].cadterm; if (pfinal!= 0) pfinal->psig = pcar; else Caracter.Entrada = pcar; // c queda con el valor que tenía ++MaxCar; // hay un caracter más en la lista else c = 0; pdi->dato.id = c; // Enganchar el descriptor del Ident a la entrada de TablaIdent pdi->psig = TablaIdent.Entrada; TablaIdent.Entrada = pdi; // else... si pdi!= 0, la definición de Ident es incorrecta, // se la ingorará pila[pp+2].cadterm.vaciar(); pila[pp+3].cadterm.vaciar(); else if (numregla == 7 numregla == 9) { // 7: Disy > Disy ' ' Concat // 9: Concat ----> Concat Unario char indice; indice = numregla == 7? 3 : 2; pn = new NodoArbol; if (pn == 0) return; pn->tipo = numregla == 7? N_DIS : N_CON; // ' ' o '.' pn->pizq = pila[pp+1].pnodo; pn->pder = pila[pp + indice].pnodo; pila[pp+1].pnodo = pn; pila[pp + indice].pnodo = 0; // no se usa hasta nuevo aviso else if (numregla == 11 numregla == 12 numregla == 13) { // 11: Unario ----> Par_Car '?' // 12: Unario ----> Par_Car '+' // 13: Unario ----> Par_Car '*' pn = new NodoArbol; if (pn == 0) return; pn->tipo = numregla == 11? N_OPC : (numregla == 12? N_MAS : N_AST); 360

361 Códigos Fuentes de la Biblioteca ExpReg v 1.x pn->pizq = pila[pp+1].pnodo; pila[pp+1].pnodo = pn; else if (numregla == 15) { // 15: Par_Car ---> '(' Disy ')' pila[pp+1].pnodo = pila[pp+2].pnodo; pila[pp+2].pnodo = 0; else if (numregla == 16) { // 16: Par_Car ---> Caracter pn = new NodoArbol; if (pn == 0) return; pn->tipo = NumHoja++; // asigna número de hoja e incrementa NumHoja // Agrega descriptor de hoja: // primero busca caracter en la lista Caracter for (c = 0, pcar = pfinal = Caracter.Entrada; pcar!= 0; pcar = pcar->psig, ++c) { if (! strcmp(pila[pp+1].cadterm, pcar->dato)) break; if (pcar->psig!= 0) pfinal = pcar->psig; if (pcar == 0) { // agregar si no existe pcar = new NodoLSE<Ascii0>; if (pcar!= 0) { pcar->dato << pila[pp+1].cadterm; if (pfinal!= 0) pfinal->psig = pcar; else Caracter.Entrada = pcar; // c queda con el valor que tenía ++MaxCar; // hay un caracter más en la lista else c = 0; dh.id = pn->tipo; dh.idcar = c; Hoja.AgregFinal(dh); // enganchar con elemento actual de la pila LR pila[pp+1].pnodo = pn; // anular campo CadTerm del elemento actual de la pila LR para que no // use memoria pila[pp+1].cadterm.vaciar(); else if (numregla == 17) { // 17: Caracter --> Ident for (pdi = TablaIdent.Entrada; pdi!= 0; pdi = pdi->psig) if (! strcmp(pila[pp+1].cadterm, pdi->dato.cad)) break; if (pdi!= 0) // si Ident fue definido, se cambia Ident por su def. pila[pp+1].cadterm = * Caracter.DatoNodoI( pdi->dato.id ); // sino queda Ident como cte. caracter. else if (numregla == 18) { // 18: Caracter --> CteCar if (pila[pp+1].cadterm.long() >= 3) ElimComillaCambSecEsc(pila[pp+1].CadTerm); else { char * pcad; pcad = (char *) pila[pp+1].cadterm; if (*pcad == '.' &&! *(pcad+1)) { pila[pp+1].cadterm = "^\n"; pila[pp+1].cadterm.reasignar(pila[pp+1].cadterm.tamblq()); else if (numregla == 19) { // 19: Caracter --> Rango if (pila[pp+1].cadterm.long() >= 3) ElimComillaCambSecEsc(pila[pp+1].CadTerm); 361

362 Códigos Fuentes de la Biblioteca ExpReg v 1.x // La función que genera el autómata finito determinístico es la siguiente: # ifndef EXPREG_H # include "expreg.h" # endif int ExpReg::Reasignar( const char * ExpRegular, Ascii0 & CadPosErr ) { if (ExpRegular == 0 *ExpRegular == 0) return 0; Vaciar(); // Iniciar el analizador lexicográfico: AnaLexExpReg.Reiniciar(ExpRegular); // Iniciar variables del analizador sintáctico: InicVariablesGlob(); // Analizador sintáctico SLR(1) AnalSLR1<ElemPilaER> analizador(noterm_long_pd, accion, ir_a, AccSem); // Generar el árbol sintáctico usando el analizador sintáctico: if (! analizador.analizar(analexexpreg, CadPosErr)) { // Los siguientes objetos son tablas generadas por AccSem. // Se debe devolver la memoria usada por ellas. // lista de descriptores de hojas del árbol sintáctico Hoja.Vaciar(); // conjunto de caracteres que aparecieron en la expresión regular Caracter.Vaciar(); // Tabla de identificadores TablaIdent.Vaciar(); return 0; // la memoria del arbol generado la devuelve el destructor // de la clase ElemPilaER al hacer 'delete pnodo'. // RaizAnalSint es la raíz del árbol sintáctico generado por el // analizador. Se debe agregar la concatenación con caracter '#' FinArch. NodoArbol * raiz, * HojaFinal; Ent8ns c; DescrHoja dh; int resultado; resultado = 0; // por defecto, resultado indica que hay problemas. // Primero crear la hoja para el caracter de fin de expresión regular '#' HojaFinal = new NodoArbol; if (HojaFinal == 0) { delete RaizAnalSint; // árbol generado por el analizador sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); return 0; HojaFinal->Tipo = NumHoja; // asigna número de hoja sin incrementar NumHoja // Agrega descriptor de hoja: dh.id = HojaFinal->Tipo; dh.idcar = MaxCar; Hoja.AgregFinal(dh); // Luego crear el nodo raiz raiz = new NodoArbol; if (raiz == 0) { delete RaizAnalSint; // árbol generado por el analizador sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico 362

363 Códigos Fuentes de la Biblioteca ExpReg v 1.x Caracter.Vaciar(); TablaIdent.Vaciar(); return 0; raiz->tipo = N_CON; // '.' raiz->pizq = RaizAnalSint; raiz->pder = HojaFinal; RaizAnalSint = 0; // Ahora se calcula SiguientePos para todas las hojas del árbol LSE<Ent16ns> * Sig; Sig = new LSE<Ent16ns>[ NumHoja ]; // pide un vector de LSE de NumHoja elem if (Sig == 0) { delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); return 0; raiz->calcprimultsig(sig); LSE<Estado> EstadosD; NodoLSE<Estado> * pestact, * pfinal, * pauxest; LSE<Ent16ns> * U; NodoLSE<Ent16ns> * ppos, * ppos2; NodoLSE<DescrHoja> * phoja; Ent16ns NumEst, i, j; // El estado inicial es PrimeraPos(raiz) pestact = new NodoLSE<Estado>; if (pestact == 0) { // falta memoria delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; pestact->dato.pos = new LSE<Ent16ns>; if (pestact->dato.pos == 0) { // falta memoria delete pestact; delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; AgregLSE(* pestact->dato.pos, raiz->prim); pestact->dato.tran = new Ent16ns[NumHoja]; if (pestact->dato.tran == 0) { // falta memoria delete pestact->dato.pos; delete pestact; delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; for (i = 0; i < NumHoja; ++i) pestact->dato.tran[i] = TNDEF; // se agrega el estado inicial a la lista de estados pfinal = EstadosD.Entrada = pestact; NumEst = 1; 363

364 Códigos Fuentes de la Biblioteca ExpReg v 1.x while (pestact!= 0) { for (c = 0; c < MaxCar; ++c) { // para cada símbolo de entrada c hacer U = new LSE<Ent16ns>; if (U == 0) { delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; // en U se guardará la unión de todos los conjuntos SiguientePos(pPos) // para las posiciones ppos del estado actual tales que el símbolo en // esa posición ppos es c. // Se recorre el estado actual para ver si alguna posición corresponde // al símbolo actual c. for (ppos = pestact->dato.pos->entrada; ppos!= 0; ppos = ppos->psig) { // Para cada posición, se busca la misma en la lista descriptora de // hojas; es allí donde dice a qué caracter corresponde. for (phoja = Hoja.Entrada; phoja!= 0; phoja = phoja->psig) // si encuentra el descriptor de la hoja salir if (ppos->dato == phoja->dato.id) break; // Si la hoja corresponde al caracter c, agregar SiguientePos de // esa hoja a U. if (phoja!= 0 && phoja->dato.idcar == c) AgregLSE(*U, Sig[pHoja->Dato.Id]); if (U->Entrada!= 0) { // Si U es un conjunto no vacío, hay transición // Buscar si el estado se repite. Si se repite, pauxest apuntará al // estado previamente agregado, sino pauxest valdrá 0. for (pauxest = EstadosD.Entrada; pauxest!= 0; pauxest = pauxest->psig) { // Para cada estado ya agregado... // si el número de elementos de U es distinto se continua el bucle if (pauxest->dato.pos->numelem()!= U->NumElem()) continue; elemento // sino se busca si cada elemento de U se repite en ese estado. for (ppos2 = U->Entrada; ppos2!= 0; ppos2 = ppos2->psig) { for (ppos = pauxest->dato.pos->entrada; ppos!= 0; ppos = ppos->psig) // Si se encuentra el elemento de U en el estado se sale if (ppos->dato == ppos2->dato) break; // sino, ppos valdrá 0 al salir del for y al menos un // de U no está en el actual actual estado ése. if (ppos == 0) break; // salir, este estado no es igual al // si U coincide con el estado actual, el estado no es nuevo if (ppos2 == 0) { // si todos los elementos de U están en el U->Vaciar(); delete U; U = 0; // apuntado por pauxest, eliminar U y usar 364

365 Códigos Fuentes de la Biblioteca ExpReg v 1.x break; // salir del for, estado repetido // si pauxest == 0 el estado es nuevo if (pauxest == 0) { // crear un estado nuevo pauxest = new NodoLSE<Estado>; if (pauxest == 0) { // falta memoria delete U; delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; pauxest->dato.pos = U; // se usa U, posiciones del estado U = 0; // y U se pone a 0 para la siguiente iteración pauxest->dato.id = NumEst++; pauxest->dato.tran = new Ent16ns[NumHoja]; if (pauxest->dato.tran == 0) { // falta memoria delete pauxest->dato.pos; // antes era U delete pauxest; delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; for (i = 0; i < NumHoja; ++i) pauxest->dato.tran[i] = TNDEF; // enganchar estado al final de la lista de estados pfinal->psig = pauxest; pfinal = pauxest; // Del actual se transiciona al estado apuntado por pauxest. pestact->dato.tran[c] = pauxest->dato.id; else { // no hay transición para el caracter c, U es vacío delete U; U = 0; // for (c =... pestact = pestact->psig; // avanzar al siguiente estado // while afd = new AutomFinDet; if (afd == 0) { delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; apu = 0; afd->expregular = ExpRegular; afd->caracter = 0; afd->numcar = MaxCar; 365

366 Códigos Fuentes de la Biblioteca ExpReg v 1.x afd->transicion = 0; // AutomFinDet no tiene constructor por defecto afd->estfinales = 0; // " " Ent16ns * Transicion, * EstFinales, NumEstFinales, * TranEstAct; Transicion = new Ent16ns[NumEst * MaxCar]; if (Transicion == 0) { Vaciar(); delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; afd->transicion = Transicion; EstFinales = new Ent16ns[NumEst + 1]; if (EstFinales == 0) { Vaciar(); delete raiz; // árbol sintáctico Hoja.Vaciar(); // lista de descriptores de hojas del árbol sintáctico Caracter.Vaciar(); TablaIdent.Vaciar(); EstadosD.Vaciar(); // Conjunto de estados delete [] Sig; // Vector de SiguientePos(h) para cada hoja h return 0; afd->estfinales = EstFinales; for (pestact = EstadosD.Entrada, i = 0, NumEstFinales = 0; pestact!= 0; pestact = pestact->psig) { // Copiar el vector de transiciones del estado actual a la // tabla de transiciones que se está armando. TranEstAct = pestact->dato.tran; for (j = 0; j < MaxCar; ++j, ++i) Transicion[i] = TranEstAct[j]; // Luego verificar si el estado actual es un estado final for (ppos = pestact->dato.pos->entrada; ppos!= 0; ppos = ppos->psig) { for (phoja = Hoja.Entrada; phoja!= 0; phoja = phoja->psig) // si encuentra el descriptor de la hoja salir if (ppos->dato == phoja->dato.id) break; if (phoja!= 0 && phoja->dato.idcar == MaxCar) { // se incrementa el número de estados finales y se guarda en vector // de estados finales. EstFinales[++NumEstFinales] = pestact->dato.id; break; // salir del for, ya se sabe que el estado actual es final EstFinales[0] = NumEstFinales; // ahora realizar una copia de los caracteres encontrados en la exp reg char ** pvcar; Ascii0 * pcari; pvcar = new char *[MaxCar]; if (pvcar!= 0) { for (i = 0; i < MaxCar; ++i) { pcari = Caracter.DatoNodoI(i); if (pcari!= 0) { pvcar[i] = new char[pcari->long() + 1]; if (pvcar[i]!= 0) 366

367 Códigos Fuentes de la Biblioteca ExpReg v 1.x strncpy(pvcar[i], *pcari, pcari->long() + 1); afd->caracter = (const char **) pvcar; resultado = 1; // Eliminación de la memoria utilizada por este módulo (se realiza en // orden inverso al que aparecieron las variables): // Conjunto de estados EstadosD.Vaciar(); // Vector de conjuntos SiguientePos(h) para cada hoja h delete [] Sig; // árbol sintáctico delete raiz; // lista de descriptores de hojas del árbol sintáctico Hoja.Vaciar(); // conjunto de caracteres que aparecieron en la expresión regular Caracter.Vaciar(); // Tabla de identificadores TablaIdent.Vaciar(); return resultado; SECESC1.CMM // secesc1.cmm Jue 19 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. static const char * cad[] = { "\\a", // campana, 7 "\\b", // backspace, 8 "\\t", // tab, 9 "\\n", // nueva l nea, 10 "\\v", // tab vertical, 11 "\\f", // salto de p gina, 12 "\\r", // retorno de carro, 13 "\\\\", // \, 92 "\\\'", // comilla simple, 39 "\\\"" // comilla doble, 34 ; const char * CadSecEscape( char c ) { register const char * res; if (c >= 7 && c <= 13) res = cad[c-7]; else if (c == '\\') res = cad[7]; else if (c == '\'') res = cad[8]; else if (c == '\"') res = cad[9]; else res = 0; return res; char CarSecEscape( const char * pcad ) { if (*pcad!= '\\') return 0; register char c; switch ( *(pcad+1) ) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; 367

368 Códigos Fuentes de la Biblioteca ExpReg v 1.x case 't': c = '\t'; break; case 'n': c = '\n'; break; case 'v': c = '\v'; break; case 'f': c = '\f'; break; case 'r': c = '\r'; break; case '\\': c = '\\'; break; case '\'': c = '\''; break; case '\"': c = '\"'; break; default: c = 0; return c; SECESC2.CMM // secesc2.cmm Jue 19 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include <archivo.h> # include "expreg.h" int ImprCaracter( Archivo & arch, char c ) { const char * carsecesc; if (! c) return 0; carsecesc = CadSecEscape(c); if (carsecesc!= 0) arch << carsecesc; else arch << c; return CodError; int ImprCadena( Archivo & arch, const char * cad ) { register const char * p; if ((p = cad) == 0) return 0; while (*p) ImprCaracter(arch, *p++); return 0; SECESC3.CMM // secesc3.cmm Vie 20 Ene 95 // Copyright (c) 1995 by Domingo Becker. // All rights reserved. # include "expreg.h" int CambSecEsc( char * pcad ) { if (pcad == 0) return 0; register char * p, * q, carsecesc; for (p = pcad; *p; ++p) if (*p == '\\') { // cambiar '\Eabc' a 'eabc' carsecesc = CarSecEscape(p); // cambia \E en e if (carsecesc) { *p = carsecesc; // cambia '\Eabc' en 'eeabc' for (q = p+1; *q; ++q) // cambia 'eeabc' en 'eabc' *q = *(q+1); 368

369 Códigos Fuentes de la Biblioteca ExpReg v 1.x return 1; 369

370 Códigos Fuentes de AFD v 3.x Capítulo 22: Códigos Fuentes de AFD v 3.x AFD v 3.x, al igual que las versiones anteriores, ha sido diseñado como un programa del usuario que hace uso de la biblioteca ExpReg de soporte para la generación de autómatas finitos determinísticos. De esta manera es posible la reutilización del generador de AFDs en cualquier programa del usuario. Esta implementación es dependiente del sistema operativo. Los módulos fuente aquí presentados pueden ser compilados con Borland C++ para Windows (4.02 o posterior) o para OS/2 (2.0 o posterior). Para el modo gráfico se utiliza la biblioteca OWL v 2.x que viene incluida en esos compiladores, ganando así portabilidad entre Windows y Presentation Manager. Para el modo caracter se utiliza la biblioteca BCE, ganando así la portabilidad a cualquier sistema operativo que disponga de una versión de BCE. A los efectos de no repetir código fuente dentro de este trabajo, cuando sea aplicable, se introducirán referencias hacia otras partes dentro de este documento, en donde era oportuno incluirlos AFDD.CMM Se puede generar una aplicación a partir de este módulo, compilándolo y encadenándolo con la biblioteca BCE, la biblioteca de soporte ExpReg y las bibliotecas estándar. Este módulo no debe ser incluídos con el resto de módulos presentado en este capítulo. Esta implementación está basada en la que se propone en la sección 6.7. // afdd.cmm 23Feb96 // Implementación de la versión para DOS, OS/2 modo caracter y Windows 95 // y NT modo caracter de AFD # include "expreg.h" # include <archivo.h> int main(int narg, char * carg[]) { salida << "AFD v 3.4 para modo caracter (Feb 96)\nD.E.Becker Tel (085) / \n"; if (narg < 2) { salida << "Falta el nombre del archivo con la expresion regular"; return 1; // Determinar cadenas postfijos de los nombres: const char * PostfijoObj, * PostfijoSubObj; 370

371 Códigos Fuentes de AFD v 3.x PostfijoObj = carg[2]; PostfijoSubObj = narg > 3? carg[3] : PostfijoObj; // Cargar archivo en memoria: ArchivoSO arch; Ent16ns tam; Ascii0 texto; if (arch.abrir(carg[1], MA_L, MD_BIN)) { salida << "ERROR al intentar abrir " << carg[1] << "\n"; return 1; tam = (Ent16ns) arch.tam(); if (! texto.reasignar(tam + 1)) { salida << "Falta memoria para cargar la expresión regular en memoria.\n"; return 1; if (arch.leer(texto, tam), CodError) { salida << "ERROR al intentar leer el archivo de entrada."; return 1; *texto[tam] = 0; if (! texto.long()) { salida << "ERROR: el archivo no contiene una expresion regular."; return 1; // Probar generar el AFD a partir del texto fuente: ExpReg er; Ascii0 CadPosErr; if (! er.reasignar(texto, CadPosErr)) { salida << "ERROR de sintaxis en la expresion regular:\n" << CadPosErr; return 1; // Guardar fuente C++ if (! er.imprfuente(nomext(carg[1], ".cmm"), PostfijoObj, PostfijoSubObj)) salida << "Error al abrir archivo C++.\n"; // Guardar archivo de texto con autómata arch.cerrar(); if (arch.abrir(nomext(carg[1], ".txt"), MA_E, MD_TXT)) salida << "No se genero archivo de texto.\n"; else { er.imprimir(arch); arch.cerrar(); // Mensajes finales: salida << "Automata generado sin problemas.\n"; salida << NomExt(cArg[1], ".cmm") << " contiene el fuente C++.\n"; salida << NomExt(cArg[1], ".txt") << " contiene el AFD en formato legible.\n"; salida << "\ngracias por usar AFD."; return 0; 371

372 Códigos Fuentes de AFD v 3.x 22.2 AFDEDTVW.CMM /* Project AFD Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: afd.exe Application afdedtvw.cmm Domingo Eduardo Becker */ OVERVIEW ======== Source file for implementation of AFDEditView (TEditView). #include <owl\owlpch.h> #pragma hdrstop #include "afdapp.h" #include "afdedtvw.h" #include <stdio.h> # include "dlgdatos.h" # include "dlgerror.h" # ifndef ASCII0_H # include <ascii0.h> # endif # ifndef ARCHIVO_H # include <archivo.h> # endif # ifndef EXPREG_H # include "expreg.h" # endif //{{AFDEditView Implementation // // Build a response table for all messages/commands handled // by AFDEditView derived from TEditView. // DEFINE_RESPONSE_TABLE1(AFDEditView, TEditView) //{{AFDEditViewRSP_TBL_BEGIN EV_WM_GETMINMAXINFO, EV_COMMAND(CM_GENERARAFD, GenerarAFD), //{{AFDEditViewRSP_TBL_END END_RESPONSE_TABLE; ////////////////////////////////////////////////////////// // AFDEditView // ========== // Construction/Destruction handling. AFDEditView::AFDEditView (TDocument& doc, TWindow* parent) : TEditView(doc, parent) { // INSERT>> Your constructor code here. 372

373 Códigos Fuentes de AFD v 3.x AFDEditView::~AFDEditView () { // INSERT>> Your destructor code here. // // Paint routine for Window, Printer, and PrintPreview for a TEditView client. // void AFDEditView::Paint (TDC& dc, bool, TRect& rect) { AFDApp *theapp = TYPESAFE_DOWNCAST(GetApplication(), AFDApp); if (theapp) { // Only paint if we're printing and we have something to paint, otherwise do nothing. if (theapp->printing && theapp->printer &&!rect.isempty()) { // Use pagesize to get the size of the window to render into. For a Window it's the client area, // for a printer it's the printer DC dimensions and for print preview it's the layout window. TSize pagesize(rect.right - rect.left, rect.bottom - rect.top); HFONT hfont = (HFONT)GetWindowFont(); TFont font("arial", -12); if (hfont == 0) dc.selectobject(font); else dc.selectobject(tfont(hfont)); TEXTMETRIC tm; int fheight = (dc.gettextmetrics(tm) == true)? tm.tmheight + tm.tmexternalleading : 10; // How many lines of this font can we fit on a page. int linesperpage = MulDiv(pageSize.cy, 1, fheight); if (linesperpage) { TPrintDialog::TData &printerdata = theapp->printer->getsetup(); int maxpg = ((GetNumLines() / linesperpage) + 1.0); // Compute the number of pages to print. printerdata.minpage = 1; printerdata.maxpage = maxpg; // Do the text stuff: int frompage = printerdata.frompage == -1? 1 : printerdata.frompage; int topage = printerdata.topage == -1? 1 : printerdata.topage; char buffer[255]; int currentpage = frompage; while (currentpage <= topage) { int startline = (currentpage - 1) * linesperpage; int lineidx = 0; while (lineidx < linesperpage) { // If the string is no longer valid then there's nothing more to display. if (!GetLine(buffer, sizeof(buffer), startline + lineidx)) 373

374 Códigos Fuentes de AFD v 3.x break; dc.tabbedtextout(tpoint(0, lineidx * fheight), buffer, strlen(buffer), 0, NULL, 0); lineidx++; currentpage++; void AFDEditView::EvGetMinMaxInfo (MINMAXINFO far& minmaxinfo) { AFDApp *theapp = TYPESAFE_DOWNCAST(GetApplication(), AFDApp); if (theapp) { if (theapp->printing) { minmaxinfo.ptmaxsize = TPoint(32000, 32000); minmaxinfo.ptmaxtracksize = TPoint(32000, 32000); return; TEditView::EvGetMinMaxInfo(minmaxinfo); void AFDEditView::GenerarAFD () { // INSERT>> Your code here. // Primero sacar el texto del TEditView Ent16ns tam = GetTextLen() + 1; Ascii0 cad(tam), CadPosErr; if (! GetText(cad, tam)) return; // si no hay texto editado // Crear una ventana de diálogo para pregunar más datos: OtrosDatosAFD * pdlg = new OtrosDatosAFD(this); Parent->GetWindowTextTitle(); pdlg->archtxt = Parent->Title; if (pdlg->execute() == IDCANCEL) { MessageBox("Autómata no generado.", "OBSERVACION", MB_OK MB_ICONINFORMATION); return; // Probar generar el AFD a partir del texto fuente: ExpReg er; if (! er.reasignar(cad, CadPosErr)) { TDlgError * pdlgerror = new TDlgError(this); pdlgerror->cad = CadPosErr; pdlgerror->execute(); delete pdlgerror; return; // Guardar fuente C++ if (pdlg->generarcmm && pdlg->archcmm.long() &&! er.imprfuente(pdlg->archcmm, pdlg->postfijoafd, pdlg->postfijoresto)) { MessageBox("Error al abrir archivo C++.", "ERROR", MB_ICONHAND MB_OK); return; // Guardar archivo de texto con autómata ArchivoSO arch; 374

375 Códigos Fuentes de AFD v 3.x { if (! pdlg->archtxt.long() arch.abrir(pdlg->archtxt, MA_E, MD_TXT)) MessageBox("No se generó archivo de texto.", "ERROR", MB_ICONHAND MB_OK); return; er.imprimir(arch); arch.cerrar(); // Mensaje final MessageBox("Autómata Finito Determinístico\ngenerado sin problemas.", "Resultado", MB_OK MB_ICONINFORMATION); 22.3 DLGDATOS.H #if!defined( dlgdatos_h) not already included. #define dlgdatos_h // Sentry, use file only if it's /* Project afd Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: afd.apx Application dlgdatos.h Domingo Eduardo Becker */ OVERVIEW ======== Class definition for OtrosDatosAFD (TDialog). #include <owl\owlpch.h> #pragma hdrstop #include <owl\checkbox.h> #include "afdapp.rh" // Definition of all resources. # ifndef ASCII0_H # include <ascii0.h> # endif //{{TDialog = OtrosDatosAFD struct OtrosDatosAFDXfer { //{{OtrosDatosAFDXFER_DATA bool ChkGenCMM; //{{OtrosDatosAFDXFER_DATA_END ; class OtrosDatosAFD : public TDialog { public: OtrosDatosAFD (TWindow* parent, TResId resid = IDD_DLGMASDATOS, TModule* module = 0); virtual ~OtrosDatosAFD (); Ascii0 ArchCMM, ArchTXT, PostfijoAFD, PostfijoResto; Ent8 GenerarCMM; 375

376 Códigos Fuentes de AFD v 3.x //{{OtrosDatosAFDVIRTUAL_BEGIN public: virtual void SetupWindow (); //{{OtrosDatosAFDVIRTUAL_END //{{OtrosDatosAFDXFER_DEF protected: TCheckBox *ChkGenCMM; //{{OtrosDatosAFDXFER_DEF_END //{{OtrosDatosAFDRSP_TBL_BEGIN protected: void Aceptar (); //{{OtrosDatosAFDRSP_TBL_END DECLARE_RESPONSE_TABLE(OtrosDatosAFD); ; //{{OtrosDatosAFD #endif // dlgdatos_h sentry DLGDATOS.CMM El diseño del diálogo donde se introducen los datos faltantes es el siguiente: /* Project afd Fig. 38 Diseño del diálogo para la entrada de datos adicionales usados por AFD v 3.x Copyright All Rights Reserved. SUBSYSTEM: FILE: AUTHOR: afd.apx Application dlgdatos.cmm Domingo Eduardo Becker OVERVIEW 376

Capítulo 4: Algoritmos usados por el Generador de Analizadores Sintácticos

Capítulo 4: Algoritmos usados por el Generador de Analizadores Sintácticos Capítulo 4: Algoritmos usados por el Generador de Analizadores Sintácticos 4.1 Introducción En este capítulo se presentan los algoritmos usados por el Generador de Analizadores Sintácticos SLR. Se tratará

Más detalles

Capítulo 3: Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos

Capítulo 3: Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos Capítulo 3: Algoritmo Uado por el Generador de Autómata Finito Determinítico 3.1 Introducción En ete capítulo e preentan lo algoritmo uado por el generador de autómata finito determinítico que irve como

Más detalles

Analizadores sintácticos LR(0) y SLR

Analizadores sintácticos LR(0) y SLR Teoría de Lenguajes Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires Clase de Hoy Anteriores: Parsing descendente (LL(1), ELL) Recursivos e iterativos Generan árbol de derivación desde

Más detalles

Lenguajes y Compiladores Aspectos Formales (Parte 1) Compiladores

Lenguajes y Compiladores Aspectos Formales (Parte 1) Compiladores Facultad de Ingeniería de Sistemas Lenguajes y Aspectos Formales (Parte 1) 1 Aspectos Formales Los compiladores traducen lenguajes que están formalmente definidos a través de reglas que permiten escribir

Más detalles

Tema 5. Análisis sintáctico ascendente

Tema 5. Análisis sintáctico ascendente Tema 5 Análisis sintáctico Ciencias de la Computación e Inteligencia Artificial Índice 5.1 Introducción 5.2 Análisis sintáctico por desplazamiento y reducción 5.3 El autómata reconocedor de prefijos viables

Más detalles

16 Análisis sintáctico I

16 Análisis sintáctico I 2 Contenido Recordando la estructura de un compilador Recordando el análisis léxico l análisis sintáctico Comparación con el análisis léxico l Rol del Parser Lenguajes de programación Gramáticas structura

Más detalles

Proyecto Intermedio Algoritmo de Earley

Proyecto Intermedio Algoritmo de Earley Fundamentos de Computación Proyecto Intermedio: Algoritmo de Earley Profesor: Dr. José Torres Jiménez Alumnos: Edna Gutiérrez Gasca Aureny Magaly Uc Miam Jorge Rodríguez Núñez Proyecto Intermedio Algoritmo

Más detalles

Tema 4: Gramáticas independientes del contexto. Teoría de autómatas y lenguajes formales I

Tema 4: Gramáticas independientes del contexto. Teoría de autómatas y lenguajes formales I Tema 4: Gramáticas independientes del contexto Teoría de autómatas y lenguajes formales I Bibliografía Hopcroft, J. E., Motwani, R., y Ullman, J. D. Introducción a la Teoría de Autómatas, Lenguajes y Computación.

Más detalles

Software para la Enseñanza de las Fases de Análisis Léxico y Análisis Sintáctico en Procesadores de Lenguajes

Software para la Enseñanza de las Fases de Análisis Léxico y Análisis Sintáctico en Procesadores de Lenguajes Software para la Enseñanza de las Fases de Análisis Léxico y Análisis Sintáctico en Procesadores de Lenguajes Manual de usuario Realizado por: José Francisco Jódar Reyes Dirigido por: Jorge Revelles Moreno

Más detalles

Pontificia Universidad Católica del Ecuador

Pontificia Universidad Católica del Ecuador 1. DATOS INFORMATIVOS: MATERIA O MÓDULO: COMPILADORES E INTERPRETES CÓDIGO: 10730 CARRERA: NIVEL: SISTEMAS QUINTO No. CRÉDITOS: 4 CRÉDITOS TEORÍA: 4 CRÉDITOS PRÁCTICA: - SEMESTRE / AÑO ACADÉMICO: 2 / 2010

Más detalles

Lenguajes y Compiladores Aspectos Formales (Parte 2) Compiladores

Lenguajes y Compiladores Aspectos Formales (Parte 2) Compiladores Facultad de Ingeniería de Sistemas Lenguajes y Aspectos Formales (Parte 2) 2007 1 Derivaciones El proceso de búsqueda de un árbol sintáctico para una cadena se llama análisis sintáctico. El lenguaje generado

Más detalles

Capítulo 9. Introducción a los lenguajes formales. Continuar

Capítulo 9. Introducción a los lenguajes formales. Continuar Capítulo 9. Introducción a los lenguajes formales Continuar Introducción Un lenguaje es un conjunto de símbolos y métodos para estructurar y combinar dichos símbolos. Un lenguaje también recibe el nombre

Más detalles

Contenido. Capítulo 1. Teoría de conjuntos. 1. Capítulo 2. Lenguaje. 39. Capítulo 3. Lenguajes formales. 55

Contenido. Capítulo 1. Teoría de conjuntos. 1. Capítulo 2. Lenguaje. 39. Capítulo 3. Lenguajes formales. 55 Contenido Capítulo 1. Teoría de conjuntos. 1 1.1 Conjuntos.... 3 1.1.1 Definiciones básicas.... 3 1.1.2 Operaciones sobre conjuntos.... 6 1.1.3 Diagrama de Venn.... 7 1.1.4 Álgebra de conjuntos.... 7 1.2

Más detalles

Tema: Análisis Sintáctico LR

Tema: Análisis Sintáctico LR Compiladores. Guía 4 1 Facultad: Ingeniería Escuela: Computación Asignatura: Compiladores Tema: Análisis Sintáctico LR Contenido En esta guía se abordarán los conceptos pertenecientes al componente de

Más detalles

LENGUAJES Y GRAMÁTICAS

LENGUAJES Y GRAMÁTICAS LENGUAJES Y GRAMÁTICAS Orlando Arboleda Molina Escuela de Ingeniería de Sistemas y Computación de La Universidad del Valle 20 de septiembre de 2008 Contenido Lenguajes y Gramáticas Gramáticas Gramáticas

Más detalles

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 2.

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 2. UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes Tema 2 Análisis Léxico Javier Vélez Reyes jvelez@lsi.uned.es Objetivos del Tema

Más detalles

Controla el flujo de tokens reconocidos por parte del analizador léxico. 4.2 Introduccion a las gramaticas libres de contexto y arboles de derivacion

Controla el flujo de tokens reconocidos por parte del analizador léxico. 4.2 Introduccion a las gramaticas libres de contexto y arboles de derivacion UNIDAD IV Analisis Sintactico 4.1 Introduccion Sintaxis significa estructura del orden de las palabras en una frase. La tarea del analisis sintactico es revisar si los símbolos aparecen en el orden correcto

Más detalles

Tema 1. Introducción

Tema 1. Introducción Departamento de Tecnologías de la Información Tema 1 Introducción Ciencias de la Computación e Inteligencia Artificial Índice 1.1 Conceptos 1.2 Un poco de historia 1.3 Estructura de un compilador 1.4 Teoría

Más detalles

1. Define que es un Autómatas finitos determinanticos y cuáles son sus elementos constitutivos (explique cada uno de ellos).

1. Define que es un Autómatas finitos determinanticos y cuáles son sus elementos constitutivos (explique cada uno de ellos). Unidad 2.- Lenguajes Regulares Los lenguajes regulares sobre un alfabeto dado _ son todos los lenguajes que Se pueden formar a partir de los lenguajes básicos?, {_}, {a}, a 2 _, por medio De las operaciones

Más detalles

Tema 2 Conceptos básicos de programación. Fundamentos de Informática

Tema 2 Conceptos básicos de programación. Fundamentos de Informática Tema 2 Conceptos básicos de programación Fundamentos de Informática Índice Metodología de la programación Programación estructurada 2 Pasos a seguir para el desarrollo de un programa (fases): Análisis

Más detalles

Unidad I Introducción a la programación de Sistemas. M.C. Juan Carlos Olivares Rojas

Unidad I Introducción a la programación de Sistemas. M.C. Juan Carlos Olivares Rojas Unidad I Introducción a la programación de Sistemas M.C. Juan Carlos Olivares Rojas Agenda 1.1 Qué es y que estudia la programación de sistemas? 1.2 Herramientas desarrolladas con la teoría de programación

Más detalles

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V.

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Compiladores: Análisis Sintáctico Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Parsing LR: Ejemplo: E E + T E T T T * id T id S n : shift al estado

Más detalles

Analizador Sintáctico Ascendente

Analizador Sintáctico Ascendente Analizador Sintáctico Ascente Un Analizador Sintáctico (A. St.) Ascente construye el árbol desde las hojas hacia la raíz. Funciona por reducción-desplazamiento, lo cual quiere decir que, siempre que puede,

Más detalles

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 3.

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 3. UNIVRSIDAD NACIONAL D DUCACIÓN A DISTANCIA scuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes Tema 3 Parte I Análisis Sintáctico Javier Vélez Reyes jvelez@lsi.uned.es Objetivos

Más detalles

18 Análisis sintáctico III Compiladores - Profr. Edgardo Adrián Franco Martínez. Clasificación de métodos de análisis sintáctico Análisis descendente

18 Análisis sintáctico III Compiladores - Profr. Edgardo Adrián Franco Martínez. Clasificación de métodos de análisis sintáctico Análisis descendente 2 Contenido Clasificación de métodos de análisis sintáctico Análisis descendente Análisis descendente recursivo Análisis descendente predictivo Métodos deterministas Problemas del análisis descendente

Más detalles

Procesadores de Lenguaje

Procesadores de Lenguaje Procesadores de Lenguaje Analizadores sintácticos descendentes: LL(1) Cristina Tîrnăucă Dept. Matesco, Universidad de Cantabria Fac. Ciencias Ing. Informática Primavera de 2013 Analizadores sintácticos

Más detalles

Nombre de la asignatura: Lenguajes y Autómatas I. Créditos: Aportación al perfil

Nombre de la asignatura: Lenguajes y Autómatas I. Créditos: Aportación al perfil Nombre de la asignatura: Lenguajes y Autómatas I Créditos: 2 3 5 Aportación al perfil Desarrollar, implementar y administrar software de sistemas o de aplicación que cumpla con los estándares de calidad

Más detalles

El proceso del Análisis Léxico

El proceso del Análisis Léxico El proceso del Análisis Léxico El proceso de análisis léxico se refiere al trabajo que realiza el scanner con relación al proceso de compilación. El scanner representa una interfaz entre el programa fuente

Más detalles

ÁRBOLES DE SINTAXIS. Los nodos no terminales (nodos interiores) están rotulados por los símbolos no terminales.

ÁRBOLES DE SINTAXIS. Los nodos no terminales (nodos interiores) están rotulados por los símbolos no terminales. ÁRBOLES DE SINTAXIS ÁRBOL grafo dirigido acíclico. Los nodos no terminales (nodos interiores) están rotulados por los símbolos no terminales. Los nodos terminales (nodos hojas) están rotulados por los

Más detalles

Conceptos básicos sobre gramáticas

Conceptos básicos sobre gramáticas Procesamiento de Lenguajes (PL) Curso 2014/2015 Conceptos básicos sobre gramáticas Gramáticas y lenguajes Gramáticas Dado un alfabeto Σ, un lenguaje es un conjunto (finito o infinito) de cadenas de símbolos

Más detalles

Introducción a la programación: Contenido. Introducción

Introducción a la programación: Contenido. Introducción Introducción a la programación: Contenido Introducción a la programación:... 1 Introducción... 1 1. Procesamiento automatizado de información... 1 2. Concepto de algoritmo.... 2 3. Lenguajes de programación....

Más detalles

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V.

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Compiladores: Análisis Sintáctico Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Análizador Sintáctico de abajo hacia arriba Es un proceso de Reducción,

Más detalles

GRAMATICAS LIBRES DEL CONTEXTO

GRAMATICAS LIBRES DEL CONTEXTO GRMTICS LIBRES DEL CONTEXTO Estas gramáticas, conocidas también como gramáticas de tipo 2 o gramáticas independientes del contexto, son las que generan los lenguajes libres o independientes del contexto.

Más detalles

Ciencias de la Computación I

Ciencias de la Computación I Ciencias de la Computación I Gramáticas Regulares Expresiones Regulares Gramáticas - Intuitivamente una gramática es un conjunto de reglas para formar correctamente las frases de un lenguaje - Por ejemplo,

Más detalles

Introducción al Diseño de Compiladores. Año

Introducción al Diseño de Compiladores. Año Introducción al Diseño de Compiladores Año 2003 1 BIBLIOGRAFÍA [AHO] Compilers. Principles, Techniques, and Tools Aho, Sethi; Adisson-Wesley 1986 [TEU] Compiladores: Conceptos fundamentales. Teufel ; Addison

Más detalles

Máquinas de estado finito y expresiones regulares

Máquinas de estado finito y expresiones regulares Capítulo 3 Máquinas de estado finito y expresiones regulares En este tema definiremos y estudiaremos máquinas de estado finito, llamadas también máquinas de estado finito secuenciales o autómatas finitos.

Más detalles

NOTAS PARA LA MATERIA LENGUAJES DE PROGRAMACIÓN

NOTAS PARA LA MATERIA LENGUAJES DE PROGRAMACIÓN NOTAS PARA LA MATERIA LENGUAJES DE PROGRAMACIÓN G r a m á t i c a s UNIVERSIDAD DE SONORA DEPARTAMENTO DE MATEMÁTICAS LICENCIATURA EN CIENCIAS DE LA COMPUTACIÓN Dra. María de Guadalupe Cota Ortiz Lenguaje

Más detalles

Lenguajes y Compiladores Análisis Sintáctico Parte I. Teoría Lenguajes 1

Lenguajes y Compiladores Análisis Sintáctico Parte I. Teoría Lenguajes 1 Facultad de Ingeniería de Sistemas Lenguajes y Compiladores Análisis Sintáctico Parte I 1 Introducción El analizador sintáctico recibe una secuencia de tokens y decide si la secuencia está correcta o no.

Más detalles

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 4

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. Tema 4 UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes Tema 4 Análisis Sintáctico Ascendente Javier Vélez Reyes jvelez@lsi.uned.es Objetivos

Más detalles

NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación

NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación UNIVERSIDAD DE SAN CARLOS DE GUATEMALA FACULTAD DE INGENIERIA ESCUELA DE CIENCIAS Y SISTEMAS NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación CODIGO: 796 CREDITOS: 3 ESCUELA: Ciencias

Más detalles

PROGRAMA DE LABORATORIO SECCIÓN: ÁREA A LA QUE PERTENECE: POS-REQUISITO: AUXILIAR:

PROGRAMA DE LABORATORIO SECCIÓN: ÁREA A LA QUE PERTENECE: POS-REQUISITO: AUXILIAR: UNIVERSIDAD DE SAN CARLOS DE GUATEMALA FACULTAD DE INGENIERÍA ESCUELA DE CIENCIAS PROGRAMA DE LABORATORIO CÓDIGO: 777 CRÉDITOS: 4 NOMBRE CURSO: ESCUELA: PRE-REQUISITO: Organización de Lenguajes y Compiladores

Más detalles

Introducción a la lingüística computacional

Introducción a la lingüística computacional Introducción a la lingüística computacional César Antonio Aguilar Facultad de Lenguas y Letras 17/08/2017 Cesar.Aguilar72@gmail.com Síntesis de la clase pasada (1) En la clase anterior nos dedicamos a

Más detalles

Tema 1: Introducción. Teoría de autómatas y lenguajes formales I

Tema 1: Introducción. Teoría de autómatas y lenguajes formales I Tema 1: Introducción Teoría de autómatas y lenguajes formales I Bibliografía Hopcroft, J. E., Motwani, R., y Ullman, J. D. Introducción a la Teoría de Autómatas, Lenguajes y Computación. Addison Wesley.

Más detalles

Análisis sintáctico Analizadores descendentes

Análisis sintáctico Analizadores descendentes Procesadores de Lenguajes Ingeniería Técnica superior de Ingeniería Informática Departamento de Lenguajes y Sistemas informáticos Análisis sintáctico Analizadores descendentes Javier Vélez Reyes jvelez@lsi.uned.es

Más detalles

TIPOS DE GRAMATICAS JERARQUIAS DE CHOMSKY

TIPOS DE GRAMATICAS JERARQUIAS DE CHOMSKY TIPOS DE GRAMATICAS JERARQUIAS DE CHOMSKY Para el estudio de este tema es necesario analizar dos tipos de gramáticas de la clasificación de Chomsky, las regulares y las independientes de contexto, las

Más detalles

ANÁLISIS SINTÁCTICO I ANÁLISIS SINTÁCTICO DESCENDENTE LL(1)

ANÁLISIS SINTÁCTICO I ANÁLISIS SINTÁCTICO DESCENDENTE LL(1) Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

ANÁLISIS SINTÁCTICO II LR1

ANÁLISIS SINTÁCTICO II LR1 Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

2.1 METODOLOGÍA PARA LA SOLUCIÓN DE PROBLEMAS

2.1 METODOLOGÍA PARA LA SOLUCIÓN DE PROBLEMAS 2.1 METODOLOGÍA PARA LA SOLUCIÓN DE PROBLEMAS El proceso de resolución de un problema con una computadora conduce a la escritura de un programa y su ejecución en la misma. Aunque el proceso de diseñar

Más detalles

Tema 2 Introducción a la Programación en C.

Tema 2 Introducción a la Programación en C. Tema 2 Introducción a la Programación en C. Contenidos 1. Conceptos Básicos 1.1 Definiciones. 1.2 El Proceso de Desarrollo de Software. 2. Lenguajes de Programación. 2.1 Definición y Tipos de Lenguajes

Más detalles

SSL Guia de Ejercicios

SSL Guia de Ejercicios 1 SSL Guia de Ejercicios INTRODUCCIÓN A LENGUAJES FORMALES 1. Dado el alfabeto = {a, b, c}, escriba las palabras del lenguaje L = {x / x }. 2. Cuál es la cardinalidad del lenguaje L = {, a, aa, aaa}? 3.

Más detalles

ANÁLISIS LÉXICO Ing. Ronald Rentería Ayquipa

ANÁLISIS LÉXICO Ing. Ronald Rentería Ayquipa ANÁLISIS LÉXICO Ing. Ronald Rentería Ayquipa ANALIZADOR LÉXICO (AL) El Analizador léxico (scanner), lee la secuencia de caracteres del programa fuente, caracter a caracter, y los agrupa para formar unidades

Más detalles

Gramáticas libres de contexto

Gramáticas libres de contexto Gramáticas libres de contexto Conceptos básicos El siguientes es un ejemplo de una gramática libre de contexto, a la cual llamaremos G1. A 0A1 A B B # Una gramática consiste de una colección de reglas

Más detalles

ANÁLISIS SINTÁCTICO II SLR

ANÁLISIS SINTÁCTICO II SLR Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación

NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación UNIVERSIDAD DE SAN CARLOS DE GUATEMALA FACULTAD DE INGENIERIA ESCUELA DE CIENCIAS Y SISTEMAS NOMBRE DEL CURSO: Laboratorio de Lenguajes Formales y de Programación CODIGO: 796 CREDITOS: 3 ESCUELA: Ciencias

Más detalles

UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO FACULTAD DE INGENIERÍA PROGRAMA DE ESTUDIO

UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO FACULTAD DE INGENIERÍA PROGRAMA DE ESTUDIO UNIVERSIDAD NACIONAL AUTÓNOMA DE MÉXICO FACULTAD DE INGENIERÍA PROGRAMA DE ESTUDIO COMPILADORES 1764 7 o 09 Asignatura Clave Semestre Créditos Ingeniería Eléctrica Ingeniería en Computación Ingeniería

Más detalles

AUTÓMATAS DE PILA Y LENGUAJES INDEPENDIENTES DEL CONTEXTO

AUTÓMATAS DE PILA Y LENGUAJES INDEPENDIENTES DEL CONTEXTO Autómatas de pila y lenguajes independientes del contexto -1- AUTÓMATAS DE PILA Y LENGUAJES INDEPENDIENTES DEL CONTEXTO AUTÓMATAS DE PILA - Son autómatas finitos con una memoria en forma de pila. - Símbolos

Más detalles

ANÁLISIS SINTÁCTICO I ANALIZADORES SINTÁCTICOS

ANÁLISIS SINTÁCTICO I ANALIZADORES SINTÁCTICOS Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

Compiladores e Intérpretes

Compiladores e Intérpretes Programa de la Asignatura: Compiladores e Intérpretes Código: 767 Carrera: Ingeniería en Computación Plan: 2008 Carácter: Obligatoria Unidad Académica: Secretaría Académica Curso: Cuarto Año Primer cuatrimestre

Más detalles

Procesadores de Lenguaje

Procesadores de Lenguaje Procesadores de Lenguaje Repaso TALF Cristina Tîrnăucă Dept. Matesco, Universidad de Cantabria Fac. Ciencias Ing. Informática Primavera de 2013 La Jerarquía de Chomsky Cuatro niveles de lenguajes formales

Más detalles

Analizador Léxico. Programación II Margarita Álvarez. Analizador Léxico - Funciones

Analizador Léxico. Programación II Margarita Álvarez. Analizador Léxico - Funciones Analizador Léxico Programación II Margarita Álvarez Analizador Léxico - Funciones Función Principal Leer carácter por carácter de la entrada y elaborar como salida una secuencia de componentes léxicos

Más detalles

Universidad Nacional del Santa Facultad de Ingeniería E.A.P. de Ingeniería de Sistemas e Informática TEORIA DE COMPILADORES ANALISIS SINTACTICO

Universidad Nacional del Santa Facultad de Ingeniería E.A.P. de Ingeniería de Sistemas e Informática TEORIA DE COMPILADORES ANALISIS SINTACTICO ANALISIS SINTACTICO Análisis Sintáctico Descendente Análisis Sintáctico Ascendente EOF y α S (axioma inicial). 2. : la cadena de entrada no es válida. 3. : consiste en aplicar

Más detalles

Procesadores de lenguaje Tema 1 Introducción a los compiladores

Procesadores de lenguaje Tema 1 Introducción a los compiladores Procesadores de lenguaje Tema 1 Introducción a los compiladores Salvador Sánchez, Daniel Rodríguez Departamento de Ciencias de la Computación Universidad de Alcalá Resumen del tema Traductores Estructura

Más detalles

Unidad II: Análisis semántico

Unidad II: Análisis semántico Unidad II: Análisis semántico Se compone de un conjunto de rutinas independientes, llamadas por los analizadores morfológico y sintáctico. El análisis semántico utiliza como entrada el árbol sintáctico

Más detalles

JavaCC Parte I. 1 Compiladores / Guía VII / Ciclo Facultad: Ingeniería Escuela: Computación Asignatura: Compiladores.

JavaCC Parte I. 1 Compiladores / Guía VII / Ciclo Facultad: Ingeniería Escuela: Computación Asignatura: Compiladores. 1 Compiladores / Guía VII / Ciclo 02-2016 Centro de Investigación y Transferencia de Tecnología JavaCC Parte I Contenido Facultad: Ingeniería Escuela: Computación Asignatura: Compiladores En la presente

Más detalles

UNIVERSIDAD DE LOS LLANOS Facultad de Ciencias Básicas e Ingeniería Programa Ingeniería de Sistemas ALGORITMIA AVANZADA

UNIVERSIDAD DE LOS LLANOS Facultad de Ciencias Básicas e Ingeniería Programa Ingeniería de Sistemas ALGORITMIA AVANZADA CURSO: ALGORITMIA AVANZADA 1 SEMESTRE: VII 2 CODIGO: 602802 3 COMPONENTE: 4 CICLO: 5 AREA: Profesional 6 FECHA DE APROBACIÓN: 7 NATURALEZA: Teórico - Practica 8 CARÁCTER: Obligatorio 9 CREDITOS (RELACIÓN):

Más detalles

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V.

Compiladores: Análisis Sintáctico. Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Compiladores: Análisis Sintáctico Pontificia Universidad Javeriana Cali Ingenieria de Sistemas y Computación Prof. Gloria Inés Alvarez V. Sintaxis Define la estructura del lenguaje Ejemplo: Jerarquía en

Más detalles

Es un conjunto de palabras y símbolos que permiten al usuario generar comandos e instrucciones para que la computadora los ejecute.

Es un conjunto de palabras y símbolos que permiten al usuario generar comandos e instrucciones para que la computadora los ejecute. Los problemas que se plantean en la vida diaria suelen ser resueltos mediante el uso de la capacidad intelectual y la habilidad manual del ser humano. La utilización de la computadora en la resolución

Más detalles

Métodos para escribir algoritmos: Diagramas de Flujo y pseudocódigo

Métodos para escribir algoritmos: Diagramas de Flujo y pseudocódigo TEMA 2: CONCEPTOS BÁSICOS DE ALGORÍTMICA 1. Definición de Algoritmo 1.1. Propiedades de los Algoritmos 2. Qué es un Programa? 2.1. Cómo se construye un Programa 3. Definición y uso de herramientas para

Más detalles

Compiladores. Análisis Sintáctico Ascendente. Adrian Ulises Mercado Martínez. Facultad de Ingeniería, UNAM. 5 de septiembre de 2013

Compiladores. Análisis Sintáctico Ascendente. Adrian Ulises Mercado Martínez. Facultad de Ingeniería, UNAM. 5 de septiembre de 2013 Compiladores Análisis Sintáctico Ascendente Adrian Ulises Mercado Martínez Facultad de Ingeniería, UNAM 5 de septiembre de 2013 Adrian Ulises Mercado Martínez (FI,UNAM) Compiladores 5/07/2013 1 / 34 Índice

Más detalles

Tema 5. Análisis semántico

Tema 5. Análisis semántico Departamento de Tecnologías de la Información Tema 5 Análisis semántico Ciencias de la Computación e Inteligencia Artificial Índice 5.1 Características del análisis semántico 5.2 Gramáticas atribuidas

Más detalles

300CIG007 Computabilidad y Lenguajes Formales: Autómatas Finitos

300CIG007 Computabilidad y Lenguajes Formales: Autómatas Finitos 300CIG007 Computabilidad y Lenguajes Formales: Autómatas Finitos Pontificia Universidad Javeriana Cali Ingeniería de Sistemas y Computación Prof. Gloria Inés Alvarez V. Qué es un computador? Todos lo sabemos!!!

Más detalles

PRACTICA 5: Autómatas Finitos Deterministas

PRACTICA 5: Autómatas Finitos Deterministas E. T. S. DE INGENIERÍA INFORMÁTICA Departamento de Estadística, I.O. y Computación Teoría de Autómatas y Lenguajes Formales PRACTICA 5: Autómatas Finitos Deterministas 5.1. Requisito de codificación Cada

Más detalles

Teoría de Autómatas y Lenguajes Formales. Introducción a las Gramáticas. Gramáticas incontextuales

Teoría de Autómatas y Lenguajes Formales. Introducción a las Gramáticas. Gramáticas incontextuales Teoría de utómatas y Lenguajes Formales Introducción a las ramáticas. ramáticas incontextuales José M. Sempere Departamento de Sistemas Informáticos y Computación Universidad Politécnica de Valencia Introducción

Más detalles

Algoritmos. Medios de expresión de un algoritmo. Diagrama de flujo

Algoritmos. Medios de expresión de un algoritmo. Diagrama de flujo Algoritmos En general, no hay una definición formal de algoritmo. Muchos autores los señalan como listas de instrucciones para resolver un problema abstracto, es decir, que un número finito de pasos convierten

Más detalles

Departamento de Tecnologías de la Información. Tema 4. Máquinas de Turing. Ciencias de la Computación e Inteligencia Artificial

Departamento de Tecnologías de la Información. Tema 4. Máquinas de Turing. Ciencias de la Computación e Inteligencia Artificial Departamento de Tecnologías de la Información Tema 4 Máquinas de Turing Ciencias de la Computación e Inteligencia Artificial Índice 4.1 Límites de los autómatas 4.2 Definición de Máquina de Turing 4.3

Más detalles

INTRODUCCIÓN A COMPILADORES Y LENGUAJES FORMALES LENGUAJES FORMALES

INTRODUCCIÓN A COMPILADORES Y LENGUAJES FORMALES LENGUAJES FORMALES Todos los derechos de propiedad intelectual de esta obra pertenecen en exclusiva a la Universidad Europea de Madrid, S.L.U. Queda terminantemente prohibida la reproducción, puesta a disposición del público

Más detalles

Tema: Autómatas de Estado Finitos

Tema: Autómatas de Estado Finitos Compiladores. Guía 2 1 Facultad: Ingeniería Escuela: Computación Asignatura: Compiladores Tema: Autómatas de Estado Finitos Contenido En esta guía se aborda la aplicación de los autómatas en el campo de

Más detalles

Teoría de Autómatas y Lenguajes Formales Práctica 4

Teoría de Autómatas y Lenguajes Formales Práctica 4 Departamento de Tecnologías de la Información Área de Ciencias de la Computación e Inteligencia Artificial Teoría de Autómatas y Lenguajes Formales Práctica 4 1.- OBJETIVOS El objetivo de esta práctica

Más detalles

LEX. Las definiciones y subrutinas son opcionales. El segundo %% es opcional pero el primer %% indica el comienzo de las reglas.

LEX. Las definiciones y subrutinas son opcionales. El segundo %% es opcional pero el primer %% indica el comienzo de las reglas. LEX Estructura de un programa en LEX { definiciones { reglas { subrutinas del usuario Las definiciones y subrutinas son opcionales. El segundo es opcional pero el primer indica el comienzo de las reglas.

Más detalles

Análisis sintáctico 1

Análisis sintáctico 1 Análisis sintáctico 1 Análisis sintáctico 1. Introducción 2. Análisis descendente (top-down) 2.1 Análisis con backtracking 2.2 Análisis predictivo 2.2.1 Método recursivo 2.2.2 Método iterativo 3. Análisis

Más detalles

Procesadores de Lenguajes. Análisis sintáctico. Analizadores descendentes

Procesadores de Lenguajes. Análisis sintáctico. Analizadores descendentes Procesadores de Lenguajes Ingeniería Técnica superior de Ingeniería Informática Departamento de Lenguajes y Sistemas informáticos Análisis sintáctico Analizadores descendentes Javier Vélez Reyes jvelez@lsi.uned.es

Más detalles

GRAMMAR Aplicación de apoyo para el aprendizaje de los lenguajes formales.

GRAMMAR Aplicación de apoyo para el aprendizaje de los lenguajes formales. Manual de usuario. GRAMMAR Aplicación de apoyo para el aprendizaje de los lenguajes formales. LINKSOFT CORP Manual de Usuario GRAMMAR" 1 PROLOGO Este manual introducirá al usuario al uso del programa educativo

Más detalles

Cátedra Sintaxis y Semántica del Lenguaje

Cátedra Sintaxis y Semántica del Lenguaje Cátedra Sintaxis y Semántica del Lenguaje 1 º CICLO DE CAPACITACION DOCENTE 1 Simplificación de Gramáticas Tipo 2 Forma Normal de Chomsky (FNC) Forma Normal de Greibach (FNG) 2 1 Jerarquía de Chomsky Gramáticas

Más detalles

PROGRAMACIÓN II AÑO 2009 TALLER 3: TEORÍA DE LENGUAJES Y AUTÓMATAS

PROGRAMACIÓN II AÑO 2009 TALLER 3: TEORÍA DE LENGUAJES Y AUTÓMATAS Licenciatura en Sistemas de Información PROGRAMACIÓN II AÑO 2009 TALLER 3: TEORÍA DE LENGUAJES Y AUTÓMATAS UNSE FCEyT 1. DESCRIPCIÓN Este taller consta de tres partes. En cada una de ellas se especifican

Más detalles

09 Análisis léxico V Compiladores - Profr. Edgardo Adrián Franco Martínez

09 Análisis léxico V Compiladores - Profr. Edgardo Adrián Franco Martínez 2 Contenido Autómata Definición formal de autómata Representación de un autómata Mediante tablas de transiciones Mediante diagramas de estados Autómata finito Definición formal de autómata finito Lenguaje

Más detalles

Funcionamiento del A.L.

Funcionamiento del A.L. ANALIZADOR LEXICO 1 Funcionamiento del A.L. Programa fuente Analizador léxico Componente léxico Obtén el siguiente componente léxico Analizador sintáctico Tabla de símbolos 2 Maneja el fichero del programa

Más detalles

NOMBRE DEL CURSO: Organización de Lenguajes y Compiladores 2 CÓDIGO: 781 CRÉDITOS: 5 ÁREA A LA QUE PERTENECE: POST-REQUISITO:

NOMBRE DEL CURSO: Organización de Lenguajes y Compiladores 2 CÓDIGO: 781 CRÉDITOS: 5 ÁREA A LA QUE PERTENECE: POST-REQUISITO: UNIVERSIDAD DE SAN CARLOS DE GUATEMALA FACULTAD DE INGENIERÍA ESCUELA DE CIENCIAS NOMBRE DEL CURSO: Organización de Lenguajes y Compiladores 2 CÓDIGO: 781 CRÉDITOS: 5 ESCUELA: Ciencias y Sistemas ÁREA

Más detalles

Universidad de Valladolid

Universidad de Valladolid Universidad de Valladolid Departamento de Informática Teoría de autómatas y lenguajes formales. 2 o I.T.Informática. Gestión. Examen de primera convocatoria. 18 de junio de 29 Apellidos, Nombre... Grupo:...

Más detalles

Introducción a la Lógica y la Computación

Introducción a la Lógica y la Computación Introducción a la Lógica y la Computación Parte III: Lenguajes y Autómatas Clase del 7 de Noviembre de 2014 Parte III: Lenguajes y Autómatas Introducción a la Lógica y la Computación 1/20 Lenguajes Formales

Más detalles

Compiladores e intérpretes Introducción

Compiladores e intérpretes Introducción Introducción Profesor: Eridan Otto Introducción Perspectiva histórica Motivación Definiciones Componentes y fases de un compilador 1 Introducción Definiciónes básicas Traductor: desde un punto de vista

Más detalles

El análisis descendente LL(1) 6, 7 y 13 de abril de 2011

El análisis descendente LL(1) 6, 7 y 13 de abril de 2011 6, 7 y 13 de abril de 2011 Analizadores sintácticos (repaso) Los analizadores descendentes: Corresponden a un autómata de pila determinista. Construyen un árbol sintáctico de la raíz hacia las hojas (del

Más detalles

Lenguajes Incontextuales

Lenguajes Incontextuales Tema 5: Gramáticas Formales Lenguajes Incontextuales Departamento de Sistemas Informáticos y Computación http://www.dsic.upv.es p.1/31 Tema 5: Gramáticas Formales Gramáticas. Tipos de Gramáticas. Jerarquía

Más detalles

Técnicas de Programación

Técnicas de Programación Técnicas de Programación 2.1.- Introducción: unos conceptos previos y primeros conceptos de la API Introducción La resolución de un problema con medios informáticos implica generalmente la siguiente secuencia

Más detalles

Introducción a la Lógica y la Computación

Introducción a la Lógica y la Computación Introducción a la Lógica y la Computación Parte III: Lenguajes y Autómatas Clase del 4 de Noviembre de 2015 Parte III: Lenguajes y Autómatas Introducción a la Lógica y la Computación 1/21 Lenguajes Formales

Más detalles

Objetivos Que el estudiante logre conocer, comprender y manejar conceptos y técnicas vinculados con el Analizador Léxico, para lo cual debe:

Objetivos Que el estudiante logre conocer, comprender y manejar conceptos y técnicas vinculados con el Analizador Léxico, para lo cual debe: 09:19 1 2 Temas Funciones del Analizador Léxico Manejo de buffers de entrada Especificación y reconocimiento de tokens Generación automática de Analizadores Léxicos Objetivos Que el estudiante logre conocer,

Más detalles

Desarrollo del Programa Docente de Procesadores del Lenguaje

Desarrollo del Programa Docente de Procesadores del Lenguaje Grado en Ingeniería Informática. Procesadores del Lenguaje. Curso 2010-2011 -1- Desarrollo del Programa Docente de Procesadores del Lenguaje Introducción Se presenta en este texto el programa desarrollado

Más detalles

Coordinación de Ciencias Computacionales INAOE. Teoría de Autómatas y Lenguajes Formales. Temario detallado para examen de ingreso 2012

Coordinación de Ciencias Computacionales INAOE. Teoría de Autómatas y Lenguajes Formales. Temario detallado para examen de ingreso 2012 Coordinación de Ciencias Computacionales INAOE Teoría de Autómatas y Lenguajes Formales Temario detallado para examen de ingreso 2012 1. Autómatas 1.1. Por qué estudiar la teoría de autómatas? 1.1.1. Introducción

Más detalles

Construcción de tablas de análisis sintáctico LL(1)

Construcción de tablas de análisis sintáctico LL(1) Construcción de tablas de análisis sintáctico LL(1) Universidad de Costa Rica Escuela de Ciencias de la Computación e Informática Diego Centeno Gerardo Cortés Juan Diego Alfaro Resumen. A la medida en

Más detalles

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes.

UNIVERSIDAD NACIONAL DE EDUCACIÓN A DISTANCIA Escuela Técnica Superior de Ingeniería Informática Procesadores de Lenguajes. UNIVERIDAD NACIONAL DE EDUCACIÓN A DITANCIA Escuela Técnica uperior de Ingeniería Informática Procesadores de Lenguajes Tema 3 Parte II Análisis intáctico Descendente Javier Vélez Reyes jvelez@lsi.uned.es

Más detalles

Semana Lenguajes 7de programación Tipos de lenguajes de programación

Semana Lenguajes 7de programación Tipos de lenguajes de programación Semana Lenguajes 7de programación Semana 6 Empecemos! Estimados participantes, bienvenidos a esta nueva semana, en la que estudiaremos los lenguajes de programación más utilizados. No olvides repasar los

Más detalles