Beruflich Dokumente
Kultur Dokumente
(Bacharelado)
RONALD GLATZ
BLUMENAU, DEZEMBRO/2000
2000/2-49
PROTÓTIPO PARA TRANSFORMAÇÃO DE UMA
EXPRESSÃO REGULAR PARA UMA FUNÇÃO
EQUIVALENTE EM PASCAL, UTILIZANDO DOIS
ALGORITMOS BASEADOS NO TEOREMA DE KLEENE
RONALD GLATZ
BANCA EXAMINADORA
ii
DEDICATÓRIA
iii
AGRADECIMENTOS
A Deus.
A todos que, de alguma forma, apoiaram minhas decisões e vontades. Àqueles que
souberam respeitar, seja no acerto ou no erro.
Ao professor e orientador deste trabalho, professor José Roque Voltolini da Silva, pelo
apoio, paciência e perseverança.
iv
SUMÁRIO
DEDICATÓRIA ...................................................................................................................iii
AGRADECIMENTOS.......................................................................................................... iv
SUMÁRIO............................................................................................................................. v
LISTA DE FIGURAS........................................................................................................... ix
RESUMO ............................................................................................................................ xv
1 INTRODUÇÃO ................................................................................................................ 1
2.3 Alfabeto.......................................................................................................................... 6
2.4.1 Gramáticas.................................................................................................................... 7
2.4.1.1 Reconhecedores.......................................................................................................... 9
v
2.4.1.2.6 Autômato finito não determinístico (AFND) ........................................................ 18
2.5.1 Compiladores.............................................................................................................. 28
vi
4.3.2 Estruturas de dados ..................................................................................................... 64
5.2.1 Tela principal do protótipo que implementa o algoritmo descrito por [HOP1979] ....... 70
5.3.1 Tela principal do protótipo que implementa o algoritmo descrito por [SIL2000] ......... 78
vii
6 COMPARAÇÃO DOS PROTÓTIPOS............................................................................ 82
7 CONCLUSÃO ................................................................................................................ 96
viii
LISTA DE FIGURAS
Figura 1 - Gramáticas e Linguagens ....................................................................................... 9
Figura 10 - AFND................................................................................................................ 18
Figura 11 - AFε.................................................................................................................... 19
Figura 14 - AFND............................................................................................................... 22
Figura 23 - BNF................................................................................................................... 27
ix
Figura 24 - Fases de um compilador..................................................................................... 28
Figura 26 - Concatenação..................................................................................................... 32
Figura 27 - União................................................................................................................. 32
Figura 33 - AFD................................................................................................................... 36
Figura 46 - LLC para gerar expressão pós-fixada pelo algoritmo descrito por [SIL2000] ..... 44
x
Figura 49 – “Tabela do Desmonte” ...................................................................................... 46
xi
Figura 74 - Tela principal de hopcroft.exe ............................................................................ 70
xii
Figura 100 - Abertura de autômatos em moore.exe............................................................. 100
xiii
LISTA DE ABREVIATURAS E SIGLAS
AFD - AUTÔMATO FINITO DETERMINÍSTICO
CR - CONJUNTO REGULAR
ER - EXPRESSÃO REGULAR
GT - GRAFO DE TRANSIÇÕES
xiv
RESUMO
xv
ABSTRACT
xvi
1
1 INTRODUÇÃO
Este trabalho apresenta a transformação de uma expressão regular (ER) em uma
função na linguagem de programação Pascal, utilizando para isto os algoritmos descritos por
[HOP1979] e [SIL2000] e que são baseados no Teorema de Kleene. Estes algoritmos
mostram diferentes métodos de aplicação do Teorema de Kleene na transformação de
expressões regulares em autômatos finitos.
O Teorema de Kleene define que qualquer conjunto reconhecido por uma máquina de
estado finito é regular, e qualquer ER pode ser representada através de uma máquina de
estados finitos ([GER1995]).
O Autômato Finito é o conjunto de regras que define a validade de uma ER, conforme
apresentado em [AHO1995]. Quando AFD poderá haver somente uma entrada válida no
conjunto de regras para avaliação da ER, sendo que para AFND, há mais de uma entrada
válida ([GER1995]).
1.1 OBJETIVOS
O objetivo principal do trabalho é a automatização do processo de transformação de
uma expressão regular em um autômato finito, com base no Teorema de Kleene que é
aplicado através dos dois algoritmos que serão apresentados.
2 REVISÃO BIBLIOGRÁFICA
Neste capítulo serão apresentados os tópicos básicos e essenciais para contextualização
do presente trabalho.
Uma máquina de estado finito pode reconhecer conjuntos que podem ser expressos na
forma de expressões regulares, onde cada expressão representa um conjunto em particular.
Diversos sistemas naturais ou construídos pelo homem podem ser associados ao estudo
dos sistemas de estados finitos. Por exemplo, o elevador. Cada estado sumariza as
informações de "andar corrente" e "direção do movimento do elevador". Neste caso, as
entradas para o sistema são as requisições pendentes. Em computação existem diversos
exemplos de sistemas de estados finitos e que utilizam a teoria dos autômatos finitos como
elemento central no seu desenvolvimento. Como exemplo disto estão alguns editores de texto
e analisadores léxicos encontrados em compiladores ([MEN1998]).
6
2.3 ALFABETO
O termo alfabeto denota qualquer conjunto finito de símbolos. Por exemplo, o
conjunto {0,1} é o alfabeto binário ([AHO1995]). Um conjunto vazio também é considerado
um alfabeto ([MEN1998]).
2.4 LINGUAGENS
Toda troca de informação utiliza-se de determinadas regras que impõem padrões a
comunicação, seja entre os seres humanos, entre o humano e o computador ou mesmo entre
computadores. Linguagem é, portanto, o conjunto de todos os padrões possíveis.
Uma linguagem pode ser definida como sendo uma coleção de cadeias de símbolos, de
comprimento finito.
Ainda, uma linguagem denota qualquer conjunto de cadeias sobre algum alfabeto fixo,
conforme descrito em [AHO1995] e [GER1995]. As cadeias de símbolos são denominadas
palavras da linguagem, sendo formadas pela justaposição de elementos individuais, os
símbolos ou átomos da linguagem ([MEN1998]). Aplicando uma linguagem a uma unidade
computacional (reconhecedor), pode-se analisar se uma determinada palavra é válida para esta
linguagem ou não ([AHO1995]). Uma linguagem pode ser representada através de alguns
métodos básicos:
a) enumeração das cadeias de símbolos: todas as sentenças aparecem
explicitamente na enumeração. Para verificar se uma determinada cadeia pertence à
linguagem realiza-se uma busca no conjunto de sentenças da linguagem. Por
exemplo, a linguagem L={a, b, ab, ba}, onde a, b, ab, ba representam as cadeias ou
palavras desta linguagem. Da mesma forma o conjunto vazio {} ou ∅ e o conjunto
formado pela palavra vazia (^);
b) conjunto de leis de formação das cadeias ou gramática: quando se dispõe das
leis de formação da linguagem, dada uma cadeia de símbolos, só é possível afirmar
que tal cadeia pertence à linguagem se for possível, através da aplicação das leis de
formação que compõe a gramática da linguagem, sintetizar a cadeia em questão.
Ao processo de obtenção de uma sentença a partir da gramática dá-se o nome de
derivação de sentença;
7
2.4.1 GRAMÁTICAS
As gramáticas são conjuntos de regras que especificam a sintaxe da linguagem
impondo uma estrutura as suas sentenças. As regras de formação são denominadas produções
([HOP1979]). Formalmente gramáticas são dispositivos de geração de sentenças de
linguagens que às definem, caracterizadas como quádruplas ordenadas G=(Vn,Vt,P,S), onde
Vn representa o vocabulário não terminal da linguagem (este vocabulário corresponde ao
conjunto de todos os símbolos utilizados pela gramática para definir as leis de formação das
sentenças da linguagem); Vt é o vocabulário terminal contendo os símbolos ou átomos dos
quais as sentenças da linguagem são constituídas (dá-se o nome de terminais aos elementos de
Vt); P representa as leis de formação utilizadas pela gramática para definir a linguagem; S é
um elemento de Vn, o símbolo inicial ou axioma da gramática (S é um não-terminal que dá
início ao processo de geração de sentenças).
pode ser classificada como sendo dos tipos 2, 1 e 0. Da mesma forma uma linguagem do tipo
1 é também do tipo 0. A Figura 2 mostra a Hierarquia de Chomsky ([MEN1998]).
Figura 1 - Gramáticas e Linguagens
Gramáticas Regulares
Fonte: [MEN1998]
2.4.1.1 RECONHECEDORES
Ao contrário das gramáticas que são dispositivos geradores de sentenças de uma
linguagem, os reconhecedores são dispositivos de aceitação de cadeias, ou seja, realizam um
teste de aceitação para determinar se uma cadeia pertence ou não a linguagem. Conforme
visto em [MEN1998], um autômato finito (AFD, AFND ou AFε) é um reconhecedor para as
linguagens regulares. Desta forma, um reconhecedor pode ser determinístico ou não-
determinístico. Para o reconhecedor determinístico haverá apenas uma possibilidade de
10
reconhecimento, ao passo que o não-determinístico admite um número finito, maior que um,
de possibilidades de reconhecimento.
Segundo [WAZ1994], um autômato finito pode decidir para certas linguagens, se uma
cadeia pertence ou não à linguagem, ou seja, o autômato pode determinar se a cadeia é
aceitável ou não. Para um reconhecedor de estados finitos, a entrada é uma seqüência de
símbolos de um conjunto finito e que é chamado de alfabeto de entrada da máquina
representando o conjunto de símbolos que esta máquina conseguirá processar. Considera-se
que uma máquina de estados finitos possa observar apenas um símbolo de cada vez. Um
conjunto finito de estados representa as informações retidas pela máquina sobre os símbolos
anteriormente processados e a única memória desta máquina é o estado em ela foi levada após
o processamento de um símbolo. Um destes estados é o estado inicial da máquina onde ela irá
iniciar o processamento. Na leitura de um símbolo, a máquina muda de estado em função do
estado atual de máquina e do símbolo lido. A mudança de estado é chamada transição. A
operação da máquina é chamada função de transição. Esta função determina um estado novo
da máquina em função de um estado velho e um símbolo de entrada. Alguns dos estados desta
máquina são designados estados finais e se a seqüência de símbolos faz com que a máquina
pare em um estado final, então esta seqüência é aceita pela máquina. Caso contrário, a
seqüência será rejeitada.
AXIOMA: PROPRIEDADE:
r+s=s+r Comutativa
r+(s+t)=(r+s)+t Associativa
(s+t)r=sr+tr
r^=r
aa Somente a palavra aa
A linguagem denotada por uma expressão regular é chamada Conjunto Regular (CR)
([AHO1995]). Toda expressão regular r descreve um Conjunto Regular (CR) de palavras
sobre o ∑. A Figura 5 mostra exemplos de Expressões Regulares e seus respectivos
Conjuntos Regulares (CR), sendo o alfabeto ∑={a,b}.
14
(ab)* {^,ab,abab,ababab,...,abab...ab}
(a+b)* {^,a...a,b...b,aba...ba,...ba...bb...}
a+b {a,b}
a* {^,a,aa,aaa,...}
(a+b)(a+b) {aa,ab,ba,bb}
Fonte: [AHO1995]
Um conjunto é regular sobre o ∑ se e somente se este pode ser expresso por uma
expressão regular sobre o ∑. Ainda, um conjunto regular pode ser descrito por mais de uma
expressão regular ([MAN1974]).
Ainda segundo [MAN1974], conjuntos regulares são expressos de uma forma mais
conveniente utilizando-se grafo de transições.
ba* q0
b
fq0f
Nos exemplos de autômatos finitos convencionou-se que o estado inicial será sempre
um estado atingido por uma seta sem origem. Para os estados finais, convencionou-se um
circulo interno para sua representação, conforme pode ser visto na Figura 7, que também
apresenta a convenção para o estado inicial.
Figura 7 - Convenção para estado inicial e final
q0 fq0f
ESTADO ESTADO
INICIAL FINAL
Um autômato finito pode ser visto como uma máquina composta das seguintes partes:
a) fita: dispositivo de entrada que compõem a informação a ser processada. A fita é
finita tanto a esquerda como a direita, sendo dividida em células. Cada célula
representa um símbolo. Os símbolos pertencem a um alfabeto de entrada;
b) unidade de controle: reflete o estado corrente da máquina. Possui uma unidade de
leitura (cabeça da fita) a qual acessa uma célula da fita de cada vez e movimenta-se
exclusivamente para a direita. A unidade de controle possui um número finito e
definido de estados. Inicialmente, a cabeça de leitura está posicionada na célula
mais a esquerda da fita;
c) função de transição ou programa: função que comanda as leitura e define o
estado da máquina.
a a b c c b a a
controle
Fonte: [MEN1998]
O AFD é uma quíntupla da forma M=(∑, Q, δ, q0, F), onde ∑ representa o alfabeto de
símbolos de entrada, Q é o conjunto dos estados possíveis do autômato o qual é finito, δ é a
função de transição, q0 é o estado inicial (q0 ∈ Q) e F é o conjunto dos estados finais (F ⊂ Q)
([HOP1979] [MEN1998]).
b a a
1 2 3 4
a b b
a
Fonte: [AHO1995]
18
1 f30
b
b
2
a
f40
a
O AFε pode ser entendido como um modelo de AFND com transições vazias (^). Um
movimento vazio é uma transição sem leitura de símbolo algum da fita. O AFε M é uma
quíntupla M=(∑, Q, δ, q0, F) onde ∑ representa o alfabeto de símbolos de entrada, Q é o
conjunto dos estados possíveis do autômato o qual é finito, δ é a função de transição, q0 é o
estado inicial (q0 ∈ Q) e F é o conjunto dos estados finais (F ⊂ Q). A função de transição δ é
19
b a
1 2
^
Conforme será visto mais adiante, tanto o algoritmo descrito por [HOP1979] como
aquele proposto por [SIL2000] utilizam, em algum momento, a equivalência de autômatos
finitos.
Seja M=(∑, Q, δ, q0, F) um AFND qualquer a partir do qual será construído um AFD
M'=(∑, Q', δ', q'0, F') equivalente (M≡M'). No AFD M' todos os estados Q' são subconjuntos
dos estados Q de M. O conjunto dos estados finais F' é formado pelo conjunto de todos os
estados de Q' que contem o estado final de M. O estado inicial q'0 de M' é o conjunto formado
pelo estado inicial q0 de M e todos os estados atingidos a partir de q0 pelo símbolo vazio (^).
A função de transição δ' é tal que δ'([q1q2...qn],x)=[r1r2...rm] se, e somente se,
δ({q1q2...qn},x)={r1r2...rm} ([MEN1998]), ou seja, aplicando um símbolo x a um conjunto
de estados de M através função de transição δ, o conjunto resultante de estados atingidos será
o mesmo que o conjunto de estados atingidos quando da aplicação de um símbolo x sobre o
mesmo conjunto de estados de M' através da função de transição δ'. Com base nesta definição,
pode-se construir um AFD M' equivalente utilizando-se uma Tabela de Transições, conforme
Figura 13.
dois símbolos, o número de sub-colunas da coluna das transições do AFD M' é igual a dois,
nomeadas pelos símbolos a e b, respectivamente.
Figura 13 - Tabela de transições sendo ∑={a,b}
Linhas
Ilustrativas
Para construir esta Tabela de Transições do AFD M', aplicam-se os seguintes passos:
a) cada estado do AFD M' é representado por um conjunto S de estados do AFND M;
b) o primeiro estado, na coluna de estados, do AFD M', na Tabela de Transições,
representa o conjunto S formado pelo estado inicial q0 do AFND M e todos os
estados atingidos a partir do estado inicial q0 com o símbolo vazio (^), no AFND
M. Este conjunto S representa o estado inicial do AFD M';
c) para cada conjunto S representando um estado de AFD M', computar as transições
de AFND M a partir dos estados presentes no conjunto S com cada símbolo do
alfabeto, formando um conjunto de estados de chegada para cada um dos símbolos.
Estes conjuntos ficam, respectivamente, armazenados em cada uma das sub-
colunas da Tabela de Transições. A ordem dos estados de chegada no conjunto não
representa um novo estado do AFD M', ou seja, {q0q1}={q1q0};
d) se o conjunto formado no passo c não constar na coluna de estados do AFD M',
acrescenta-se uma nova linha na Tabela de Transições com este conjunto.
Novamente, a ordem dos estados no conjunto não representa um novo estado do
AFD M';
e) aplicar os passos c e d até que todos os estados do AFD M' tenham sido
processados.
Figura 14 - AFND
q0 q1 q2 fq0f
a a a
b
Fonte: [MEN1998]
Figura 15 - Tabela de transições
A.F.D. M'
a b
Da tabela de transições da Figura 15 obtêm-se o AFD M'. da Figura 16. Cada um dos
conjuntos que representam os estados do AFD M' foi nomeado com números de 1 a 4 para
simplificar a interpretação.
Figura 16 - AFD M'
b a
1 2 3 f40
a a a
b
b
b
Fonte: [MEN1998]
O conjunto de estados finais F' do AFD M' é formado pelo conjunto dos estados de Q'
que contém o estado final do AFND M. Desta forma, o conjunto de estados finais do AFD M'
é igual a {4}, pois somente o conjunto que representa o estado 4 do AFD M' tem no seu
conjunto de estados o estado final do AFND M. No AFND M o estado qf representa o estado
23
final e somente o conjunto {q0q1q2qf}, na Tabela de Transições da Figura 15, apresenta este
estado.
Seja M=(∑, Q, δ, q0, F) um AFε a partir do qual será construído um AFND M'=(∑, Q,
δ', q0, F') equivalente (M≡M'). Seja δ'=Fε(δ''(δ'''(q,^),x)) a função de transição onde q
representa um estado de Q do AFε e x um símbolo do ∑. Aplica-se, para cada estado do AFε a
função de transição δ''' com símbolo vazio (^). Obtêm-se para cada estado, um conjunto de
estados atingidos pela transição com o símbolo vazio (^). Submetendo cada estado, dos
conjuntos gerados anteriormente, aos símbolos do alfabeto ∑, através da função de transição
δ'', obtêm-se os conjuntos por símbolo para cada estado do AFε M. Todos os conjuntos,
submetidos à função Fε(q), resultarão em novos conjuntos para cada estado do AFε e que
representam as transições do AFND M'. A função Fε(q) denota o conjunto de todos os estados
atingidos, inclusive q, a partir do estado q, com uma transição com o símbolo vazio (^). Se o
estado atingido apresentar outras transições com o símbolo vazio (^) para outros estados,
todos estes farão parte do conjunto, sendo que este ciclo terminará somente quando não
houver mais transições com o símbolo vazio (^) a serem processadas. O conjunto de estados
Q do AFND M' é igual ao conjunto de estados Q do AFε M. O estado inicial q0 do AFND M'
é igual ao estado inicial q0 do AFε M. O conjunto de estados finais F' do AFND M' é obtido
aplicando-se a função Fε(q) sobre os estados do AFε M. O estado que apresentar, no conjunto
gerado pela função Fε(q), um ou mais estados finais do AFε M fará parte do conjunto de
estados finais F' do AFND M'.
Figura 17 - AFεε M
0 1 2
q0 q1 q22
^ ^
Fonte: [HOP1979]
0 1 2
1 1
q0 q1 q22
0 2
0
1
2
Fonte: [HOP1979]
δ'''(q,^)
q0 q1 q2
∑= δ''(δ'''(q,^),x)
Fε(δ''(δ'''(q,^),x))
q1 ∅ {q1,q2} {q2}
q2 ∅ ∅ {q2}
26
Fonte: [AHO1995]
Tanto o algoritmo descrito por [HOP1979] como aquele proposto por [SIL2000]
apresentam uma definição em LLC no seu contexto de transformação de uma expressão
regular em AFD. Isto será abordado no capítulo que trata destes algoritmos.
2.5.1 COMPILADORES
De uma forma simples, um compilador é um programa que lê um programa escrito
numa linguagem, dita linguagem-fonte, e o traduz num programa equivalente numa outra
linguagem, dita linguagem-alvo. A linguagem alvo é aquela entendida pelo computador.
Desta forma a função do compilador é servir de tradutor entre o homem e a máquina. No
processo de tradução o compilador realiza a análise léxica, sintática e semântica do programa-
fonte informando ao usuário (homem) se o programa está correto (sem erros léxicos,
sintáticos e os semânticos detectáveis) ou não para a linguagem utilizada ([AHO1995]). A
Figura 24 mostra as fases de um compilador.
Figura 24 - Fases de um compilador
Análise Léxica
Análise Sintática
Análise Semântica
Gerador de Código
Intermediário
Otimizador
Código de Máquina
Fonte: [AHO1995]
3 TEOREMA DE KLEENE
Este capítulo apresenta o Teorema de Kleene e a sua relação com a transformação de
expressões regulares em autômatos finitos determinísticos.
O Teorema de Kleene define que qualquer conjunto reconhecido por uma máquina de
estados finitos é regular, e qualquer conjunto regular pode ser reconhecido por uma máquina
de estados finitos ([GER1995]).
Segundo [DAV1994], uma linguagem é regular se e somente se esta pode ser obtida a
partir de linguagens regulares por aplicação de um número finito de vezes das operações de
união, concatenação e fechamento. Ainda, para todo conjunto regular sobre um alfabeto existe
uma expressão regular sobre o mesmo alfabeto. A linguagem L é regular se e somente se esta
é gerada por uma expressão regular.
saindo do mesmo nó). Considera também, um autômato finito, como sendo um grafo
determinístico, onde de cada nó pode sair no máximo n arestas, onde n indica o número de
símbolos do alfabeto. Ainda, cada aresta que sai deve apresentar um elemento distinto do
alfabeto.
R
q0 q
2f
32
AB A B
i j i k j
Figura 27 - União
A|B i j
i j
Figura 28 - Fechamento
A
A* i ^ k ^ j
i j
q0
(ab)*(ba)*(aa|bb)* qf
q0
(ab)* (ba)* (aa|bb)* qf
QUEBRA DA CONCATENAÇÃO
ab ab aa
^ ^ ^ ^ ^ ^
|bb
q0 qf
QUEBRA DO FECHAMENTO
ab ab aa
q0
^ ^ ^ ^ ^ ^ qf
bb
QUEBRA DA UNIÃO
a
a
q0
^ ^ ^ ^ ^ ^ qf
b
a
a
b
b
b
QUEBRA DA CONCATENAÇÃO
34
a
1
^ 3
^ 7
b
b
2 4 6
Mj
ESTADOS DO AUTÔMATO FINITO Mi
a b
Q6 {5} {7} {}
Q7 {6} {} {7}
35
Figura 33 - AFD
a a
a
Q1 b Q2 Q4 Q6
a
b
b
b
Q3 a Q5 Q7
b
a
AFND
AFD AFε
ER
Fonte: [HOP1979]
37
q1 M1 f1
^ ^
q0 f0
^ ^
q2 M2 f2
UNIÃO
q1 M1 f1 q2 M2 f2
^
CONCATENAÇÃO
q0 q1 M1 f1 f0
^ ^
FECHAMENTO
Fonte: [HOP1979]
38
qf00 q0 f0f
q q0 fq0f
a
r=^ r=∅ r=a
Fonte: [HOP1979]
A Figura 40 mostra a aplicação das regras da Figura 35, dada a expressão regular
(ER) a*(aa|bb) sobre ∑={a,b}. O processo para determinar o AFε desta expressão regular
consiste na divisão da expressão à nível de símbolo do alfabeto da esquerda para a direita, ou
seja, primeiramente realiza-se a construção da sub-expressão a*. Em seguida realiza-se a
construção da sub-expressão aa|bb. A sub-expressão aa|bb necessita de mais divisões pois
ainda existem sub-operações entre os seus símbolos. Divide-se a sub-expressão aa|bb em aa e
bb. Tanto a expressão aa como a expressão bb são novamente divididas. Nas Figuras 38 e 39
é demonstrada a construção, por partes, do AFε da ER a*(aa|bb). É importante observar que,
de uma certa forma, as propriedades da Figura 35 dependem das propriedades com zero
operadores mostradas na Figura 36, pois o processo para construção do AFε consiste na
divisão da expressão ao nível de símbolos do alfabeto. Então, por exemplo, para a expressão
aa constrói-se primeiramente um grafo com base nas propriedades da Figura 36, conforme
mostra a Figura 37. Após isto, aplica-se a propriedade de concatenação da Figura 35 sobre a
expressão aa.
39
expressão aa
a a
1 2 3 4
a a
1 2 3 4
a ^ a
CONCATENAÇÃO
3 1 2 4
^ a ^
FECHAMENTO
40
a*(aa|bb) a*(aa|bb)
a a b b
5 6 7 8 9 10 11 12
a a b b
5 6 7 8 9 10 11 12
a ^ a b ^ b
CONCATENAÇÃO CONCATENAÇÃO
a*(aa|bb)
aa|bb
5 6 7 8
^ a ^ a ^
13 14
^ ^
9 10 11 12
b ^ b
UNIÃO
5 6 7 8
^ a ^ a ^
^
3 1 2 4 13 14
^ a ^ ^
^ ^
^ 9 10 11 12
b ^ b
41
b
b
a
a a
a b
a
a
b a
a b
a a b
a
b b
a a a b
a a a b
1 2 3 4 5 6 7 8 9 10 11 12 13 14
a a a b
a a
b b
a
a
a b
a
a
a a a
Mj
ESTADOS DO AUTÔMATO
Mi
FINITO
a b
3 {10,11} {} {12,14}(5)
5 (Final) {12,14} {} {}
b b
b
1 2 3 4 5
a b
a a
EXPRESSÃO
EXPRESSÃO TABELA DO GRAFO DE TABELA DE
REGULAR AFD
PÓS-FIXADA DESMONTE TRANSIÇÕES TRANSIÇÕES
IN-FIXADA
R → SR RR;
RR → '+' SR | '.' SR | ^;
SR → T SR1;
SR1 → '+' T SR1 | ^; {EMITIR '+'}
T → F T1;
T1 → '.' F T1 | ^; {EMITIR '.'}
F → '(' R ')' F1 | #S F1; {EMITIR Símbolo S}
F1 → '*' F1 | ^; {EMITIR '*'}
1 | 6
1 | 6
1 ^ 6
1 & 6
1 & 6
1 c 2
2 * 6
2 ^ 3
3 ^ 6
3 a 3
1 a 4
4 * 6
4 ^ 5
5 ^ 6
5 | 5
5 b 5
5 c 5
1 ^ 6
1 c 2
2 ^ 3
3 ^ 6
3 a 3
1 a 4
4 ^ 5
5 ^ 6
5 b 5
5 c 5
^
1 6
a
^
c
2 3
^
^
a
4 5
^
c
mesmos passos que são utilizados no algoritmo descrito por [MAN1974], apresentados na
Figura 32. Desta forma, para o GT da Figura 52, obtém-se uma tabela de transições da
Figura 53. Na Figura 53 os símbolos "-" e "+" na coluna Estados do AFD indicam,
respectivamente, o estado inicial e os estados finais.
Figura 53 - Tabela de transições obtida a partir do GT da Figura 52
Estados do AFD Mi Mj
a b c
A partir da tabela de transições obtém-se o AFD. Para o exemplo da Figura 53, o AFD
resultante é mostrado na Figura 54.
Figura 54 - AFD resultante da Figura 53
1 f20 b f40
a
c c
c
3
a
f50
a
Observando o AFD da Figura 54 é possível notar que existem 5 estados e que todos
estes fazem parte do conjunto de estados finais do AFD. Analisando o código da Figura 55
pode-se observar que existe a declaração de 8 labels (linha 120 do código), sendo que 5 destes
são para os estados que compõe o AFD (que, de fato, apresenta 5 estados); 1 para inicializar a
variável I (linha 160 do código) utilizada como índice para a cadeia que será submetida ao
AFD e 2 labels para atribuir verdadeiro ou falso a função, de acordo com a aceitação ou não
da cadeia pelo AFD.
Considerando como exemplo uma cadeia abc que será aplicada a função ValidaER, os
labels que serão "visitados" são, respectivamente, S0, S1, S2, S4 e S6. Quando a função inicia,
o contador I (que é responsável por indicar a posição do símbolo lido na cadeia que está sendo
avaliada) é inicializado com zero (linha 160). Logo após inicia o "caminhamento" pelo AFD,
onde, de fato, a função começa pelo estado inicial do AFD no label S1 (linha 170). O primeiro
símbolo é lido. Se nenhum símbolo fosse lido (cadeia que o usuário passa a função tem
comprimento zero), então a execução da função seria desviada (goto) para o label S6, o que
significaria aceitação, pois o AFD em questão permite cadeias vazias (o estado inicial é
também estado final). De outra forma, considerando que a cadeia é abc, então o primeiro
símbolo lido é o a. Assim na execução da função o comprimento de cadeia é maior que zero e
53
o comando if da linha 180 não é verdadeiro. A execução continua na linha 200 onde se
verifica pelo comando que o símbolo lido é o a. Desta maneira a linha 210 (goto S2) é
executada e a função continua a execução no label S2 (linha 250). No label S2 um novo
símbolo é lido (b) pelo incremento do índice I. A cadeia não terminou (linha 260) e o
processamento continua na linha 280. É verificado (comando if) que o símbolo lido é o b. Na
linha 290 a execução é desviada (goto S4) para o label S4 (linha 390). O índice I da cadeia
abc é novamente incrementado (linha 390) e o símbolo c da cadeia agora será processado. Na
linha 400 o comando if não é validado e a execução continua na linha 420. O comando if da
linha 440 é validado pois o símbolo lido é o b e então a linha 450 é executada. A execução é
novamente desviada (goto S4) para o label S4. O índice I da cadeia é incrementado (linha
390). A linha 400 é executada e o comando if validado pois a cadeia terminou. A execução é
desviada (goto S6) para o label S6. No label S6 a função ValidaER recebe a atribuição true
pois a cadeia foi aceita.
Com base nestas etapas, serão apresentados projetos distintos dos protótipos que
implementam os algoritmos descritos por [HOP1979] e [SIL2000].
Um problema poderá ser extenso e complexo. Desta forma, este problema poderá ir
além da capacidade de compreensão de uma pessoa mesmo que o problema venha a ser
55
decomposto em partes. De acordo com estudos científicos, a mente humana é capaz de lidar
com apenas sete entidades de uma só vez e o limite seria, no máximo, nove. Assim qualquer
problema não deverá ser decomposto em mais de nove partes. Cada uma das partes não
poderá ser subdividida em mais de nove partes e assim por diante. A divisão do problema em
partes deverá ser procedida até que se atinja um estágio em que cada parte se torne pequena o
suficiente para que seja entendida, manipulada e testada. A divisão poderá ser aleatória,
arbitrária (baseada numa qualidade da parte a ser decomposta, baseada na experiência mas
sem princípios formais ou fundamentos) e formal (baseada em alguma característica do
problema a ser resolvido) ([LON1985]).
Assim, para cada problema, haverá um conjunto de elementos que deverão ser
avaliados e decompostos em partes suficientemente pequenas para o entendimento claro e
objetivo, refletindo, desta forma, numa solução correta para o problema em questão.
ENTRADA DE DADOS
A fase 4 apresenta a estrutura de dados mais complexa do protótipo. Serão duas tabelas
alocadas dinamicamente em memória que se comunicam entre si. Para entender esta estrutura
de dados torna-se necessário um entendimento da tabela de transições que irá compor o AFD.
A tabela de transições é composta pela coluna Mi e pela coluna Mj. A coluna Mi apresenta os
estados do AFD. Já a coluna Mj apresenta as transições por símbolo do alfabeto. Isto significa
dizer que, para cada linha da coluna Mi, existe uma linha na coluna Mj. A Figura 60
apresenta esta estrutura de dados. A Figura 61 mostra um esquema para um melhor
entendimento desta estrutura de dados. Esta estrutura de dados é idêntica para o protótipo que
implementa o algoritmo descrito por [SIL2000].
Figura 60 - Estrutura de dados da tabela de transições do AFD
Registro_Mj = ^Reg_Mj;
Reg_Mj = record
Simbolo : char;
ProximoEstado : integer;
Prox_Mj : Registro_Mj;
Mi : Cjto;
end;
Registro_Mi = ^Reg_Mi;
Reg_Mi = record
Estado : integer;
EstadoMi : Cjto;
EstadoFinal : boolean;
Prox_Mi : Registro_Mi;
Mj : Registro_Mj; {coluna Mj}
end;
58
APONTADOR
COLUNA Mi
APONTADOR
APONTADOR
COLUNA Mj
símbolos "&", "*" e "|". Estes símbolos permitem uma maior flexibilidade para a
entrada de expressões regulares. A concatenação usa normalmente o símbolo "." e
a união o símbolo "+". Certas expressões regulares apresentam o símbolo "." e o
símbolo "+" em seu alfabeto, impedindo a sua utilização como operador. O
símbolo "ε", que normalmente é utilizado para representar o símbolo vazio, foi
substituído pelo símbolo "^" para permitir uma maior simplicidade ao protótipo e
praticidade do protótipo. O símbolo "ε" não é padrão de teclado o que impede
(dificulta) sua utilização nos protótipos;
b) geração do AFεε: a fase seguinte do protótipo é a geração do AFε com base nos
dados fornecidos na etapa anterior. Esta fase implementa uma especificação em
LLC (Figura 63) apresentada por [HOP1979] e que é responsável pela verificação
sintática da expressão regular. [HOP1979] também apresenta um trecho de
programa que implementa esta LLC, conforme mostra a Figura 62. A LLC da
Figura 63 é baseada nas regras apresentadas no capítulo 3 que tratam da
construção de um AFε a partir da expressão regular. A execução do trecho de
programa apresentado na Figura 62 consiste na chamada do procedimento
ACHA_EXPRESSÃO após a entrada do alfabeto e da expressão regular. Cada
símbolo da expressão regular será processado individualmente, da esquerda para a
direta. O procedimento para leitura individual dos símbolos está descrito no item
4.4. Após o processamento de cada símbolo e operador haverá a geração de uma
transição na tabela que armazena o AFε. Isto é realizado através de um
procedimento ALOCA_TRANSIÇÃO. Este procedimento deverá ser capaz de reunir
os estados de saída, de chegada e o símbolo que compõe a transição. Além disto,
este procedimento deverá ser construído de tal forma a atender as regras
apresentadas no capitulo 3. O AFε resultante desta fase estará armazenado numa
tabela, onde cada registro da tabela será da forma como apresentado na Figura 59;
Figura 62 - Implementação da LLC da Figura 63
procedimento ACHA_EXPRESSAO;
inicio
ACHA_PRODUTO;
enquanto primeiro simbolo da expressao for o operador "|" faça
inicio
apague o primeiro simbolo da expressao;
ACHA_PRODUTO;
ALOCA_TRANSIÇÃO;
fim;
fim;
60
procedimento ACHA_PRODUTO;
inicio
ACHA_TERMO;
enquanto primeiro simbolo da expressao for o operador "&" faça
inicio
apague o primeiro simbolo da expressao;
ACHA_TERMO;
ALOCA_TRANSIÇÃO;
fim;
fim;
procedimento ACHA_TERMO;
inicio
se primeiro simbolo da expressao for palavra vazia (^) ou um
simbolo do alfabeto ou expressao vazia então
apague o primeiro simbolo da expressão
ALOCA_TRANSIÇÃO;
caso contrário
se primeiro simbolo da expressao for "(" então
inicio
apague o primeiro simbolo da expressao
ACHA_EXPRESSAO;
se primeiro simbolo da expressao for ")" então
apague o primeiro simbolo da expressao
caso contrário
retorne erro
fim
enquanto primeiro simbolo da expressao for o operador "*" faça
apague o primeiro simbolo da expressao;
ALOCA_TRANSIÇÃO;
fim
Fonte: [HOP1979]
Figura 63 - LLC
E = P + E|P
P = T . P|T
T = 0|1|^|∅|T*|(E)
Fonte: [HOP1979]
c) geração do AFND: nesta fase é gerado um AFND com base no AFε gerado na
etapa anterior. Este procedimento será realizado por equivalência de autômatos
finitos, apresentado no capítulo 2. Consiste em obter o conjunto de estados finais
do AFND e o AFND propriamente dito. A Figura 64 apresenta um fluxograma
que define o procedimento para obter o conjunto de estados finais do AFND. A
Figura 65 apresenta o procedimento para gerar o AFND. O AFND resultante desta
fase estará armazenado numa tabela, onde cada registro será da forma como
apresentado na Figura 59 (idêntico ao do AFε);
61
AFε da Etapa
Anterior
sim
ARMAZENAR ESTADO
LIDO NO CONJUNTO DE
ESTADOS FINAIS DO
AFND
62
INICIO AFND
FIM AFND
sim
ARMAZENAR ESTADO
ATINGIDO AO CONJUNTO DE
ESTADOS ATINGIDOS COM ^
ESTADO ATINGINDO
ATINGIU OUTRO COM ^
não
sim
63
d) geração do AFD: o AFD é gerado com base no AFND da fase anterior. A geração
do AFD é com base nos passos apresentados na Figura 32. O AFD estará na forma
de uma tabela de transições conforme apresenta a Figura 61. Cada registro da
tabela será da forma como apresentado na Figura 60;
e) geração da função equivalente na linguagem de programação Pascal: esta
geração é realizada com base na tabela de transições do AFD. A função gerada será
da forma como apresentado no capítulo 3, item 3.1.4.1. O procedimento consiste
em processar cada uma das linhas da tabela de transições (Figura 61) e gerar as
linhas de código de acordo com a tabela de transições. As linhas de código serão
gravadas em arquivo texto com extensão .pas da linguagem de programação
Pascal.
ENTRADA DE DADOS
Entrada do Alfabeto e da ER
FASE 1
Geração do GT
FASE 4
Geração do AFD
FASE 5
Cjto = object
Prim,
Ult,
Valor : Elementos;
Tam : integer;
procedure Inicia;
procedure Limpa;
procedure Adiciona(D : integer);
function Verifica(D : integer) : boolean;
procedure Lista;
end;
cjtoEstados : Cjto;
A utilização dos métodos do objeto será realizada conforme apresenta a Figura 72.
Figura 72 – Descrição dos métodos do object Cjto
MÉTODO: DESCRIÇÃO:
cjtoEstados.Inicia Inicia o conjunto cjtoEstados
cjtoEstados.Adiciona(20) Adiciona um estado 20 ao conjunto
cjtoEstados
cjtoEstados.Adiciona(21) Adiciona um estado 21 ao conjunto
cjtoEstados
cjtoEstados.Lista Lista o conjunto cjtoEstados. Serão
visualizados os estados 20 e 21.
Apenas utilizada para testes nos
protótipos.
cjtoEstados.Verifica(20) Retorna true pois o estado 20 existe
cjtoEstados.Verifica(22) Retorna false pois o estado 22 não
existe.
cjtoEstados.Limpa Limpa o conjunto. Todos os estados
adicionados são eliminados.
69
O protótipo que implementa o algoritmo descrito por [SIL2000] é bastante similar nos
resultados apresentados ao usuário. Mudam os itens c e d, onde no item o usuário terá acesso
a Tabela do Desmonte, ao GT e ao AFD e, no item d, a detalhes de processamento do AFD
(tabela de transições).
73
As fases de processamento do AFND vistas na Figura 82 são duas. Estas fases seguem
a descrição do algoritmo de [HOP1979]. Conforme este algoritmo, o número de estados do
conjunto de estados do AFND será o mesmo que o número de estados do conjunto de estados
do AFε. Na primeira fase o algoritmo processa cada estado do conjunto de estados do AFND
com o símbolo vazio (^). Este processo gera novos conjuntos de estados para cada estado do
AFND. Cada conjunto gerado na primeira fase é submetido, estado a estado, ao alfabeto
resultando na segunda fase do algoritmo. A terceira fase é final e que irá resultar no AFND.
Nesta fase todos os estados do conjunto de estados do AFND são novamente processados com
o símbolo vazio (^).
Mesmo assim, todas estas questões podem não ser suficientes para estimar com
precisão a eficiência de um programa. Diferentes arquiteturas de hardware e software podem
trazer resultados completamente diferentes. Portanto esta questão deverá ser avaliada com o
cuidado necessário. O contexto de estudo da complexidade de um algoritmo deverá ser muito
claro e objetivo.
Assim, apesar de todos estes pontos, são raros os casos de um algoritmo que apresenta
uma situação ideal, ou seja, este algoritmo é ótimo para a tarefa que irá realizar e qualquer
outro necessitará de mais recursos, ou a mesma quantidade, para executar a mesma tarefa
([LUC1979]).
Função Pascal
Numa primeira análise da Figura 88 poder-se-ia concluir que o algoritmo descrito por
[HOP1979] é o mais eficiente, pois apresenta menos etapas que o algoritmo descrito por
[SIL2000]. Mas esta afirmação seria totalmente incorreta e leviana, pois não se considerou a
possibilidade que, internamente, o algoritmo de [SIL2000] possa apresentar funções que lhe
permitam um desempenho tão eficiente ou mais que o algoritmo descrito por [HOP1979]. Isto
mostra que é necessário delimitar precisamente os pontos que serão considerados numa
avaliação de complexidade para se poder determinar qual algoritmo será mais eficiente para
um determinado problema.
As fases seguintes de cada protótipo, até a fase de geração do AFD, fazem parte do
escopo de processamento de cada um dos algoritmos. A partir daqui será necessária uma
avaliação, por partes, de cada protótipo. Para cada etapa serão levantados os pontos
importantes de cada algoritmo. Com base neste levantamento será apresentada uma conclusão
sobre a eficiência dos algoritmos implementados.
Tanto o algoritmo descrito por [HOP1979] como aquele descrito por [SIL2000]
utilizam, basicamente, o mesmo conjunto de estruturas de dados para armazenar informações
geradas durante o processamento. Como exemplo disto cita-se a estrutura utilizada para
armazenar a “Tabela do Desmonte" no algoritmo descrito por [SIL2000] e a tabela utilizada
para armazenar o AFε gerado no algoritmo descrito por [HOP1979]. Ambas são da forma
conforme apresenta a Figura 89. Esta mesma estrutura de dados também é utilizada para
armazenar o AFND do algoritmo descrito por [HOP1979].
87
Linhas
ilustrativas
A Figura 91 apresenta os valores para cada uma das expressões regulares, de acordo
com as três estruturas de dados vistas na Figura 90. Foram realizados testes com diferentes
tipos de expressões agrupadas de acordo com a quantidade de símbolos, operadores e níveis
de parênteses. Três grupos são vistos na Figura 91. Ao final de cada grupo foi realizada uma
comparação percentual da utilização de memória de um algoritmo em relação ao outro. Para
os valores do algoritmo descrito por [HOP1979] foi considerado 100%. Desta forma, para o
algoritmo descrito por [SIL2000] for considerada a sua proporcionalidade em relação ao
algoritmo descrito por [HOP1979].
90
EXPRESSÃO REGULAR A B C A B C
1 a(b|c)*|ca*|^ 17 10 5 28 174 6
2 A(aa|bb)* 12 7 5 16 58 6
3 a(bc|cb*)*acb* 24 14 9 30 217 10
4 ((abb)*(ba)*(b|aa)) 21 12 8 26 88 8
5 bca*(c|aa|bb)*c|a*(ab|ba**)** 48 28 13 67 830 14
[SIL2000] [HOP1979]
EXPRESSÃO REGULAR A B C A B C
6 a(b|c)*|ca*|^ 17 10 5 28 174 6
7 A(aa|bb)* 12 7 5 16 58 6
8 a(bc|cb*)*acb* 24 14 9 30 217 10
9 ((abb)*(ba)*(b|aa)) 21 12 8 26 88 8
[SIL2000] [HOP1979]
EXPRESSÃO REGULAR A B C A B C
10 ab* 6 4 3 7 16 3
11 A(aa|bb)* 12 7 5 16 58 6
12 a(bc|cb*)*acb* 24 14 9 30 217 10
13 ((abb)*(ba)*(b|aa)) 21 12 8 26 88 8
TOTAIS: 63 37 25 79 379 27
600,00%
500,00%
400,00%
SIL2000
%
300,00%
HOP1979
200,00%
100,00%
0,00%
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Expressões Regulares
através dos protótipos, é possível determinar qual dos protótipos é o mais eficiente neste
tópico. Observado a estrutura de dados do AFD e o resultado mostrado na Figura 91 que
apresenta o número de estados dos AFD’s (coluna C de [HOP1979] e [SIL2000]) é possível
identificar que todas as expressões regulares submetidas aos protótipos tiveram um resultado,
em alguns casos, igual para os dois protótipos, e na maioria delas, um resultado melhor para o
algoritmo descrito por [SIL2000]. Ou seja, o resultado produzido pelo algoritmo descrito por
[SIL2000] é mais otimizado que o resultado produzido pelo algoritmo descrito por
[HOP1979]. A Figura 95 apresenta uma comparação dos resultados gerados por cada um dos
algoritmos. No gráfico da Figura 95 é possível perceber que o número de transições geradas,
para cada AFD, pelo algoritmo de [HOP1979], é maior que o número gerado pelo algoritmo
de [SIL2000]. Isto prova que este último, na maioria dos casos, gera resultados tão ou mais
otimizados que o primeiro.
Figura 95 – Otimização do AFD resultante
Otimização do AFD resultante
16
Número de Transições
14
12
10
8
6
4
2
0
1 2 3 4 5 6 7 8 9 10 11 12 13
SIL2000
Expressões Regulares
HOP1979
exigem formas especiais de armazenamento. Isto pode ser exemplificado através da estrutura
tipo object implementada para armazenagem dos conjuntos de estados. As estruturas
intermediárias geradas no algoritmo descrito por [SIL2000] apresentam-se menos complexas.
A estrutura tipo object foi inicialmente implementada no algoritmo descrito por [HOP1979],
pois a quantidade de estados gerados para os conjuntos de estados mostrou-se sempre superior
aos conjuntos gerados pelo algoritmo descrito por [SIL2000]. A estrutura tipo object para os
conjuntos de estados acabou também sendo adotada para o protótipo que implementa o
algoritmo descrito por [SIL2000] por uma questão de padronização dos protótipos. Nos testes
realizados neste trabalho poder-se-ia optar por pelo tipo set da linguagem de programação
Pascal para declarar os conjuntos de estados do algoritmo descrito por [SIL2000], pois este
suportaria perfeitamente a quantidade de estados das várias expressões vistas neste trabalho,
algo que não foi possível com o algoritmo descrito por [HOP1979] (o número de estados
excedeu a capacidade de armazenamento do tipo set).
Um outro importante tópico que foi avaliado é o grau de otimização dos resultados dos
algoritmos. Cada algoritmo gera como resultado um AFD. Comparando os autômatos
gerados, observou-se que o algoritmo descrito por [SIL2000] apresentou-se tão ou mais
eficiente do que o algoritmo descrito por [HOP1979]. Os autômatos gerados foram
comparados através do mecanismo de equivalência de Moore (Anexo 1) para identificar se,
de fato, estes representam o mesmo resultado prático. Isto se torno necessário para provar que
um algoritmo gera a mesma saída que o outro e que o resultado de um poderá ou não ser
melhor do que do outro.
Desta forma, baseado nas avaliações e nos resultados obtidos pode-se concluir o
algoritmo descrito por [SIL2000] é mais eficiente do que aquele descrito por [HOP1979].
96
7 CONCLUSÃO
Ao longo deste trabalho foram vistos conceitos sobre alfabeto, linguagens, expressões
regulares e autômatos finitos. Todos estes conceitos são de fundamental importância para o
estudo e o desenvolvimento de algoritmos que permitam a transformação de expressões
regulares em autômatos finitos.
Vários foram os algoritmos desenvolvidos ao longo dos anos, cada qual com suas
características e particularidades. Apesar disto todos estes sempre seguem pelos mesmos
caminhos para realizar a transformação e estes caminhos são muitas vezes contestados devido
a uma série de problemas como, por exemplo, a falta de desempenho dos algoritmos e a
grande quantidade de recursos computacionais exigidos por estes.
Talvez mais adiante o algoritmo descrito por [SIL2000] venha a ser uma importante
tecnologia a ser utilizada em compiladores, ferramentas de desenvolvimento e linguagens de
programação.
97
Além disto poderá ser elaborado um estudo para viabilização prática de aplicação do
algoritmo de [SIL2000] no contexto de compiladores e linguagens de programação.
98
ABRINDO OS AUTÔMATOS
O primeiro passo para verificar a equivalência entre dos autômatos pelo programa
moore.exe é abrir cada autômato. O programa moore.exe é baseado nos autômatos que podem
ser salvos nos protótipos que implementam os algoritmos descritos por [HOP1979] e
[SIL2000]. O capítulo 5, na seção 5.2.1.3.2 apresenta maiores informações sobre o
salvamento de um AFD pelos protótipos. O usuário deverá clicar sobre o botão Abrir AFD e
então escolher o arquivo que contenha o AFD. As Figuras 99 e 100 apresentam este
procedimento.
100
PROCESSANDO A EQUIVALÊNCIA
Após a abertura dos autômatos bastará ao usuário clicar sobre o botão "Processar". A
tabela de comparação será montada e o usuário informado da equivalência ou não-
equivalência (Figura 101).
101
REFERENCIAS BIBLIOGRÁFICAS
[AHO1995] AHO, Alfred V.; SETHI, Ravi; ULLMANN, Jeffrey D.; Compiladores:
princípios, técnicas e ferramentas. Rio de Janeiro : Guanabara Koogan,
1995.
[NUN1998] NUNES, Maria das Graças Volpe. Página pessoal. Endereço Eletrônico:
http://www.icmc.sc.usp.br/~mdgvnune/. Data da consulta: 10/09/2000.
[SIL2000] SILVA, José Roque Voltolini da. Proposta de um novo algoritmo para
transformação de uma expressão regular em um autômato finito
determinístico. Artigo não publicado. Blumenau : Universidade Regional
de Blumenau, 2000.