Download Algoritmo para Convertir una Expresión Regular en un

Document related concepts
no text concepts found
Transcript
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
.
Anulable(si) y Anulable(sd)
si
sd
|
Anulable(si) o Anulable(sd)
si
sd
*
Verdadero
s
+
Anulable(s)
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