Ami Pro - TESIS.SAM - No

advertisement
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
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
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
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 20.2.
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 22.2.
Un programa para validar esquemas deductivos por medio de la Teoría
Semántica del Cálculo Proposicional. Sección 11.9, 11.10, 12.12 y 12.13. 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
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++ 2.0 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++ 2.0 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++ 2.0 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
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++.
3. 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
Parte 1. Marco Teórico
7
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
2.2.1 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.
2.2.2 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
Introducción a la Teoría de los Lenguajes Formales
2.2.3 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 2.3.5.
2.2.4 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:
G=(N,T,R,O)
donde:
9
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 -*-> α }
2.2.5 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
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
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.
2.3.1 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
Introducción a la Teoría de los Lenguajes Formales
N ---> D D D
D ---> 0 | 1 | 2 | ... | 9
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 ---> 0 | 1 | 2 | ... | 9
2.3.2 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.
2.3.3 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
Introducción a la Teoría de los Lenguajes Formales
2.3.4 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.
2.3.5 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
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.
2.3.6 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
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.
2.3.7 Esquemas de Traducción
Se define un esquema de traducción como un sistema formal del tipo:
EDT = { N, Te, Ts, R, O }
donde
N: es el conjunto finito de símbolos no terminales.
Te: es el conjunto finito de símbolos terminales del lenguaje de entrada o
fuente.
Ts: 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
Introducción a la Teoría de los Lenguajes Formales
α pertenece a ( N U Te)*
β pertenece a ( N U Ts)*
En cualquier caso se cumple que
N intersección (Te U Ts) = { }
O: es el símbolo inicial.
La estructura del lenguaje de salida se puede explicar con la gramática
siguiente:
Gs = { N, Ts, P, O }
donde
N: es el conjunto de símbolos no terminales del lenguaje fuente. O es un
elemento de este conjunto.
Ts: 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 }
2.3.8 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
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
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
3.2.1 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 10.3. 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
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
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.
3.2.2 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
Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos
Tipo de nodo
Hoja con número i
Anulable(Nodo)
Falso
.
si
sd
Anulable(si) y Anulable(sd)
|
Anulable(si) o Anulable(sd)
si
sd
*
Verdadero
s
+
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
Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos
Tipo de nodo
PrimeraPos(Nodo)
Hoja con número i
.
si
sd
UltimaPos(Nodo)
{i}
{i}
Si Anulable(sd) Entonces
Si Anulable(si) Entonces
PrimeraPos(si) U PrimeraPos(sd) UltimaPos(sd) U UltimaPos(si)
Sino UltimaPos(sd)
Sino PrimeraPos(si)
|
PrimeraPos(si) U PrimeraPos(sd) UltimaPos(sd) U UltimaPos(si)
si
sd
*
PrimeraPos(s)
UltimaPos(s)
PrimeraPos(s)
UltimaPos(s)
PrimeraPos(s)
UltimaPos(s)
s
+
s
?
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
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
3.3.1 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
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 #.
3.3.2 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
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
FinPara
FinMientras
Fig. 7 Algoritmo para la construcción del conjunto de estados y
la tabla de transiciones para el AFD de la expresión
regular.
3.3.3 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
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 }
Ud = { 3, 4 }
Transición[ {1, 2, 3}, d ] = { 4, 3 }
EstadosD = { {1, 2, 3}0, {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
Ud = { 3, 4 }
Transición[ {3}, d ] = { 3, 4 }
EstadosD = { {1, 2, 3}0, {3}0, {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
Ud = { 3, 4 }
Transición[ {3, 4}, d ] = { 3, 4 }
EstadosD = { {1, 2, 3}0, {3}0, {3, 4}0 }
5. 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
0
1
2
'+'
1
no definido
no definido
'-'
1
no definido
no definido
d
2
2
2
La gráfica del autómata finito determinístico para el ejemplo dado es:
26
Algoritmos Usados por el Generador de Autómatas Finitos Determinísticos
0
d
d
'+' / '-'
1
2
d
Fig. 8 Autómata finito determinístico construido para la
expresión regular ( '+' | '-' ) ? d +
3.4
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
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;
FinSi
FinSi
Mientras EstadoActual sea igual a EstadoNuevo;
Si EstadoActual es un elemento del conjunto de estados finales entonces
La cadena de entrada leída hasta este momento cumple con la
expresión regular.
FinSi
Fig. 9 Algoritmo para el funcionamiento de un Autómata Finito
Determinístico.
27
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
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.
4.2.1
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
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
Se ha llegado al estado de aceptación.
Salir = Si.
Aceptar = Si.
Sino
Salir = Si.
Aceptar = No; hay error de sintaxis.
FinSi
Hasta que Salir = Si.
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
Algoritmos usados por el Generador de Analizadores Sintácticos
Fig. 10 Algoritmo de análisis sintáctico SLR
4.2.2 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:
2:
3:
4:
5:
6:
7:
P
D
D
L
L
I
I
-->
-->
-->
-->
-->
-->
-->
D I
'Var' L ':' 'Tipo' ';'
L ',' 'Id'
'Id'
I 'Instr' ';'
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
----+--------------------------------------------+----------------------0 |
d1
·
·
·
·
·
r3 r3 | 2 3 · ·
1 |
·
·
·
·
·
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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Pila
0
0 Var 1
0 Var 1 Id 4
0 Var 1 L
0 Var 1 L 5
0 Var 1 L 5 : 7
0 Var 1 L 5 : 7 Tipo 10
0 Var 1 L 5 : 7 Tipo 10 ; 13
0D
0D3
0D3I
0D3I6
0 D 3 I 6 Instr 9
0 D 3 I 6 Instr 9 ; 12
0D3I
0D3I6
0P
0P2
Entrada
Var Id : Tipo ; Instr ;
Id : Tipo ; Instr ;
: Tipo ; Instr ;
: Tipo ; Instr ;
: Tipo ; Instr ;
Tipo ; Instr ;
; Instr ;
Instr ;
Instr ;
Instr ;
Instr ;
Instr ;
;
Acción
d1
d4
r5
Ir a 5
d7
d10
d13
r2
Ir a 3
r7
Ir a 6
d9
d12
r6
Ir a 6
r1
Ir a 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.
4.2.3 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
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 2.3.8 y 12.3.3),
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.
4.3.1 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
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
A
A
A
--->
--->
--->
--->
.
B
B
B
B
.
C
C
C
C
.
D
D
D
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".
4.3.2 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
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'
4.3.3 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.
4.3.4 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
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.
4.3.5 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
Algoritmos usados por el Generador de Analizadores Sintácticos
4.3.6 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
Agregar ir_a(I, X) a C.
FinPara
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 --->
D --->
D --->
L --->
L --->
I --->
I --->
P
D I
'Var' L ':' 'Tipo' ';'
L ',' 'Id'
'Id'
I 'Instr' ';'
37
Algoritmos usados por el Generador de Analizadores Sintácticos
y la colección canónica es:
Estado 0 (I0):
P' ---> • P
P ---> • D I
D ---> • 'Var' L ':' 'Tipo' ';'
D ---> •
Estado
D --->
L --->
L --->
1 (I1):
'Var' • L ':' 'Tipo' ';'
• L ',' 'Id'
• 'Id'
Estado 2 (I2):
P' ---> P •
Estado
P --->
I --->
I --->
3:
D • I
• I 'Instr' ';'
•
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' ';' •
4.3.7 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
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'
ar'
'V
0 P
1
4
L
':'
5
2
7
','
D
8
3
I
6
'Instr'
9
'Tipo'
'Id'
';'
10
';'
13
11
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.
4.3.8 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
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
A
B
B
C
C
--->
--->
--->
--->
--->
--->
--->
A B C
a B
b C
C
c O
El cálculo de AnulableSimple para cada uno de los símbolos daría el
siguiente resultado:
O:
A:
B:
C:
no
si
no
si
es
es
es
es
anulable
anulable
anulable
anulable
simple.
simple.
simple.
simple.
El cálculo de Anulable arroja el siguiente resultado:
O:
A:
B:
C:
si
si
si
si
es
es
es
es
anulable
anulable
anulable
anulable
porque
porque
porque
porque
A, B y C son anulables.
es anulable simple.
hay una regla B --> C, y C es anulable.
es anulable simple.
4.3.9 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
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 --> Y1 Y2 ... Yk entonces se agrega b a
Primero(X) si, para alguna i, b está en Primero(Yi) y λ está en todos
los conjuntos Primero(Y1), ..., Primero(Yi - 1); esto es, la cadena
Y1...Yi - 1 ==>* λ.
Si λ está en Primero(Yj) para toda j = 1, 2, ..., k, entonces se agrega
λ a Primero(X); esto es, la cadena Y1...Yk ==>* λ.
Por ejemplo, todo lo que está en Primero(Y1) sin duda estará en
Primero(X). Si Y1 no deriva en λ, entonces no se agrega nada más a
Primero(X), pero si Y1 ==>* λ, entonces se agrega Primero(Y2), 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)
Primero(A)
Primero(B)
Primero(C)
=
=
=
=
{
{
{
{
a
a
b
c
b c }
}
c }
}
41
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)
Primero(D)
Primero(L)
Primero(I)
=
=
=
=
{
{
{
{
'Var' 'Instr' }
'Var' }
'Id' }
'Instr' }
4.3.10 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(Xi) para i = 1, ..., k y todos
los Primero(Xi) excepto Primero(Xk) 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
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)
Siguiente(L)
Siguiente(I)
4.4
=
=
=
=
{
{
{
{
}
'Instr' }
':' ',' }
'Instr' }
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
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 = { I0, I1, ..., In }, 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 Ii. Las acciones de análisis
sintáctico para el estado i se determinan como sigue:
a. Si A --> α . x β está en Ii con x terminal e ir_a(Ii, x) = Ij,
entonces asignar "desplazar j" a accion[i, x].
b. Si A --> α . está en Ii, 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 Ii, 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[Ii, X] = Ij 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 4.3.6. Los conjuntos
Siguiente(X) los encontrará en la sección 4.3.10.
Ejemplo 2: para la gramática dada en la sección 4.3.8, los conjuntos
Siguiente(X) los encontrará en la sección 4.3.10. La colección canónica de
44
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
A --->
B --->
B --->
C --->
C --->
1:
'a' • B
• 'b' C
• C
• 'c' O
•
Estado 2:
O' ---> O •
Estado
O --->
B --->
B --->
C --->
C --->
3:
A • B C
• 'b' C
• C
• 'c' O
•
Estado
B --->
C --->
C --->
4:
'b' • C
• 'c' O
•
Estado
C --->
O --->
A --->
A --->
5:
'c' • O
• A B C
• 'a' B
•
Estado 6:
A ---> 'a' B •
Estado 7:
B ---> C •
Estado
O --->
C --->
C --->
8:
A B • C
• 'c' O
•
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'
ir_a(e, X):
FA
O
A
B
C
45
Algoritmos usados por el Generador de Analizadores Sintácticos
0
1
2
3
4
5
6
7
8
9
10
11
d1
·
·
·
·
d1
·
·
·
·
·
·
r3
d4
·
d4
r7
r3
r2
r5
r7
r4
r6
r1
r3
d5
·
d5
d5
r3
r2
r5
d5
r4
r6
r1
r3
r7
a
r7
r7
r3
r2
r5
r7
r4
r6
r1
2
·
·
·
·
10
·
·
·
·
·
·
3
·
·
·
·
3
·
·
·
·
·
·
· ·
6 7
· ·
8 7
· 9
· ·
· ·
· ·
· 11
· ·
· ·
· ·
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
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 2.3.6
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
5.2.
5.1
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.
5.1.1 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
El Generador de Analizadores Lexicográficos
5.1.2 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).
5.1.3 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
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 5.1.3.
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
Parte 2. Implementación de los Generadores
50
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
Implementación del Generador de Autómatas Finitos Determinísticos
6.1.1 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.
6.1.2 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
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.
6.1.3 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 13.2.
6.1.4 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 13.2.
6.1.5 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 13.4.
6.1.6 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 13.5.
6.1.7 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 13.6.
6.2
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
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 3.2.2 y en la figura 6.
La función NodoArbol::Anulable es también recursiva, y procede de
acuerdo a lo especificado por la figura 5.
6.4
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
Implementación del Generador de Autómatas Finitos Determinísticos
las transiciones mediante el procedimiento dado en la figura 7, en la sección
3.3.2.
6.5
Funcionamiento de un AFD
El funcionamiento de un AFD se implementa con la función
ExpReg::Examinar, que se documenta en la sección 13.4.2 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 13.6.2 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 13.4.2 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
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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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.
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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
7.1.1 Estructura de un Símbolo de una Gramática
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 23767 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.
7.1.2 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
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.
7.1.3 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.
7.1.4 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.
7.1.5 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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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:
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.
7.1.6 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.
7.1.7 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
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).
7.1.8 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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
7.1.9 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.
7.1.10 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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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.
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.
7.1.11 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
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.
7.1.12 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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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
16384. Habrá únicamente 1 solo elemento con ese valor.
Para los elementos que correspondan a reducciones se encontrarán
números en el rango de 32769 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:
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
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 0.
7.1.13 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.
7.1.14 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).
7.1.15 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
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).
7.1.16 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 12.3.
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
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
Gramatica::CodError.
7.3
error se encontrará en
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
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
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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);
70
Implementación del Generador de Analizadores Sintácticos SLR
y del Generador de Analizadores Lexicográficos
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
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
Parte 3. Documentación para el Usuario
72
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.
La sintaxis de llamada es:
AFD
"expreg"
[ PostFijoObj ] [ PostFijoSubobj ]
donde:
expreg: es la expresión regular. Su sintaxis se explica más adelante.
73
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
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
8.4.1 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
Documentación de AFD versión 1.x
Salida de AFD:
Expresión regular: b+
Caracter o rango: (0, "b")
Tabla de transiciones:
0
0
1
1
1
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) 22-2488/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 };
8.4.2 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 3.39. 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:
0
1
0
1
0
1
1
2
76
Documentación de AFD versión 1.x
2
1
3
3
1
0
Estado final: 3
8.4.3 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:
0
1
2
3
4
0
1
1
2
3
3
2
3
3
3
3
3
4
4
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) 22-2488/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
Documentación de AFD versión 1.x
8.4.4 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:
0
1
2
3
4
5
6
0
1
1
2
3
3
2
3
3
3
3
3
2
3
3
4
4
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) 22-2488/1088.
# ifndef __EXPREG_H
# include "expreg.h"
# endif
static
static
"[",
};
static
const char erRango[] = "[((\(\|'|d|e))|e|.)+]";
const char * vcRango[] = {
"\\", "\'", "d", "e", ".", "]"
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
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
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
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
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
Documentación de AFD versión 2.x
AFD
"expreg"
[ PostFijoObj ] [ PostFijoSubobj ]
donde:
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
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
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
9.4.1 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
Documentación de AFD versión 2.x
0
1
0
1
1
2
2
2
2
2
Estado final: 2
9.4.2 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:
0
1
0
1
1
2
2
2
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) 22-2488/1088.
# ifndef __EXPREG_H
# include "expreg.h"
# endif
static
static
"/",
};
static
const char erComent[] = "/ / [^\n] * // ejemplo de comentario";
const char * vcComent[] = {
"^\n"
const unsigned tComent[] = {
1, TNDEF,
2, TNDEF,
TNDEF,
2
};
static const unsigned efComent[] = {
1, 2
};
AutomFinDet afdComent = { erComent, vcComent, 2, tComent, efComent };
9.4.3 Ejemplo 3
Expresión regular para los blancos ignorados por el analizador léxico de
AFD v 2.
86
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:
0
0
1
1
1
Estado final: 1
9.4.4 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:
0
1
2
3
4
0
1
1
2
3
3
2
3
3
3
3
3
4
4
Estado final: 4
9.5
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
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
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
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
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 SLR1.
10.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 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.
10.2 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
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.
10.3 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
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
Documentación de AFD versión 3.x
Orden
1
la más alta
2
Los 3 la
misma.
Operador
( )
?
+
*
4
5
|
Observaciones
Use para asociar o romper
precedencias.
0 o 1 vez.
1 o más veces.
0 o más veces.
Concatenación. No tiene caracter que
lo represente.
Disyunción.
Fig. 18 Precedencia de operadores en una expresión regular.
10.3.1 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 10.3.2. 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
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
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.
10.3.2 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
10.4.1 Ejemplo 1
Expresión regular para un rango reconocido por el analizador léxico de
AFD.
96
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:
0
1
2
3
4
5
6
0
1
1
2
3
2
2
2
3
2
4
3
2
2
2
2
4
Estado final: 4
10.4.2 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:
0
1
0
1
1
1
1
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) 22-2488/1088.
97
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
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
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.
10.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.
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
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).
11.1 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.
11.2 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
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
ListaSímbolos ---> ListaSímbolos Símbolo
ListaSímbolos --->
FinLínea
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
Documentación de SLR1 versión 1.x
11.3 Ejemplos de gramáticas
11.3.1 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,
};
unsigned
0,
0,
16384,
32769,
32771,
0,
32770,
0,
32772,
0,
};
0x402,
0x400
accion[] = {
1,
0,
0,
0,
0,
5,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
32771,
0,
0,
0,
0,
0, 32774, 32774,
32770,
0,
0,
0,
0,
0,
8,
9,
32772,
0,
0,
0,
0,
0, 32773, 32773
unsigned ir_a[] = {
2,
3,
4,
0,
0,
0,
0,
0,
0,
0,
0,
6,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
};
0,
0,
0,
0,
0,
7,
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
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
0
1
2
3
4
5
6
7
8
9
acción(estado, simbolo)
IdentIzq Flecha FinLínea Símbolo
d1
.
.
.
.
d5
.
.
.
.
.
.
d1
.
.
.
r3
.
.
.
.
.
r6
r6
r2
.
.
.
.
.
d8
d9
r4
.
.
.
.
.
r5
r5
FA
.
.
a
r1
r3
.
r2
.
r4
.
e
ir_a (estado, símbolo):
Gramática Reglas Regla ListaSímbolos
2
3
4
.
.
.
.
.
.
.
.
.
.
.
6
.
.
.
.
.
.
.
.
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11.3.2 Ejemplo 2
La gramática que se da a continuación especifica la sintaxis de una
expresión aritmética.
E
E
T
T
F
-->
-->
-->
-->
-->
E '+' T
T
T '*' F
F
'(' E ')'
104
Documentación de SLR1 versión 1.x
F --> id
Archivo .CMM:
unsigned noterm_long_pd[] = {
0x305,
0x103, 0x101, 0x203, 0x201,
};
unsigned
0,
0,
32774,
16384,
32770,
32772,
0,
0,
0,
32773,
32769,
32771,
};
accion[] = {
0,
0,
0,
0,
32774, 32774,
7,
0,
32770,
8,
32772, 32772,
7,
0,
0,
0,
0,
0,
32773, 32773,
32769,
8,
32771, 32771,
1,
1,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0x303,
0,
0,
32774,
0,
32770,
32772,
9,
0,
0,
32773,
32769,
32771,
0x301
2,
2,
0,
0,
0,
0,
0,
2,
2,
0,
0,
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
Documentación de SLR1 versión 1.x
Estado
F --->
E --->
E --->
T --->
T --->
F --->
F --->
1:
'(' . E
. E '+'
. T
. T '*'
. F
. '(' E
. id
')'
T
F
')'
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
E --->
T --->
T --->
F --->
F --->
7:
E '+' . T
. T '*' F
. F
. '(' E ')'
. id
Estado
T --->
F --->
F --->
8:
T '*' . F
. '(' E ')'
. id
Estado 9:
F ---> '(' E ')' .
Estado 10:
E ---> E '+' T .
T ---> T . '*' F
Estado 11:
T ---> T '*' F .
Funciones
0
1
2
3
4
5
6
7
8
9
10
11
acción(estado, simbolo)
'+' '*' '(' ')'
.
. d1
.
.
. d1
.
r6 r6
. r6
d7
.
.
.
r2 d8
. r2
r4 r4
. r4
d7
.
. d9
.
. d1
.
.
. d1
.
r5 r5
. r5
r1 d8
. r1
r3 r3
. r3
id
d2
d2
.
.
.
.
.
d2
d2
.
.
.
FA
.
.
r6
a
r2
r4
.
.
.
r5
r1
r3
e
ir_a(estado, símbolo):
E T F
3 4 5
6 4 5
. . .
. . .
. . .
. . .
. . .
. 10 5
. . 11
. . .
. . .
. . .
106
Documentación de SLR1 versión 1.x
11.3.3 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
0,
32771,
16384,
0,
0,
32769,
32770,
};
accion[] = {
0,
0,
32771, 32771,
3,
4,
0,
0,
0,
0,
32769,
4,
32770, 32770,
1,
0,
0,
1,
1,
0,
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
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
E --->
E --->
E --->
E --->
3:
E '+' . E
. E '*' E
. id
. E '+' E
Estado
E --->
E --->
E --->
E --->
4:
E '*' . E
. E '+' E
. E '*' E
. id
Estado
E --->
E --->
E --->
5:
E '+' E .
E . '*' E
E . '+' E
Estado
E --->
E --->
E --->
6:
E '*' E .
E . '+' E
E . '*' E
Funciones
0
1
2
3
4
5
6
acción(estado, simbolo)
'+' '*' id FA
.
. d1 .
r3 r3 . r3
d3 d4 . a
.
. d1 .
.
. d1 .
r1 d4 . r1
r2 r2 . r2
e
ir_a(estado, símbolo):
E
2
.
.
5
6
.
.
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
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 2.
11.4 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
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.
11.5 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 3.
11.5.1 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
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.
11.5.2 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 16384 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
Documentación de SLR1 versión 1.x
11.5.3 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.
11.6 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
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
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
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.
11.7 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
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.
11.9 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
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
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++ 4.5 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++ 2.0 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
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
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
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
Prem
F
F
I
I
D
D
C
C
Prop
Prop
Prop
Prop
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
Prem '#' F
Prem ',' F
F
F '<==>' I
I
I '==>' D
D
D '+' C
C
C '*' Prop
Prop
c
v
'(' F ')'
- 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
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
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
Documentación de SLR1 versión 1.x
11.10 Módulos fuentes escritos para el ejemplo 4
11.10.1 ED.GLC
E
Prem
Prem
F
F
I
I
D
D
C
C
Prop
Prop
Prop
Prop
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
Prem '#' F
Prem ',' F
F
F '<==>' I
I
I '==>' D
D
D '+' C
C
C '*' Prop
Prop
c
v
'(' F ')'
- Prop
11.10.2 ED.CMM
?????????
No se incluye por ser salida de SLR1 v 1 y generable a partir de
ED.GLC.
11.10.3 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
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
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];
}
11.10.4 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;
}
11.10.5 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
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
11.10.6 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;
}
11.10.7 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
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
Documentación de SLR1 versión 1.x
// los códigos que asigne SLR1 v1 (puede cambiarlos si cambiamos la
gramática).
#
#
#
#
#
#
#
#
#
#
#
#
define
define
define
define
define
define
define
define
define
define
define
define
IDENT
CONS
NEG
DISY
CONJ
IMPL
DOBLIMP
A_PAR
C_PAR
COMA
CONCLU
NO_SE
8
7
11
5
6
4
3
9
10
2
1
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
if (psimb = SimbSig(psimb, simbolo, disy), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, conj), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, impl), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, doblimp), *simbolo) return
if (psimb = SimbSig(psimb, simbolo, a_par), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, c_par), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, coma), *simbolo)
return
if (psimb = SimbSig(psimb, simbolo, conclu), *simbolo) return
if (psimb != 0 && *psimb) return NO_SE;
else return 0;
}
NEG;
DISY;
CONJ;
IMPL;
DOBLIMP;
A_PAR;
C_PAR;
COMA;
CONCLU;
// 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
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
Documentación de SLR1 versión 1.x
++ifnp;
break;
}
}
11.10.8 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
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;
}
11.10.9 ESQDED.H
# ifndef __ESQDED_H
# define __ESQDED_H
// Copyright © 1993 by Domingo Eduardo Becker.
// All rights reserved.
// Creación: 26 May 93
132
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
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
11.10.10SLR1.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
11.10.11ELEM.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
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
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.
12.1 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.
12.2 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
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.
12.3 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 ñ.
12.3.1 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
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.
12.3.2 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
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
Documentación de SLR1 versión 2.x
12.3.3 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.
En total son 4 las directivas disponibles en esta versión del generador:
ACCSEM
'#'
'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
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
'TERMINAL'
Ident
':'
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
# TERMINAL
Cadena // archivo con expresión regular: cadena.er
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
'IGNORAR'
Ident
':'
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
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
# IGNORAR
Cadena // archivo con expresión regular: cadena.er
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
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
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.
12.4 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
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
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 .
12.5 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
Documentación de SLR1 versión 2.x
fuente en la que el ancho de todos los caracteres es el mismo, por
ejemplo Courier.
12.6 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.
12.6.1 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.
12.6.2 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
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 16384 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).
12.6.3 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.
12.7 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
Documentación de SLR1 versión 2.x
12.7.1 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
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.
12.7.2 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.
12.8 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.
12.8.1 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
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
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).
12.8.2 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.
12.8.3 Campos dato protegidos:
Ent16ns
Ent16ns
Ent16ns
void (*
void (*
void (*
* ntlpd,
* accion,
* ir_a;
fAccSem)( Ent16ns numregla, ELEMPILA * pila, Ent16ns pp );
fLimPila)( ELEMPILA * pila, Ent16ns pp, Ent8 cumple );
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
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.
12.10 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
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.
12.11 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
Documentación de SLR1 versión 2.x
12.12 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
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
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++ 4.5 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++ 2.0 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
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
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
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
Documentación de SLR1 versión 2.x
12.13 Módulos fuentes escritos para el proyecto
12.13.1 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
TERMINAL
TERMINAL
IGNORAR
IGNORAR
Variable
DobleImpl
Implica
Blancos
Comentarios
:
:
:
:
:
Var
DImpl
Impl
Blanco
Coment
//
//
//
//
//
Expr
Expr
Expr
Expr
Expr
regular
regular
regular
regular
regular
en
en
en
en
en
var.er
dimpl.er
impl.er
blanco.er
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
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
Prem --> Prem ',' F
*ACImplicación;
*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
Documentación de SLR1 versión 2.x
Constante --> '0' | '1';
12.13.2 ED.CMM
// Código fuente generado por SLR1 versión 2.1 (Nov 95)
// Domingo Eduardo Becker, Sgo. del Estero, Tel (085) 22-2488
// 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.
//
es
//
//
//
int AgregarTDS( const char * s );// Agrega s a la tabla de símbolo. s
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
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
include
include
include
include
"Coment.cmm" // afdComentarios
"Blanco.cmm" // afdBlancos
"Impl.cmm" // afdImplica
"DImpl.cmm" // afdDobleImpl
"Var.cmm" // afdVariable
static RecSimb vrs[] = {
1, 0, 0, "#",
2, 0, 0, ",",
3, 1, & afdDobleImpl, 0,
4, 1, & afdImplica, 0,
5, 0, 0, "+",
164
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);
12.13.3 ED.TAB
No se incluye porque su lectura es complicada y es un archivo generable
a partir de ED.GLC listado previamente.
12.13.4 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
Documentación de SLR1 versión 2.x
//
//
//
//
IMPLIC:
DISYUN:
CONJUN:
NEGAR:
==>
+
*
-
implicación,
disyunción,
conjunción,
negación,
binario,
binario,
binario,
unario,
p
p
p
-
===> q
+ q
* q
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
Documentación de SLR1 versión 2.x
: ValorDe(fnp[ifnp].var, tds, ttds);
}
return pila[pp-1];
}
12.13.5 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;
}
12.13.6 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
Documentación de SLR1 versión 2.x
12.13.7 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;
}
12.13.8 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
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
12.13.9 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).
12.13.10EXPREG.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
Documentación de SLR1 versión 2.x
12.14 Archivos provistos con SLR1 v 2.x
12.14.1 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
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
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
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
12.14.2 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
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
a b
0 1 1 1 2
2 - 2
3 - -
de
c
2
2
-
Transiciones del ejemplo es:
d
3
3
3
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
Documentación de SLR1 versión 2.x
// por ExpReg en un solo m¢dulo fuente entonces tendr
// al compilar.
colisi¢n de nombres
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
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
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
define
define
define
define
define
define
define
define
define
_id_Numeral
_id_Ident
_id_CteCar
_id_Rango
_id_Disyuncion
_id_Opcional
_id_Uno_o_Mas
_id_Cero_o_Mas
_id_Abre_Paren
_id_Cierra_Paren
1
2
3
4
5
6
7
8
9
10
# endif // # ifndef __EXPREG_H
177
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.
13.1 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.
13.2 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
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
Documentación de la Biblioteca ExpReg v 1.x
13.3 Funciones globales para el tratamiento de AutomFinDet
13.3.1 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.
13.3.2 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
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.
13.4.1 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.
13.4.2 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
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
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
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).
13.5 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
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
char
const AutomFinDet *
const char *
};
Id;
EsAFD;
pAFD;
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
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.
13.6 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.
13.6.1 Campos dato de la clase AnaLex
Los miembros dato públicos de esta clase son:
unsigned ComLin;
186
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
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.
13.6.2 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
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
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
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
Parte 4. Ejemplos de Aplicación
192
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.
14.1 Análisis y Diseño
14.1.1 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.
14.1.2 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
Una Calculadora Simple
La gramática precedente define precedencias de operadores, se las
resume en la siguiente tabla:
Orden
1. La más alta
2
3. La más baja
Operador
()
*
/
+
-
Observaciones
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.
14.1.3 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
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 0.
14.1.4 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
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.
14.2.1 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 1.0 - Domingo E. Becker, Tel (085) 22-2488
%{
# ifndef __SLR1_H
# include "slr1.h"
# endif
// por las dudas
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
# IGNORAR Blancos
// en numero.er está la expresión regular
// 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
Una Calculadora Simple
Factor --> Numero %{
$$.Resultado = $1.CadTerm; // conversión automática de tipo por
// Ascii0
}% | '(' Suma ')' %{
$$.Resultado = $2.Resultado;
}% | '-' Factor %{
$$.Resultado = - $2.Resultado;
}% ;
14.2.2 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) 22-2488
// 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
# include "slr1.h"
# endif
// por las dudas
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
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
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);
14.2.3 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 + ) ?
14.2.4 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] +
14.2.5 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) 22-2488/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
Una Calculadora Simple
14.2.6 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) 22-2488/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 };
14.2.7 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
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++ 2.0 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.
14.3.1 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 © 1995. All Rights Reserved.
201
Una Calculadora Simple
SUBSYSTEM:
FILE:
AUTHOR:
calc.exe Application
calcapp.h
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.
14.3.2 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 © 1995. 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
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.
14.3.3 CALCAPP.CMM: generado por AppExpert
/*
Project calc
Copyright © 1995. 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
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
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;
}
14.3.4 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 © 1995. 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
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
Una Calculadora Simple
//
}
// INSERT>> Your code here.
TWindow(GetDlgItem(IDC_FORMULA)).SetFocus();
Formula->SetFocus();
207
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.
15.1 Conceptos Teóricos Básicos
15.1.1 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
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.
15.1.2 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
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.
15.1.3 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
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.
15.1.4 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
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.
15.2 Análisis y Diseño
15.2.1 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.
15.2.2 Visión general del diseño de la interfase y el funcionamiento
La interfase en pantalla del programa es la siguiente:
212
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
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.
15.2.3 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
Consultas a Archivos Base de Datos BCE
15.2.4 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
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.
15.3 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++ 4.5 para Windows. La aplicación generada fue probada
en Windows. Procesando el archivo de recursos generado con el conversor de
recursos del Borland C++ 2.0 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.
15.3.1 DLGCLI.CMM: generado por AppExpert y modificado por el autor
/*
Project consulta
Copyright © 1996. All Rights Reserved.
SUBSYSTEM:
FILE:
AUTHOR:
consulta.exe Application
dlgcli.cmm
Domingo Eduardo Becker
OVERVIEW
========
Source file for implementation of ConsDlgClient (TDialog).
216
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
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 ?
tam) && tam
:
else
Cumple = QueContenga ?
!= 0
:
! ExprRegular.Buscar(CampoRegAct, inicio,
! ExprRegular.Examinar(CampoRegAct, 0, tam);
strstr((const char *) CampoRegAct, Expresion)
stricmp(CampoRegAct, Expresion) == 0;
// Si cumple agregarlo a la lista:
if (Cumple) {
Reg.printf("%u: %s", i, (char *) CampoRegAct);
218
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);
}
}
15.3.2 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
Consultas a Archivos Base de Datos BCE
/*
Project consulta
Copyright © 1996. 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
Consultas a Archivos Base de Datos BCE
15.3.3 CONSAPP.CMM: generado por AppExpert
/*
Project consulta
Copyright © 1996. 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
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;
}
15.3.4 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 © 1996. 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
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.
15.4 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 09.
15.4.1 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
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
define
define
define
define
define
define
define
define
define
define
tdAscii0
0
tdEnt8
1
tdEnt8ns
2
tdEnt16
3
tdEnt16ns
4
tdEnt32
5
tdEnt32ns
6
tdfloat
7
tddouble
8
tdlongdouble 9
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
Consultas a Archivos Base de Datos BCE
Ent16 EscrEstrReg( ArchivoSO & arch, const Vector<DescrCampoABD> & dc );
Ent16 LeerEstrReg( ArchivoSO & arch, Vector<DescrCampoABD> & dc );
// La
// En
// Si
// Si
Ent16
siguiente función global da soporte a PonerCampo y SacarCampo
Acceder:
Sentido == 0 se mueve así: EstrReg ===> BlqUsuario
Sentido != 0 se mueve así: EstrReg <=== BlqUsuario
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
Consultas a Archivos Base de Datos BCE
virtual ~ArchBD();
Ent16
Ent16
Ent16
Ent16
Abrir( const char * nombre, Ent8 e_s );
Crear( const char * nombre, const DescrCampoConstABD * er );
Crear( const char * nombre, Vector<DescrCampoABD> & er );
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
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
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
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
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
15.4.2 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
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
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
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
Consultas a Archivos Base de Datos BCE
if (! tamarch || ! TamRegReal) return 0;
return (tamarch - DesplInic) / TamRegReal;
}
15.4.3 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;
}
15.4.4 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
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;
}
15.4.5 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;
}
15.4.6 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
Consultas a Archivos Base de Datos BCE
15.4.7 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;
}
15.4.8 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
Consultas a Archivos Base de Datos BCE
15.4.9 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)
};
15.4.10 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
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
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
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.
16.1 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
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
ExprRelacional
ExprRelacional
ExprRelacional
ExprRelacional
ExprRelacional
ExprArit ;
'==' ExprArit
'!=' ExprArit
'<' ExprArit
'<=' ExprArit
'>' 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
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 = 12 + 4 - 5;
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 200
242
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.
Las instrucciones y su correspondiente semántica son:
NoOp
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
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
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
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
operando de la izquierda del operador. El operando de la derecha
contenido del registro del intérprete. Aplicar la operación Suma
números y almacenar el resultado en el registro del intérprete.
incrementar el puntero de instrucción.
será el
será el
a esos
Luego
Restar
Desapilar 1 número de la pila del intérprete; éste número
operando de la izquierda del operador. El operando de la derecha
contenido del registro del intérprete. Aplicar la operación Resta
números y almacenar el resultado en el registro del intérprete.
incrementar el puntero de instrucción.
será el
será el
a esos
Luego
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
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 16.2. 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
Un traductor sencillo para un lenguaje simple
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
Restar
Asignar c
MoverCte 10
Asignar c
MoverCte 0
Asignar a
MoverVar c
IrASiF 56
MoverCte 0
Asignar b
MoverVar b
ApilarReg
MoverCte 1
Sumar
Asignar b
MoverVar a
ApilarReg
MoverCte 1
Sumar
Asignar a
MoverVar b
ApilarReg
MoverCte 20
Igual
IrASiF 35
MoverVar c
ApilarReg
MoverCte 1
Restar
Asignar c
IrA 31
16.4 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.
16.4.1 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
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);
}% ;
16.4.2 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);
}% ;
16.4.3 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
Un traductor sencillo para un lenguaje simple
16.4.4 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.
16.4.5 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
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.
16.4.6 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.
16.4.7 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ó.
16.5 Implementación del Traductor
El traductor está implementado
especificadas en LENG.GLC.
por
las
acciones
semánticas
251
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.
16.5.1 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
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.
16.5.2 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 0.
2. 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 0.
3. AgregarSentCI
253
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 0.
7. 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
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.
16.6 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.
16.6.1 Estructuras de Datos usadas
Las estructuras siguientes fueron descriptas en 16.5.1:
variables, variables, código objeto y programa.
tablas de
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
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, Div0.
16.6.2 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
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 0.
6. 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
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++ 4.5. 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
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.
16.8 Guía del Usuario y Manual de Referencia del Programador del
Lenguaje Fuente
16.8.1 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.
16.8.2 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
Un traductor sencillo para un lenguaje simple
16.8.3 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
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.
16.8.4 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
1. La más alta
2. Operadores
multiplicativos
3. Operadores
aditivos
Operador
!
()
*
/
+
-
Observaciones
Menos unario
Negador lógico
Paréntesis
Multiplicación
División
Suma
Resta
261
Un traductor sencillo para un lenguaje simple
Precedencia
4. Operadores
Relacionales
5. Operador lógico
6. Operador lógico
Operador
==
!=
<
<=
>
>=
&&
||
Observaciones
Igual a
Distinto de
Menor que
Menor o igual que
Mayor que
Mayor o igual que
Conjunción
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.
16.9 Módulos fuentes Independientes del Sistema Operativo
16.9.1 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
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
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
TERMINAL
IGNORAR
IGNORAR
Ident
Real
Blancos
Coment
# MAYIGUALMIN
Leng --> Decl Sents %{
// Resolver rótulos de saltos hacia adelante:
ResolverRotulos();
}% ;
Decl --> 'Var' Idents ';'| ;
Idents --> MásIdents Ident %{
264
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
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
Un traductor sencillo para un lenguaje simple
16.9.2 IDENT.ER: expresión regular para un identificador
// Exp reg para describir un identificador:
# Letra [a-zA-Z_áéíóúñÑüÜ]
# Digito [0-9]
Letra (Letra | Digito)*
16.9.3 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 + ) ?
16.9.4 BLANCOS.ER: expresión regular para los blancos
// Expresión regular para blancos:
[ \n\r\f\t\v] +
16.9.5 COMENT.ER: expresión regular para los comentarios
// Exp reg para comentarios similares a éste:
/ / .*
16.9.6 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
//
//
//
//
o
//
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
un identificador de variable a crear.
class CodInt {
public:
Ent16ns Codigo;
Ascii0 Operando;
267
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
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
16.9.7 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
Un traductor sencillo para un lenguaje simple
16.9.8 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
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
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
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
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
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;
}
16.10 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++.
16.10.1 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 © 1995. 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
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.
16.10.2 DLGINTER.CMM: generado por AppExpert y modificado por el autor
/*
Project leng
Copyright © 1995. 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
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
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
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
Parte 5. Anexos, Codigos Fuentes y
Conclusión.
280
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.
17.1 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
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.
17.2 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
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".
Corpus
Datos:
TG
Gramática
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".
Gramática 1
Datos:
Gramática 2
TG
Respuesta: la mejor
gramática es ...
Corpus
Fig. 26 La Teoría General como un mecanismo de selección de
gramáticas.
283
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.
17.3 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
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
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.
17.4 La Gramática Generativa
17.4.1 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:
Constitucion
Entrada
(instrucciones o reglas)
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
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.
17.4.2 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.
17.4.3 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
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:
1
El
caballo
salta
el
alambrado .
2
El
caballo
salta
el
alambrado .
3
El
caballo
salta
el
alambrado .
El
caballo
salta
el
alambrado .
4
Fig. 29 Separación de la oración en constituyentes inmediatos.
288
Introducción a las Gramáticas Generativas
o bien:
1
O
2
S
3
MD
4
MD
P
N
N
OD
V
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
5
El
caballo
salta
el
N
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
OD
V
MD
El
MD
N
caballo
salta
el
N
alambrado
289
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
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
OD
V
MD
La
MD
N
niña
toma
la
N
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
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
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 1960. 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
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
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 1960.
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
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
Un poco de Historia
18.2 Historia del desarrollo de los Generadores Presentados en este
trabajo
18.2.1 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.
18.2.2 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ó.
18.2.3 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
Un poco de Historia
18.2.4 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.
18.2.5 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 2.
3. 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
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 1992. La tercera revisión en Junio de 1993. Y
la última revisión en Febrero de 1996, durante su inclusión en este trabajo.
299
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.
19.1 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: 20-10-93
// 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 23767
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
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
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
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
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
Ent8
Ent8
1 );
Ent8
Agregar( const Simbolo & s, Ent8 AceptRedundancia = 1 );
Agregar( const ConjSimbLSE & cs, Ent8 AceptRedundancia = 1 );
Agregar( Ent16ns codigo, Ent8ns noterminal, Ent8 AceptRedundancia =
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
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
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
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
// cuyo tamaño es el
actual
// (NumNoTerm()). En
// pvaPrim, si no es
y pvaPrim apuntan cada uno a un vector de Alfabetos
número de simb. no terminales de la gramática
pvaSig se guardan los conjuntos siguientes y en
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 12.14.1.
19.3 GENSLR.H
# ifndef __GENSLR_H
# define __GENSLR_H
307
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;
BCE)
// declarada en archabst.h ( de la biblioteca de clases
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
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
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
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;
}
19.10 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
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 << " }";
}
19.11 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;
}
19.12 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
Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x
return a;
}
19.13 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
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;
}
19.14 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
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
// si la regla es 'A --> Y' con A símbolo actual y Y' distinto a
lambda
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
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
terminal)
agreg = prim[i].Agregar( prim[psr->Codigo - 1] );
if (! iterar) iterar = agreg; // si se agregó se vuelve a
iterar
// Si el símbolo actual de la derecha (B en el ejemplo) es
anulable se
//
//
//
if
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
( 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
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;
}
19.15 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
Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x
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; //
del
// 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
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;
}
19.16 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;
}
19.17 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
<< "\nGramática:\n";
=
" << gram.Term
IterLSE<ReglaLC> i;
for (i = gram.Reglas; i != 0; ++i)
arch << i.Cursor->Dato << "\n";
return arch;
}
318
Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x
19.18 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
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
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
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;
}
19.19 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
Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x
arch << " " << *ps;
}
arch << " }\n";
}
Simbolo::Mostrar(b);
return CodError;
}
19.20 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
}
19.21 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
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;
}
19.22 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 0
}
19.23 CELEM04.CMM
// celem04.cmm
// Copyright (c) 1995 by Domingo Eduardo Becker.
// All rights reserved.
# include "genslr.h"
324
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;
}
19.24 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
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,
// aumentará la gramática. Si O es el símbolo,
// O' ---> O
rn = new NodoLSE<ReglaLC>;
if (rn == 0) return 0;
rn->Dato.Agregar(cad1, 0, 1); // símbolo parte
0, no terminal)
rn->Dato.Agregar(*ps);
rn->pSig = g.Reglas.Entrada; // se engancha la
del
g.Reglas.Entrada = rn;
// conjunto de reglas
se crea una regla que
la regla a agregar será:
izq de la regla (O', cod
regla nueva al comienzo
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
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
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
Códigos Fuentes de la Biblioteca de Soporte de SLR1 v 2.x
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
todo
// 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 != 0.
329
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.
}
19.25 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;
}
19.26 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;
}
19.27 CEST04.CMM
// cest04.cmm
// Copyright (c) 1995 by Domingo Becker.
// All rights reserved.
# include "genslr.h"
# include <archivo.h>
330
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
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;
}
19.28 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
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.
20.1 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
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) 22-2488/39-0567\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
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)
22-2488\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
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
if (nArg <
salida
return
}
nArg, char * cArg[]) {
2) {
<< "Falta el nombre del archivo con la gramatica.";
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
include
include
include
include
include
"progreso.h"
"dlgerror.h"
"dlgconf.h"
<archivo.h>
"genslr.h"
<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
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 2.3 - 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
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) 22-2488\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
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
Códigos Fuentes de SLR1 v 2.x
Fig. 34 Diseño de la ventana donde se muestra el progreso de la
generación con SLR1 v 2.x
/*
Project slr1
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
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:
Fig. 35 Diseño del diálogo donde se muestran los conflictos.
/*
Project slr1
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
Códigos Fuentes de SLR1 v 2.x
}
TDlgConflicto::~TDlgConflicto ()
{
Destroy();
// INSERT>> Your destructor code here.
}
void TDlgConflicto::SetupWindow ()
{
TDialog::SetupWindow();
//
if
if
if
if
if
INSERT>> Your code here.
(Titulo.Long()) SetCaption(Titulo);
(Estado.Long()) SetDlgItemText(TXT_1, Estado);
(Terminal.Long()) SetDlgItemText(TXT_2, Terminal);
(AccPrevia.Long()) SetDlgItemText(TXT_3, AccPrevia);
(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:
Fig. 36 Diseño del diálogo donde se muestran los errores de
sintaxis.
/*
Project afd
Copyright © 1995. All Rights Reserved.
SUBSYSTEM:
afd.apx Application
342
Códigos Fuentes de SLR1 v 2.x
FILE:
AUTHOR:
dlgerror.cmm
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
Códigos Fuentes de SLR1 v 2.x
Fig. 37 Diseño de la ventana principal de SLR1 v 2.x para modo
gráfico.
/*
Project slr1
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
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
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.
21.1 EXPREG.H
Este módulo se presenta en la sección 12.14.2.
21.2 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>
# endif
// independencia de la implementación de los tipos
// 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
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
//
//
La tabla de descripciones de hoja es una LSE<DescrHoja>.
Con el campo IdCar se accede a una lista de cadenas donde se encuentra
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
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
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) 22-2488/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
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
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
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
Códigos Fuentes de la Biblioteca ExpReg v 1.x
#
#
#
#
#
include
include
include
include
include
"blanco.cmm"
"coment.cmm"
"caract.cmm"
"rango.cmm"
"idafd.cmm"
static RecSimb vrs[] =
0,
1,
0,
1,
0)
_id_CteCar,
1,
_id_Rango,
1,
_id_Ident,
1,
_id_Disyuncion,
0,
_id_Uno_o_Mas,
0,
_id_Cero_o_Mas,
0,
_id_Opcional,
0,
_id_Abre_Paren,
0,
_id_Cierra_Paren, 0,
_id_Numeral,
0,
_id_CteCar,
0,
};
{
& afdBlanco,
& afdComent,
& afdCaract,
& afdRango,
& afdIdent,
0,
0,
0,
0,
0,
0,
0,
0,
0, // ignora los blancos (Id == 0)
0, // ignora los comentarios (Id ==
0,
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.
21.8 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
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();
}
21.10 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
Códigos Fuentes de la Biblioteca ExpReg v 1.x
21.11 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;
}
21.12 ER03.CMM
// er03.cmm
Mar 17 Ene 95
// Copyright (c) 1995 by Domingo Becker.
// All rights reserved.
355
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;
}
21.13 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;
}
21.14 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
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
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
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
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
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
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
Códigos Fuentes de la Biblioteca ExpReg v 1.x
Caracter.Vaciar();
TablaIdent.Vaciar();
return 0;
}
raiz->Tipo =
raiz->pIzq =
raiz->pDer =
RaizAnalSint
N_CON; // '.'
RaizAnalSint;
HojaFinal;
= 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
Caracter.Vaciar();
TablaIdent.Vaciar();
delete [] Sig; // Vector de SiguientePos(h) para cada hoja
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
Caracter.Vaciar();
TablaIdent.Vaciar();
delete [] Sig; // Vector de SiguientePos(h) para cada hoja
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
Caracter.Vaciar();
TablaIdent.Vaciar();
delete [] Sig; // Vector de SiguientePos(h) para cada hoja
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;
sintáctico
h
sintáctico
h
sintáctico
h
363
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;
// 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
elemento
// de U no está en el actual
if (pPos == 0) break; // salir, este estado no es igual al
actual
}
// 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
estado
U->Vaciar();
// apuntado por pAuxEst, eliminar U y usar
ése.
delete U;
U = 0;
364
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
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
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;
}
21.15 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
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;
}
21.16 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;
}
21.17 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
Códigos Fuentes de la Biblioteca ExpReg v 1.x
}
return 1;
}
369
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.
22.1 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)
22-2488/39-0567\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
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
Códigos Fuentes de AFD v 3.x
22.2 AFDEDTVW.CMM
/*
Project AFD
Copyright © 1995. 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
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
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
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 © 1995. 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
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.
22.4 DLGDATOS.CMM
El diseño del diálogo donde se introducen los datos faltantes es el
siguiente:
Fig. 38 Diseño del diálogo para la entrada de datos adicionales
usados por AFD v 3.x
/*
Project afd
Copyright © 1995. All Rights Reserved.
SUBSYSTEM:
FILE:
AUTHOR:
afd.apx Application
dlgdatos.cmm
Domingo Eduardo Becker
OVERVIEW
376
Códigos Fuentes de AFD v 3.x
========
Source file for implementation of OtrosDatosAFD (TDialog).
*/
#include <owl\owlpch.h>
#pragma hdrstop
#include "dlgdatos.h"
# include <archivo.h>
//
// Build a response table for all messages/commands handled
// by the application.
//
DEFINE_RESPONSE_TABLE1(OtrosDatosAFD, TDialog)
//{{OtrosDatosAFDRSP_TBL_BEGIN}}
EV_BN_CLICKED(IDOK, Aceptar),
//{{OtrosDatosAFDRSP_TBL_END}}
END_RESPONSE_TABLE;
//{{OtrosDatosAFD Implementation}}
static OtrosDatosAFDXfer OtrosDatosAFDData;
OtrosDatosAFD::OtrosDatosAFD (TWindow* parent, TResId resId, TModule*
module):
TDialog(parent, resId, module)
{
//{{OtrosDatosAFDXFER_USE}}
ChkGenCMM = new TCheckBox(this, IDC_GENCMM, 0);
SetTransferBuffer(&OtrosDatosAFDData);
//{{OtrosDatosAFDXFER_USE_END}}
// INSERT>> Your constructor code here.
}
OtrosDatosAFD::~OtrosDatosAFD ()
{
Destroy();
// INSERT>> Your destructor code here.
}
void OtrosDatosAFD::SetupWindow ()
{
TDialog::SetupWindow();
// INSERT>> Your code here.
ArchTXT = NomExt(ArchTXT, ".TXT");
ArchCMM = NomExt(ArchTXT, ".CMM");
SetDlgItemText(IDC_ARCHTXT, ArchTXT);
SetDlgItemText(IDC_FUENTECMM, ArchCMM);
ChkGenCMM->SetCheck(BF_CHECKED);
}
void OtrosDatosAFD::Aceptar ()
377
Códigos Fuentes de AFD v 3.x
{
// INSERT>> Your code here.
ArchTXT.Reasignar(200);
GetDlgItemText(IDC_ARCHTXT, ArchTXT, ArchTXT.TamBlq());
ArchCMM.Reasignar(200);
GetDlgItemText(IDC_FUENTECMM, ArchCMM, ArchCMM.TamBlq());
if (PostfijoAFD.Reasignar(50))
GetDlgItemText(IDC_POSTFIJOAFD, PostfijoAFD, PostfijoAFD.TamBlq());
if (PostfijoResto.Reasignar(50))
GetDlgItemText(IDC_POSTFIJORESTO, PostfijoResto,
PostfijoResto.TamBlq());
if (! PostfijoResto.Long()) PostfijoResto = PostfijoAFD;
GenerarCMM = ChkGenCMM->GetCheck() == BF_CHECKED;
CmOk();
}
22.5 DLGERROR.CMM
Fue incluido en la sección 20.4.
378
Bibliografía
Capítulo 23: Bibliografía
23.1 Principales (por orden de importancia)
Alfred Aho - Ravi Sethi - Jeffrey Ullman [1990]. "Compiladores: Principios
Técnicas y Herramientas". Addison-Wesley Iberoamericana.
Sanchis Llorca - Galán Pascual [1986]. "Compiladores: Teoría y
Construcción". Paraninfo.
Alfred Aho - Jeffrey Ullman [1977]. "Principles of Compiler Design".
Addison-Wesley.
Manuales del Programador del Sistema Operativo XENIX. Microsoft Corp.
Bjarne Stroustrup [1992]. "The C++ Programming Language - 2nd Edition".
Addison-Wesley.
Manuales del Programador del Borland C++ 4.02 para Windows. Borland Intl.
Inc.
Manuales del Programador del Borland C++ 2.0 para OS/2. Borland Intl. Inc.
Manuales del Programador del Borland C++ 4.5 para Windows. Borland Intl.
Inc.
Manuales del Programador de Paradox 4.5 para Windows. Borland Intl. Inc.
Manuales del Programador de dBase V para Windows. Borland Intl. Inc.
"Reference Guide" del Borland Visual Solutions Pack Volume 1 para
Windows. Capítulo 51: "SQL Primer", págs. 665 a 674. Borland Intl. Inc.
J. Tamagnini [1994]. "Un generador de analizadores sintácticos recursivos
descendentes". Tesis de Grado. Universidad Católica de Santiago del Estero.
Allen Holub [1990]. "Compiler Design in C". Prentice Hall.
Christian Nique [1985]. "Introducción metódica a la gramática generativa".
Ediciones Cátedra.
379
Bibliografía
Bjarne Stroustrup [1994]. "The design and Evolution of C++". Addison
Wesley. Págs. 337 a 381.
23.2 Secundarios (por orden alfabético)
A. V. Aho y S. C. Johnson [1974]. "LR Parsing". Computing Surveys 6:2,
págs 99-124.
A. V. Aho, S. C. Johnson y J. D. Ullman [1977]. "Code generation for
expressions with common subexpressions". Journal ACM 14:1, págs. 146-160.
A. V. Aho, B. W. Kernighan y P. J. Weinberger [1979]. "AWK - a pattern
scanning and processing language". Software - Practice and Experience 9:4,
págs. 267-280.
A. V. Aho y J. D. Ullman [1972]. "The Theory of Parsing, Translation and
Compiling, Vol I: Parsing". Prentice-Hall.
A. V. Aho y J. D. Ullman [1973a]. "The Theory of Parsing, Translation and
Compiling, Vol II: Compiling". Prentice-Hall.
A. V. Aho y J. D. Ullman [1973b]. "A technique for speeding up LR(k)
parsers". SIAM J. Computing 2:2, págs. 106-127.
T. Anderson, J. Eve y J. J. Horning [1973]. "Efficient LR(1) Parsers". Acta
Informatica 2:1, págs. 12-39.
R. C. Backhouse [1976]. "An alternative aproach to the improvement of LR
parsers". Acta Informatica 6:3, págs. 277-296.
J. W. Backus [1981]. "Transcript of presentation on the history of Fortran I, II
and III". En Wexelblat [1981], págs 45-66.
F. L. Bauer [1976]. "Historical remarks on compiler construction". en Bauer y
Eichel [1976], págs 603-621. Suplemento de Ershov. A. P. págs 622-626.
F. L. Bauer y J. Eichel [1976]. "Compiler Construction: An Advanced
Course". 2da Edición. Lecture Notes in Computer Science 21,
Springer-Verlag, Berlín.
380
Bibliografía
B. M. Brosgol [1974]. "Deterministic Translation Grammars". Tesis doctoral,
TR 3-74. Universidad de Harvard, Cambridge, Massachusetts.
Noam Chomsky [1956]. "Three models for the description of language". IRE
Trans. on Information Theory IT-2:3, págs. 113-124.
M. E. Conway y W. L. Maxwell [1963]. "CORC - the Cornell computing
language". Comm. ACM 6:6, págs. 317-321.
M. E. Conway y T. R. Wilcox [1973]. "Design and implementation of a
diagnostic compiler for PL/I". Comm. ACM 16:3, págs. 169-179.
A. J. Demers [1975]. "Elimination of single productions and merging of
nonterminal symbols in LR(1) grammars". Journal of Computer Languages
1:2, págs. 105-119.
F. DeRemer [1969]. "Practical Translators for LR(k) Languages". Tesis
doctoral, M.I.T., Cambridge, Massachusetts.
F. DeRemer [1971]. "Simple LR(k) grammars". Comm. ACM 14:7, págs.
453-460.
J. Earley [1970]. "An efficient context-free parsing algorithm". Comm. ACM
13:2, págs. 94-102.
J. Earley [1975]. "Ambiguity and precedence in syntax description". Acta
Informática 4:2, págs. 183-192.
S. I. Feldman [1979]. "Implementation of a portable Fortran 77 compiler
using modern tools". ACM SIGPLAN Notices 14:8, págs 255-265.
J. M. Foster [1968]. "A syntax improving program". Computer Journal 11:1,
págs. 31-34.
C. A. R. Hoare [1962]. "Report on the Elliott ALGOL translator". Computer
Journal 5:2, págs. 127-129.
D. A. Huffman [1954]. "The synthesis of sequential machines". J. Franklin
Inst. 257, págs. 3-4, 161, 190, 275-303.
P. Z. Ingerman [1967]. "Panini-Backus form suggested". Comm. ACM 10:3,
pág. 137.
381
Bibliografía
E. T. Irons [1961]. "A syntax directed compiler for ALGOL 60". Comm.
ACM 4:1, págs 51-55.
J. F. Jarvis [1976]. "Feature recognition in line drawings using regular
expressions". Proc. 3rd Intl. Joint Conf. on Pattern Recognition, págs.
189-192.
W. L. Johnson, J. H. Porter, S. I. Ackley y D. T. Ross [1968]. "Automatic
generation of efficient lexical processors using finite state techniques".
M. L. Joliat [1976]. "A simple technique for partial elimination of unit
productions from LR(k) parser tables". IEEE Trans. on Computers C-25:7,
págs. 763-764.
T. Kasami [1965]. "An efficient recognition and syntax analysis algorithm for
context-free languages". AFCRL-65-758, Air Force Cambridge Research
Laboratory, Bedford, Massachusetts.
B. W. Kernigan y R. Pike [1984]. "The UNIX Programming Environment".
Prentice-Hall.
S. C. Kleene [1956]. "Representation of events in nerve nets". en Shannon y
McCarthy [1956], págs. 3-40.
D. E. Knuth [1964]. "Backus Normal Form vs. Backus Naur Form". Comm.
ACM 7:12, págs. 735 y 736.
D.E. Knuth[1965]. "On the translation of languages from left to right".
Information and Control 8:6, págs. 607-639.
D.E. Knuth [1971]. "Top-down syntax analysis". Acta Informatica 1:2, págs.
79-110.
D.E. Knuth [1973]. "The Art of Computer Programming: Vol 1 2nd Edition,
Fundamental Algorithms", Addison-Wesley.
A. J. Korenjak [1969]. "A practical method for constructing LR(k)
processors". Comm ACM 12:11, págs. 613-623.
M. E. Lesk [1975]. "Lex - a lexical analyzer generator". Computing Science
Technical Report 39, AT&T Bell Laboratories.
382
Bibliografía
J. K. Levy [1975]. "Automatic correction of syntax errors in programming
languages". Acta Informatica 4, págs. 271-292.
P. M. Lewis II, D. J. Rosenkrantz y R. E. Stearns [1976]. "Compiler Design
Theory". Addison-Wesley.
P. M. Lewis II y R. E. Stearns [1968]. "Syntax-directed transduction". J.
ACM 15:3, págs. 465-488.
P. Lucas [1961]. "The structure of formula translators". Elektronische
Rechenanlagen 3, págs 159-166.
J. McCarthy [1963]. "Towards a mathematical science of computation".
Information Processing 1962, págs 21-28.
R. M. McClure [1965]. "TMG - a syntax-directed compiler". Proc. 20th ACM
National Conf., págs. 262-274.
W. S. McCullough y W. Pitts [1943]. "A logical calculus of the ideas
immanent in nervous activity". Bulletin of Math. Biophysics 5, págs. 115-133.
R. McNaughton y H. Yamada [1960]. "Regular expressions and state graphs
for automata". IRE Trans. on Electronic Computers EC-9:1, págs. 38-47.
E. F. Moore [1956]. "Gedanken experiments in sequential machines". En
Shannon y McCarthy [1956], págs 129-153.
P. G. Moulton y M. E. Muller [1967]. "DITRAN - a compiler emphasizing
diagnostics". Comm. ACM 10:1, págs. 52-54.
P. Naur [1963]. "Revised report on the algorithmic language ALGOL 60".
Comm. ACM 6:1, págs. 1-17.
D. Pager [1977]. "Eliminating unit productions from LR(k) parsers". Acta
Informatica 9, págs. 249-268.
V. R. Pratt [1973]. "Top-down operator precedence". ACM Symposium on
Principles of Programming Languages, págs. 41-51.
P. Purdom y C. A. Brown [1980]. "Semantic routines and LR(k) parsers".
Acta Informatica 14:4, págs. 299-315.
383
Bibliografía
M. O. Rabin y D. Scott [1959]. "Finite automata and their decision
problems". IBM Journal of Research and Development 3:2, págs. 114-125.
J. Röhrich [1980]. "Methods for the automatic construction of error correcting
parsers". Acta Informatica 13:2, págs. 115-139.
D. J. Rosenkrantz y R. E. Stearns [1970]. "Properties of Deterministics
top-down grammars". Information and Control 17:3, págs. 226-256.
D. V. Schorre [1964]. "Meta-II: a syntax-oriented compiler writing language".
Proc. 19th ACM National Conf., DI., 3-1 - DI. 3-11.
C. Shannon y J. McCarthy [1956]. "Automata Studies". Princeton University
Press.
E. Soisalon-Soininem [1980]. "On the space optimizing effect of eliminating
single productions from LR parsers". Acta Informatica 12, págs. 157-174.
E. Soisalon-Soininem y E. Ukkonen [1979]. "A method for transforming
grammars into LL(k) form". Acta Informatica 12, págs. 339-369.
R. E. Stearns [1971]. "Deterministic top-down parsing". Proc. 5th Annual
Princeton Conf. on Information Sciences and Systems, págs. 182-188.
J. Strong, J. H. Wegstein, A. Tritter, J. Olsztyn, O. Mock y T. Steel [1958].
"The problem of programming communication with changing machines: a
proposed solution". Comm. ACM 1:8 (agosto), págs 12-18. Segunda parte 1:9
(setiembre), págs 9-15. Informe del Comité sobre lenguas universales.
K. C. Tai [1978]. "Syntactic error correction in programming languages".
IEEE Trans. Software Engineering SE-4:5, págs. 414-425.
T. Tokuda [1981]. "Eliminating unit reductions from LR(k) parsers using
minimum contexts". Acta Informatica 15, págs. 447-470.
D. A. Watt [1977]. "The parsing problem for affix grammars". Acta
Informatica 8, págs. 1-20.
J. H. Wegstein [1981]. "Notes on ALGOL 60". en Wexelblat [1981], págs.
126-127.
R. L. Wexelblat [1981]. "History of Programming Languages". Academic
Press. New York.
384
Bibliografía
D. Wood [1969]. "The Theory of left factored languages". Computer Journal
12:4, págs. 349-356.
D. H. Younger [1967]. "Recognition and parsing of context-free languages in
time n3 ". Information and Control 10:2, págs. 189-208.
385
Conclusiones
Capítulo 24: Conclusiones
Muchos trabajos de programación, incluyendo los trabajos complejos
como por ejemplo el desarrollo de analizadores sintácticos, son tareas
repetitivas cuya ejecución podría automatizarse. Por ejemplo, nadie escribiría
hoy en día en forma directa el código fuente para una interfase de un programa
que trabaje en modo gráfico, sino que más bien usaría un programa que lo
genere a partir de especificaciones de alto nivel. Lo único que haría "a mano"
es el desarrollo de las partes de la interfase que no fueron automatizadas por el
generador de interfases. En el desarrollo de interfases es normal olvidarse de
ciertas restricciones de la interfase para el desarrollo de aplicaciones, lo que
provocará, en el mejor de los casos, que el programa se "cuelgue", y en el peor,
que provoque una caida de toda una red (si se está trabajando en red).
Entonces, es evidente que, por más compleja que sea una tarea de
desarrollo de software, si la misma es automatizable se la debe automatizar.
Pero, para que esto sea posible, el desarrollo de esos generadores de software
también debe ser una tarea medianamente simple, de manera que no se pierda
demasiado tiempo en su desarrollo (se debe recordar que los programadores
constantemente trabajan contra reloj). Es poco común ver programadores de
nuestro medio que sigan esta técnica de trabajo (esto es, construir un
generador para automatizar el desarrollo de software en las áreas que sea
posible), y esto se debe, principalmente, a que en nuestro medio no hay
disponible una herramienta que simplifique el desarrollo de tales traductores
que generarán, a partir de especificaciones de alto nivel, código fuente que
ejecute tareas rutinarias. Por ejemplo, en un sistema de bases de datos, aunque
la estructura del registro de un archivo sea distinto para diferentes
aplicaciones, los módulos de alta-bajas-modificaciones son muy similares.
El aporte más grande de este trabajo quizás sea el de simplificar el
desarrollo de tales traductores para generar partes de programas. Antes, para la
escritura de un traductor, un programador debía tener conocimientos de:
gramáticas generativas, técnicas de análisis sintáctico, técnicas de análisis
lexicográfico, construcción de un analizador sintáctico, construcción de un
analizador lexicográfico y construcción del esquema de traducción, entre otras
de menor importancia. Mientras que, es objetivo de este trabajo que para el
desarrollo de un traductor, un programador solamente deba tener
conocimientos de: gramáticas generativas, construcción del esquema de
traducción, conocimiento de la técnica de análisis sintáctico y de análisis
lexicográfico usada por los analizadores obtenidos a partir de los generadores
que se presentan, y conocimiento del lenguaje objeto usado (el C++). La parte
teórica más compleja ha sido eliminada, y la que quedó requiere de poca
386
Conclusiones
inversión de tiempo para su aprendizaje; a tal efecto se incluye documentación
lo suficientemente extensa en este trabajo como para poder orientar a cualquier
programador que se inicie en el área, pero no se incluye el aprendizaje de C++,
porque se asume que el mismo debe formar parte del conjunto de herramientas
de cualquier programador universitario.
Los generadores presentados en este trabajo son, finalmente, un humilde
aporte del autor a todos aquellos interesados en el uso de la Teoría de los
Lenguajes Formales en sus aplicaciones y para la simplificación del desarrollo
de las mismas.
387
Descargar