Você está na página 1de 120

UNIVERSIDADE REGIONAL DE BLUMENAU

CENTRO DE CIÊNCIAS EXATAS E NATURAIS

CURSO DE CIÊNCIAS DA COMPUTAÇÃO

(Bacharelado)

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

TRABALHO DE CONCLUSÃO DE CURSO SUBMETIDO À UNIVERSIDADE


REGIONAL DE BLUMENAU PARA A OBTENÇÃO DOS CRÉDITOS NA
DISCIPLINA COM NOME EQUIVALENTE NO CURSO DE CIÊNCIAS DA
COMPUTAÇÃO — 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

ESTE TRABALHO DE CONCLUSÃO DE CURSO, FOI JULGADO ADEQUADO


PARA OBTENÇÃO DOS CRÉDITOS NA DISCIPLINA DE TRABALHO DE
CONCLUSÃO DE CURSO OBRIGATÓRIA PARA OBTENÇÃO DO TÍTULO DE:

BACHAREL EM CIÊNCIAS DA COMPUTAÇÃO

Prof. José Roque Voltolini da Silva — Orientador na FURB

Prof. José Roque Voltolini da Silva — Coordenador do TCC

BANCA EXAMINADORA

Prof. José Roque Voltolini da Silva

Prof. Dalton Solano dos Reis

Prof. Roberto Heinzle

ii
DEDICATÓRIA

Dedico, na forma de agradecimento, a Deus pela força


e determinação dados, não somente durante este trabalho,
mas por toda vida, para superar desafios e obstáculos.
Sem esta força jamais teria chegado aqui.

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.

Aos familiares que sempre estiveram presentes, em todos os momentos.

iv
SUMÁRIO
DEDICATÓRIA ...................................................................................................................iii

AGRADECIMENTOS.......................................................................................................... iv

SUMÁRIO............................................................................................................................. v

LISTA DE FIGURAS........................................................................................................... ix

LISTA DE ABREVIATURAS E SIGLAS.......................................................................... xiv

RESUMO ............................................................................................................................ xv

ABSTRACT ....................................................................................................................... xvi

1 INTRODUÇÃO ................................................................................................................ 1

1.1 Objetivos ........................................................................................................................ 2

1.2 Organização do texto....................................................................................................... 2

2 REVISÃO BIBLIOGRÁFICA .......................................................................................... 4

2.1 Unidades computacionais................................................................................................ 4

2.2 Sistemas de estados finitos .............................................................................................. 4

2.3 Alfabeto.......................................................................................................................... 6

2.4 Linguagens ..................................................................................................................... 6

2.4.1 Gramáticas.................................................................................................................... 7

2.4.1.1 Reconhecedores.......................................................................................................... 9

2.4.1.2 Gramáticas regulares ................................................................................................ 10

2.4.1.2.1 Expressões regulares ............................................................................................ 10

2.4.1.2.2 Conjuntos regulares ............................................................................................. 14

2.4.1.2.3 Grafos de transições (GT) .................................................................................... 14

2.4.1.2.4 Autômatos finitos................................................................................................. 15

2.4.1.2.5 Autômato finito determinístico (AFD) ................................................................. 17

v
2.4.1.2.6 Autômato finito não determinístico (AFND) ........................................................ 18

2.4.1.2.7 Autômato finito com movimentos vazios (AFε) ................................................... 18

2.4.1.2.8 Equivalência entre autômatos finitos .................................................................... 19

2.4.1.2.8.1 Equivalência entre AFD e AFND....................................................................... 20

2.4.1.2.8.2 Equivalência entre AFND e AFε........................................................................ 23

2.4.2 Gramáticas livres de contexto ..................................................................................... 26

2.5 Linguagens de programação.......................................................................................... 27

2.5.1 Compiladores.............................................................................................................. 28

3 TEOREMA DE KLEENE ............................................................................................... 30

3.1 Transformação de expressões regulares em autômatos finitos........................................ 31

3.1.1 Algoritmo descrito por [MAN1974]............................................................................ 31

3.1.2 Algoritmo descrito por [HOP1979] ............................................................................. 36

3.1.3 Algoritmo descrito por [SIL2000] ............................................................................... 43

3.1.4 Transformação de um AFD para um programa de computador.................................... 49

3.1.4.1 Implementação por código direto.............................................................................. 50

3.1.4.1.1 A relação entre o código do programa e o AFD.................................................... 51

3.1.4.2 Implementação por controle de tabela de transições.................................................. 53

4 ESPECIFICAÇÃO DOS PROTÓTIPOS ......................................................................... 54

4.1 Projeto de programa ...................................................................................................... 54

4.2 Protótipo do algoritmo descrito por [HOP1979] ............................................................ 55

4.2.1 Visão geral do protótipo.............................................................................................. 55

4.2.2 Estruturas de dados ..................................................................................................... 56

4.2.3 Procedimentos do protótipo ........................................................................................ 58

4.3 Protótipo do algoritmo descrito por [SIL2000] .............................................................. 63

4.3.1 Visão geral do protótipo.............................................................................................. 63

vi
4.3.2 Estruturas de dados ..................................................................................................... 64

4.3.3 Procedimentos do protótipo ........................................................................................ 65

4.4 Procedimento de leitura símbolo a símbolo ................................................................... 66

4.5 Particularidades no desenvolvimento dos protótipos...................................................... 67

5 APRESENTAÇÃO E UTILIZAÇÃO DOS PROTÓTIPOS ............................................. 69

5.1 Visão geral dos protótipos ............................................................................................. 69

5.2 Protótipo que implementa o algoritmo descrito por [HOP1979]..................................... 70

5.2.1 Tela principal do protótipo que implementa o algoritmo descrito por [HOP1979] ....... 70

5.2.1.1 Entrada do alfabeto e da expressão regular................................................................ 70

5.2.1.2 Processamento da expressão regular ......................................................................... 71

5.2.1.3 Resultados do processamento ................................................................................... 72

5.2.1.3.1 Visualizar a função pascal gerada......................................................................... 73

5.2.1.3.2 Salvar o AFD ....................................................................................................... 75

5.2.1.3.3 Detalhes do processamento .................................................................................. 75

5.2.1.3.3.1 Visualizando tabela de transições do AFD ......................................................... 77

5.3 Protótipo que implementa o algoritmo descrito por [SIL2000] ...................................... 78

5.3.1 Tela principal do protótipo que implementa o algoritmo descrito por [SIL2000] ......... 78

5.3.1.1 Entrada do alfabeto e da expressão regular................................................................ 79

5.3.1.2 Processamento da expressão regular ......................................................................... 79

5.3.1.3 Resultados do processamento ................................................................................... 79

5.3.1.3.1 Visualizar a função pascal gerada......................................................................... 80

5.3.1.3.2 Salvar AFD.......................................................................................................... 80

5.3.1.3.3 Expressão pós-fixada ........................................................................................... 80

5.3.1.3.4 Tabela de transições do AFD ............................................................................... 80

5.4 Informações gerais sobre os protótipos.......................................................................... 80

vii
6 COMPARAÇÃO DOS PROTÓTIPOS............................................................................ 82

6.1 Complexidade de algoritmos ......................................................................................... 82

6.2 Estudos dos algoritmos descritos de [HOP1979] e [SIL2000]........................................ 84

6.2.1 Contextualização dos algoritmos................................................................................. 84

6.2.2 Mecanismos de comparação........................................................................................ 87

6.2.3 Aplicação dos mecanismos ......................................................................................... 88

6.2.3.1 Utilização de memória.............................................................................................. 88

6.2.3.1.1 Avaliação final da utilização de memória ............................................................. 91

6.2.3.2 Tempo de processamento.......................................................................................... 91

6.2.3.2.1 Análise final do tempo de processamento............................................................. 93

6.2.4 Grau de otimização do resultado de cada algoritmo..................................................... 93

6.3 Resultado da comparação dos algoritmos ...................................................................... 94

7 CONCLUSÃO ................................................................................................................ 96

8 SUGESTÕES PARA TRABALHOS FUTUROS ............................................................ 97

ANEXO 1 - Equivalência de Moore..................................................................................... 98

ANEXO 2 - Interpretador de tabelas .................................................................................. 102

REFERENCIAS BIBLIOGRÁFICAS................................................................................ 103

viii
LISTA DE FIGURAS
Figura 1 - Gramáticas e Linguagens ....................................................................................... 9

Figura 2 - Hierarquia de Chomsky ......................................................................................... 9

Figura 3 - Propriedades algébricas das expressões regulares................................................. 12

Figura 4 - Expressões regulares e linguagens representadas.................................................. 13

Figura 5 - Expressões regulares e conjuntos regulares .......................................................... 14

Figura 6 - Grafo de Transições a partir de uma expressão regular......................................... 15

Figura 7 - Convenção para estado inicial e final .................................................................. 15

Figura 8 - Automato finito como máquina de controle finito ................................................ 16

Figura 9 - AFD ................................................................................................................... 17

Figura 10 - AFND................................................................................................................ 18

Figura 11 - AFε.................................................................................................................... 19

Figura 12 - Equivalência entre autômatos............................................................................. 19

Figura 13 - Tabela de transições sendo ∑={a,b}.................................................................. 21

Figura 14 - AFND............................................................................................................... 22

Figura 15 - Tabela de transições........................................................................................... 22

Figura 16 - AFD M' ............................................................................................................. 22

Figura 17 - AFε M ............................................................................................................... 24

Figura 18 - AFND M'........................................................................................................... 24

Figura 19 - Função de transição δ'=Fε(δ''(δ'''(q,^),x)) por partes ........................................... 25

Figura 20 - Tabela de transições aplicando δ'=Fε(δ''(δ'''(q,^),x)) ........................................... 25

Figura 21 - Produção soma de dois números ........................................................................ 26

Figura 22 - Produção do comando if da linguagem C ........................................................... 27

Figura 23 - BNF................................................................................................................... 27
ix
Figura 24 - Fases de um compilador..................................................................................... 28

Figura 25 - Grafo de transições ............................................................................................ 31

Figura 26 - Concatenação..................................................................................................... 32

Figura 27 - União................................................................................................................. 32

Figura 28 - Fechamento ....................................................................................................... 32

Figura 29 - Aplicação da primeira etapa do algoritmo de [MAN1974] ................................. 33

Figura 30 - Grafo de transições simplificado da Figura 29.................................................... 34

Figura 31 - Tabela de transições obtida a partir do grafo da Figura 30 .................................. 34

Figura 32 - Passos para construção da tabela de transições a partir do GT ............................ 35

Figura 33 - AFD................................................................................................................... 36

Figura 34 - Fases do algoritmo de [HOP1979] ..................................................................... 36

Figura 35 - Construção de um AFε a partir de uma ER (operadores ≥ 1) .............................. 37

Figura 36 - Construção de um AFε a partir de uma ER (zero operadores)............................. 38

Figura 37 - Divisão das expressões ao nível de símbolo do alfabeto ..................................... 39

Figura 38 - Construção da expressão a* ............................................................................... 39

Figura 39 - Construção da expressão aa|bb .......................................................................... 40

Figura 40 - AFε da ER a*(aa|bb) ......................................................................................... 40

Figura 41 - AFND da ER a*(aa|bb) pelo algoritmo descrito por [HOP1979]........................ 42

Figura 42 - AFND da Figura 41 na forma de tabela.............................................................. 42

Figura 43 - Tabela de transições do AFD da ER a*(aa|bb)................................................... 43

Figura 44 - AFD resultante da ER a*(aa|bb) ........................................................................ 43

Figura 45 - Etapas do algoritmo descrito por [SIL2000]. ...................................................... 44

Figura 46 - LLC para gerar expressão pós-fixada pelo algoritmo descrito por [SIL2000] ..... 44

Figura 47 - ER a(b|c)*|ca*|^ para sua forma pós-fixada por aplicação da LLC..................... 45

Figura 48 - Passos para construção da "Tabela do Desmonte" .............................................. 45

x
Figura 49 – “Tabela do Desmonte” ...................................................................................... 46

Figura 50 – “Tabela do Desmonte" da expressão pós-fixada da Figura 47 ............................ 47

Figura 51 - Tabela construída a partir da "Tabela do Desmonte" para obtenção do GT ......... 48

Figura 52 - GT resultante da tabela da Figura 51 .................................................................. 48

Figura 53 - Tabela de transições obtida a partir do GT da Figura 52 ..................................... 49

Figura 54 - AFD resultante da Figura 53 .............................................................................. 49

Figura 55 - Código direto em Pascal do AFD da Figura 54................................................... 50

Figura 56 - Programa que declara a unit da função ValidaER. .............................................. 52

Figura 57 - Visão geral do protótipo..................................................................................... 55

Figura 58 - Procedimentos do protótipo ............................................................................... 56

Figura 59 - Estrutura de dados do AFε ................................................................................. 57

Figura 60 - Estrutura de dados da tabela de transições do AFD ............................................ 57

Figura 61 - Tabela de transições do AFD ............................................................................. 58

Figura 62 - Implementação da LLC da Figura 63 ................................................................. 59

Figura 63 - LLC ................................................................................................................... 60

Figura 64 - Procedimento do conjunto de estados finais do AFND ....................................... 61

Figura 65 - Fases da geração do AFND................................................................................ 62

Figura 66 - Visão geral do protótipo..................................................................................... 63

Figura 67 - Fases que compõe o protótipo ............................................................................ 64

Figura 68 - Registro da "Tabela do Desmonte"..................................................................... 65

Figura 69 - Registro das transições do GT............................................................................ 65

Figura 70 – Estrutura tipo object para representação de conjuntos ........................................ 68

Figura 71 – Declaração do tipo Cjto..................................................................................... 68

Figura 72 – Descrição dos métodos do object Cjto .............................................................. 68

Figura 73 - Ícones do protótipos........................................................................................... 69

xi
Figura 74 - Tela principal de hopcroft.exe ............................................................................ 70

Figura 75 - Entrada do alfabeto e da expressão regular......................................................... 71

Figura 76 - Processamento da expressão regular................................................................... 72

Figura 77 - Resultados do processamento............................................................................. 73

Figura 78 - Visualizar a função Pascal.................................................................................. 74

Figura 79 - Função Pascal .................................................................................................... 74

Figura 81 - Acessar detalhes do processamento do AFND.................................................... 76

Figura 82 - Acessar detalhes do processamento do AFND.................................................... 76

Figura 83 - Acessar tabela de transições do AFD ................................................................. 77

Figura 84 - Tabela de transições........................................................................................... 78

Figura 85 - Tela principal do protótipo que implementa [SIL2000] ...................................... 79

Figura 86 – Tempo de execução do algoritmo ...................................................................... 81

Figura 87 - Contagem de transições ..................................................................................... 81

Figura 88 - Fases dos algoritmos descritos por [HOP1979] e [SIL2000]............................... 85

Figura 89 - AFε e “Tabela do Desmonte”............................................................................. 87

Figura 90 – Equivalência entre estruturas de dados .............................................................. 88

Figura 91 – Utilização de memória nos protótipos................................................................ 90

Figura 92 – ER a(b|c)*|ca*|^ e os dados obtidos para a Figura 91 ......................................... 91

Figura 93 – Tempos de processamento................................................................................. 92

Figura 94 – Eficiência de processamento.............................................................................. 93

Figura 95 – Otimização do AFD resultante .......................................................................... 94

Figura 96 – Passo para equivalência de Moore ..................................................................... 98

Figura 97 – Ícone de moore.exe............................................................................................ 99

Figura 98 – Tela principal de moore.exe............................................................................... 99

Figura 99 – Abertura de autômatos em moore.exe .............................................................. 100

xii
Figura 100 - Abertura de autômatos em moore.exe............................................................. 100

Figura 101 – Processamento da equivalência...................................................................... 101

Figura 102 – Interpretador de tabelas ................................................................................. 102

xiii
LISTA DE ABREVIATURAS E SIGLAS
AFD - AUTÔMATO FINITO DETERMINÍSTICO

AFND - AUTÔMATO FINITO NÃO DETERMINÍSTICO

AFε - AUTÔMATO FINITO COM MOVIMENTOS VAZIOS

BNF - BACKUS-NAUM FORM

CR - CONJUNTO REGULAR

ER - EXPRESSÃO REGULAR

GT - GRAFO DE TRANSIÇÕES

LLC - LINGUAGEM LIVRE DE CONTEXTO

xiv
RESUMO

O presente trabalho descreve a construção de um protótipo para transformar uma


expressão regular em uma função equivalente na linguagem de programação Pascal, através
do Teorema de Kleene. Este teorema é demonstrado através de dois algoritmos que propõe a
transformação de uma expressão regular em autômato finito: algoritmo do Desmonte,
desenvolvido e apresentado por [SIL2000] e um algoritmo apresentado por [HOP1979]. Uma
expressão regular submetida ao protótipo é convertida em uma função equivalente na
linguagem de programação Pascal, com base nas definições de cada algoritmo. Os algoritmos
estudados são avaliados e comparados segundo aspectos de complexidade e eficiência.

xv
ABSTRACT

The present work describes a prototype construction to transform a regular expression


in an equivalent function on Pascal programming language, through of Kleene theorem. This
theorem is demonstrated through two algorithms that it proposes the transformation of a
regular expression in finite automata: “Dismounting” algorithm developed and presented by
[SIL2000] and one algorithm presented by [HOP1979]. The regular expression submitted to
the prototype is transformed into an equivalent function on Pascal programming language,
based in of each algorithm definition. The studied algorithms analysed will be appraised and
compared according complexity aspects and efficiency.

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]).

Numa máquina de estados finitos as ações em resposta a uma dada seqüência de


entrada são completamente previsíveis. O número de estados que esta máquina pode alcançar
é finito. Cada estado da máquina depende dos estados anteriores.

Uma ER é constituída de expressões regulares mais simples utilizando-se um conjunto


de regras de definição. Cada Expressão Regular r denota uma linguagem L(r). As regras de
definição especificam como L(r) é formada através da combinação, em várias formas, das
linguagens denotadas pelas subexpressões de r ([AHO1995]).

Uma ER pode ser transformada em um Grafo de Transições (GT), o qual resulta em


um Autômato Finito Determinístico (AFD) ou então em um Autômato Finito Não
Determinístico (AFND). Um AFND pode ser transformado em um AFD, conforme algoritmo
mostrado em [MAN1974]. Existe ainda um outro tipo de AFND que é o Autômato Finito
Não-Determinístico com Movimentos Vazios (AFε). Este tipo de autômato apresenta
transições vazias entre os seus estados.

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]).

[HOP1979] descreve um algoritmo para fazer a transformação de uma ER em AFD.


Inicialmente este algoritmo transforma a ER em um AFε. O AFε resultante será transformado
em um AFND e este posteriormente será convertido em AFD.
2

[SIL2000] propõe um outro algoritmo que também realiza a transformação de uma ER


em AFD. Este algoritmo realiza a conversão da ER numa expressão em notação pós-fixada. A
etapa seguinte do algoritmo é transformar a expressão pós-fixada em uma tabela, chamada
"Tabela do Desmonte". A partir desta tabela é obtido um grafo de transições (GT) que
posteriormente será convertido em AFD.

A partir das informações acima descritas, serão implementados e apresentados dois


protótipos que demonstram a aplicação dos algoritmos citados. Ainda, o presente trabalho
visa estudar e comparar os algoritmos implementados.

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.

O objetivo secundário do trabalho é servir de auxílio para o processo de ensino da


disciplina de Teoria da Computação, no que diz respeito a aplicação do Teorema de Kleene.

1.2 ORGANIZAÇÃO DO TEXTO


O capítulo 2 apresenta a revisão bibliográfica. Ainda será apresentada uma breve
descrição dos conceitos básicos que regem este trabalho. Serão abordados as unidades
computacionais, as linguagens, o alfabeto, as gramáticas e conjuntos regulares. Também será
apresentada uma definição de detalhada de expressões regulares, grafos de transições e
autômatos finitos. Ainda, este capítulo apresenta alguns conceitos de linguagens de
programação e compiladores.

O capítulo 3 trata da definição do Teorema de Kleene ([GER1995]) e da transformação


e representação de uma expressão regular em autômato finito, conforme apresentado em
[MAN1974] e [HOP1979]. Neste capítulo serão apresentados os algoritmos descritos por
[HOP1979] e [SIL2000] para realizarem a transformação de uma expressão regular em um
autômato finito. Também serão apresentadas as regras para a construção de uma função na
linguagem de programação Pascal a partir de um autômato finito.
3

O capítulo 4 trata da especificação dos protótipos de transformação de uma expressão


regular em uma função na linguagem de programação Pascal. Também serão feitas
considerações sobre a implementação do protótipo.

O capítulo 5 aborda a apresentação da funcionalidade dos protótipos.

O capítulo 6 trata da comparação e análise dos algoritmos estudados.

O capítulo 7 apresenta a conclusão do trabalho.

O capítulo 8 apresenta algumas sugestões para trabalhos futuros.


4

2 REVISÃO BIBLIOGRÁFICA
Neste capítulo serão apresentados os tópicos básicos e essenciais para contextualização
do presente trabalho.

2.1 UNIDADES COMPUTACIONAIS


As técnicas computacionais aplicadas à utilização do computador como ferramenta
eficaz de trabalho vem sendo desenvolvidas, estudas e utilizadas, cada qual da sua forma, nas
mais diversas áreas da tecnologia do tratamento da informação e sua relação com o mundo
real. Estas técnicas computacionais são, na verdade, uma área da computabilidade, que
objetiva a representação de tudo que pode ser computado. Diversas são as técnicas
apresentadas com o objetivo de especificar funções computáveis. Estas técnicas são
conhecidas por unidades computacionais. Diversas unidades foram desenvolvidas, dentre elas
a Máquina de Turing, as unidades de Kleene, de Post, de Church, as máquinas finitas com e
sem memória de pilha e os autômatos. Todas as técnicas, também chamadas de máquinas,
foram criadas com o objetivo de melhor atender o princípio da computabilidade. Como
característica importante destas unidades computacionais cita-se o reconhecimento de uma
linguagem ([WAZ1994]).

2.2 SISTEMAS DE ESTADOS FINITOS


Um computador armazena as informações internamente na forma binária. Em um
instante qualquer o computador contém determinadas informações armazenadas, em memória,
na forma de algum padrão, que serão chamados estados do computador neste instante. Como
um computador contém uma quantidade finita de memória, existe um número finito de
estados que ele pode assumir. Um relógio interno sincroniza as ações do computador. Em um
ciclo de relógio, a entrada pode ser lida, o que pode mudar algumas posições da memória e,
portanto, mudar o estado da máquina para um novo estado. A representação do novo estado
dependerá da entrada dada e do estado anterior. Sendo conhecidos o estado anterior e a
entrada, a alteração para o novo estado é previsível. O novo estado determina a saída da
máquina. Assim, à medida que os ciclos de relógio se sucedem, a máquina produzirá uma
seqüência de saídas em resposta a uma seqüência de entradas. Este computador retrata as
características de um modelo conhecido por máquina de estados finitos. Observando o
comportamento deste computador, é possível separar algumas propriedades importantes:
5

a) as operações da máquina são sincronizadas por ciclos de relógio;


b) o processamento é determinístico, ou seja, as ações em resposta a uma dada
seqüência de entrada são completamente previsíveis;
c) a máquina responde a entradas;
d) a máquina pode alcançar um número finito de estados. Num determinado momento
a máquina estará num destes estados. O estado seguinte dependerá do estado atual e
da entrada atual. O estado atual depende dos estados anteriores e entradas
anteriores, enquanto que o estado anterior depende de seus estados e entradas
anteriores e assim por diante. Portanto o estado da máquina, a qualquer momento,
serve como uma espécie de memória;
e) a máquina é capaz de produzir saídas. A saída é o estado atual da máquina que
depende dos estados anteriores.

Uma máquina de estados finitos é tida como um computador real significativamente


restrito. A máquina de estados finitos é também chamada de autômato finito e que
compartilha com o computador real uma unidade central de processamento de capacidade fixa
e finita. O autômato finito recebe uma entrada, processa esta entrada e retorna uma indicação
de aceite ou não da entrada. O finito não produz saídas e o fato de ser um modelo restrito de
computador real é devido a total ausência de memória fora da unidade central de
processamento fixa ([LEW2000]). Uma definição formal de autômato finito será vista mais
adiante.

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

c) regras de aceitação de cadeias ou reconhecedores: para verificar se uma cadeia é


sentença da linguagem, basta aplicar a ela as regras de aceitação, as quais deverão
fornecer a decisão desejada acerca da pertinência da cadeia à linguagem.

Com base na definição de linguagem, um número infinito de linguagens diferentes


pode ser gerado. Apesar disto, apenas um conjunto restrito de linguagens, que apresentam
características bem definidas, é adequado ao estudo da computabilidade. Este conjunto de
linguagens é dividido em quatro classes e cada uma dessas classes apresenta gramáticas e
reconhecedores correspondentes. Cada classe de linguagem é caracterizada pela imposição de
diferentes restrições nas produções das gramáticas que as definem. Essas classes são
agrupadas no que se denomina Classificação ou Hierarquia de Chomsky ([HOP1979]).

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).

Ao conjunto de cadeias geradas por uma gramática dá-se o nome de linguagem. Um


número infinito de linguagens pode ser gerado. No contexto do estudo de compiladores de
linguagens de programação as gramáticas apresentam limitações, pois estas linguagens não
necessitam de toda generalidade que uma gramática geral pode oferecer ([AHO1995]). Desta
forma, as linguagens de programação enquadram-se num subconjunto pequeno de linguagens
com características bem definidas.

No modelo matemático desenvolvido em 1959 pelo lingüista Noam Chomsky, as


linguagens estão divididas em quatros classes. Cada classe apresenta gramáticas e
8

reconhecedores correspondentes, sendo que cada uma é caracterizada pela imposição de


diferentes restrições nas produções das gramáticas que as definem. Este modelo deu origem a
Hierarquia de Chomsky. Assim, as gramáticas são divididas em:
a) gramáticas regulares: são denotadas por expressões regulares e geram linguagens
reconhecidas por autômatos finitos. De grande importância no estudo de
compiladores por possuírem propriedades adequadas para a obtenção de
reconhecedores simples. Uma linguagem definida por gramáticas regulares é dita
regular [MEN1998];
b) gramáticas livres de contexto: reconhecida pelos autômatos de pilha. Esta classe
gramatical gera as Linguagens Livres de Contexto, que são de grande importância
na construção de compiladores utilizados nos computadores atuais. Uma descrição
mais detalhada é apresentada a seguir;
c) gramáticas sensíveis ao contexto: não há reconhecedores para linguagens geradas
por esta gramática, porque qualquer máquina capaz de gerar linguagens deste tipo é
poderosa o suficiente para gerar linguagens baseadas em gramáticas com estrutura
de frase ([WAZ1994]). As linguagens definidas pelas gramáticas sensíveis ao
contexto, ou linguagens sensíveis ao contexto, estão contidas nas linguagens
enumeráveis recursivamente. As linguagens geradas por esta classe gramatical
apresentam algumas restrições nas produções que as definem ([MEN1998]);
d) gramáticas com estrutura de frase: gera linguagens reconhecíveis por máquinas
de Turing ou linguagens enumeráveis recursivamente. A Máquina de Turing é
considerado o dispositivo computacional mais geral. Desta forma, a classe das
linguagens enumeráveis recursivamente representa o conjunto de todas as
linguagens que podem ser reconhecidas mecanicamente e em um tempo finito.
Uma linguagem enumerável recursivamente pode ser representada através de uma
gramática sem qualquer restrição sobre a forma das produções ([MEN1998]).

Segundo a Hierarquia de Chomsky, cada linguagem é definida de acordo com a sua


gramática. Desta forma, na Hierarquia de Chomsky uma linguagem é classificada como sendo
da gramática do tipo 0 quando a linguagem é definida pelas gramáticas com estrutura de frase,
as linguagens definidas por gramáticas do tipo 1 pelas gramáticas sensíveis ao contexto, as
linguagens definidas por gramáticas do tipo 2 através das gramáticas livres de contexto e as
linguagens definidas por gramáticas do tipo 3 pelas gramáticas regulares, conforme
apresentado na Figura 1. Ainda segundo esta hierarquia, uma gramática do tipo 3 também
9

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ÁTICA TIPO LINGUAGEM

Gramáticas regulares 3 Linguagens regulares

Gramáticas livres de contexto 2 Linguagens livres de contexto

Gramáticas sensíveis ao contexto 1 Linguagens sensíveis ao contexto

Gramáticas com estrutura de frase 0 Linguagens enumeráveis recursivamente

Figura 2 - Hierarquia de Chomsky

Gramáticas Regulares

Gramáticas Livres de Contexto

Gramáticas Sensíveis ao Contexto

Gramáticas com Estrutura de Frase

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.

2.4.1.2 GRAMÁTICAS REGULARES


São denotadas por expressões regulares e geram linguagens reconhecidas por
autômatos finitos. Conforme definido na Hierarquia de Chomsky, as gramáticas regulares
definem linguagens regulares. Para demonstrar que uma linguagem é regular, é suficiente
construir um Autômato Finito que a reconheça. Toda linguagem regular pode ser descrita por
uma expressão mais simples, denominada expressão regular ([MEN1998]).

2.4.1.2.1 EXPRESSÕES REGULARES

Na linguagem de programação Pascal, um identificador é uma letra seguida por zero


ou mais letras ou dígitos. Através de uma expressão regular pode-se definir este identificador.
A expressão regular letra(letra+digito)* descreve um identificador da linguagem de
programação Pascal.
11

Uma Expressão Regular (ER) é constituída de expressões regulares mais simples


utilizando-se um conjunto de regras de definição. Cada Expressão Regular r denota uma
linguagem L(r). As regras de definição especificam como L(r) é formada através da
combinação, em várias formas, das linguagens denotadas pelas subexpressões de r
([AHO1995]).

Uma ER é uma notação formal que permite representar as manipulações algébricas


com conjuntos regulares. A classe de expressões regulares sobre o alfabeto (∑) é
indutivamente definida como segue ([MEN1998]):
a) ∅ é uma ER e denota a linguagem vazia;
b) ^ é uma ER e denota a linguagem contendo exclusivamente a palavra vazia, ou
seja, {^};
c) qualquer símbolo x pertencente ao ∑ (x ∈ ∑) é uma ER e denota a linguagem
contendo a palavra unitária x, ou seja, {x};
d) se r e s são expressões regulares e denotam as linguagens R e S, respectivamente,
então:
- (r + s) é uma ER e denota a linguagem R ∪ S (união);
- (r . s) é uma ER e denota a linguagem RS = {uv | u ∈ R e v ∈ S}
(concatenação);
- (r*) é uma ER e denota a linguagem R* (fechamento de Kleene), onde R*=R0 ∪
R1 ∪ R2 ∪ R3 ∪ Rn, sendo R0 = {^}, R1 = R, Rn = Rn-1 × R para n > 1.
Considerando o ∑ = {a,b} e a expressão regular r como sendo (ab)*, então o
conjunto regular formado é {^, ab, abab, ababab, ..., abab...ab}. A definição de
conjunto regular é vista a seguir.

A omissão dos parênteses em uma ER é usual, respeitando as seguintes convenções:


a) o operador * tem precedência sobre a união e a concatenação;
b) a concatenação tem precedência sobre a união.

A Figura 3 mostra um conjunto de propriedades ou leis algébricas que regem as


expressões regulares. Estas propriedades permitem que expressões regulares possam ser
manipuladas de forma equivalente. Se r e s são expressões regulares que denotam a mesma
linguagem, então r e s são equivalentes ([AHO1995]).
12

Figura 3 - Propriedades algébricas das expressões regulares

AXIOMA: PROPRIEDADE:

r+s=s+r Comutativa

r+(s+t)=(r+s)+t Associativa

(rs)t=r(st) Concatenação Associativa

r(s+t)=rs+rt A concatenação se distribui sobre +

(s+t)r=sr+tr

^r=r ^ é o elemento identidade da concatenação

r^=r

r*=(r+^)* Relação entre ^ e *

r*=r** Idempotência, ou seja, uma ER submetida a


um ou mais operadores * representa
exatamente a mesma ER submetida a apenas
um operador *
Fonte: [AHO1995]

A Figura 4 mostra exemplos de expressões regulares com as respectivas linguagens


representadas, considerando o ∑={a,b} ([MEN1998]).
13

Figura 4 - Expressões regulares e linguagens representadas

EXPRESSÃO REGULAR: LINGUAGEM REPRESENTADA:

aa Somente a palavra aa

ba* Todas as palavras que iniciam por b, seguidas


ou não por um ou vários símbolo a;

(a+b)* Todas as palavras sobre {a,b}

(a+b)*aa(a+b)* Todas as palavras contendo aa como


subpalavra

(a+b)*(aa+bb) Todas as palavras que terminam com aa ou bb

(a+^)(b+ba)* Todas as palavras que não possuem dois a


consecutivos

a*ba*ba* Todas as palavras sobre o alfabeto contendo


exatamente dois b
Fonte: [MEN1998]

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

Figura 5 - Expressões regulares e conjuntos regulares

EXPRESSÃO REGULAR CONJUNTO REGULAR

(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]

2.4.1.2.2 CONJUNTOS REGULARES

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]).

Os conjuntos regulares são uma classe especial de sentenças do ∑. São chamados


conjuntos regulares quando formados por um conjunto finito de sentenças do ∑ ou o conjunto
vazio (∅). Também são conjuntos regulares aqueles formados por outros conjuntos regulares
através das operações de união (A ∪ B), concatenação ou produto (A . B) e operador * (A*).
Ainda, um conjunto é regular se e somente se:
a) é possível de ser representado por uma ER;
b) for aceito por Autômato Finito Determinístico (AFD);
c) for aceito por um Grafo de Transições (GT), gerador de um AFD ou um Autômato
Finito Não Determinístico (AFND).

Ainda segundo [MAN1974], conjuntos regulares são expressos de uma forma mais
conveniente utilizando-se grafo de transições.

2.4.1.2.3 GRAFOS DE TRANSIÇÕES (GT)

Um grafo de transições T sobre ∑ é um grafo conforme mostra a Figura 6. Segundo


[MAN1974] o GT é uma generalização da notação de Autômatos Finitos.
15

O GT ou diagrama de transições é a representação gráfica das ações a serem tomadas


para avaliação de uma dada seqüência. A representação define quais os caminhos a serem
tomados para fazer a validação. O GT representa uma expressão planificada na forma gráfica,
onde as posições representadas por círculos representam os estados e as setas representam as
arestas ou lados do grafo. Um GT pode ser um AFD ou um AFND ([MAN1974]).
Figura 6 - Grafo de Transições a partir de uma expressão regular

ba* q0
b
fq0f

Expressão Regular Grafo de Transições

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

2.4.1.2.4 AUTÔMATOS FINITOS


Um autômato finito pode ser definido como um modelo restrito de um computador
real. Trata-se de uma máquina de estados finitos cuja função é receber uma entrada e dizer se
esta é válida ou não, de acordo com o contexto do autômato. Não há saída, não há memória e
a capacidade de processamento é fixa e finita ([LEW2000]).

Os Autômatos Finitos são divididos em 3 tipos:


a) determinísticos (AFD);
b) não-determinístico (AFND);
c) com movimentos vazios (AFε).
16

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 Figura 8 mostra um autômato finito como uma máquina de controle finito


([MEN1998]).
Figura 8 - Automato finito como máquina de controle finito

a a b c c b a a

controle

Fonte: [MEN1998]

Os autômatos finitos são um tipo simples de reconhecedor de linguagens regulares


definidos por M=(∑, Q, δ, q0, F), onde ∑ representa o alfabeto de entrada e corresponde ao
conjunto finito não vazio dos símbolos de entrada ou átomos indivisíveis que compõem a
cadeia de entrada submetida ao autômato para aceitação; Q representa o conjunto finito não
vazio dos estados do autômato; δ é a função de transição de estados do autômato, com o
objetivo de indicar as traduções possíveis em cada configuração do autômato; q0 é o estado
inicial do autômato e é um elemento de Q; F é um subconjunto do conjunto Q dos estados do
autômato e contém todos os estados finais do autômato finito.
17

Os autômatos finitos podem ser utilizados na construção de analisadores de linguagens


de programação ([AHO1995]). Um autômato finito pode ser representado através de um
diagrama de transições ou grafo de transições (GT).

2.4.1.2.5 AUTÔMATO FINITO DETERMINÍSTICO (AFD)


Num AFD, para cada estado s e um símbolo de entrada a existe no máximo um lado
rotulado a deixando s. Ainda, um AFD não possui transições rotuladas com o símbolo vazio
(^) ([AHO1995]). Um exemplo de AFD pode ser visto na Figura 9.

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]).

O processamento de AFD para uma palavra de entrada w, consiste na sucessiva


aplicação da função de transição ou programa para cada símbolo de w, da esquerda para
direita, até ocorrer uma condição de parada. As condições de parada são as seguintes:
a) após o processamento do último símbolo da fita, o AFD assume um estado final. A
entrada w é aceita pelo autômato;
b) após o processamento do último símbolo da fita, o AFD assume um estado não-
final. A entrada w é rejeitada pelo autômato;
c) a função de transição é indefinida para o símbolo corrente. A máquina pára e a
entrada w é rejeitada pelo autômato;
Figura 9 - AFD

b a a

1 2 3 4
a b b
a

Fonte: [AHO1995]
18

2.4.1.2.6 AUTÔMATO FINITO NÃO DETERMINÍSTICO (AFND)


Num AFND a função programa, ao processar uma entrada composta pelo estado
corrente e símbolo lido, tem como resultado um conjunto de novos estados, ou seja, o AFND
pode levar a mais de um estado ([AHO1995] [GER1995] [MEN1998]).

Um AFND é 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).
Excetuando-se a função de transição, a definição de AFND é análoga a definição de AFD
([MEN1998]). A Figura 10 mostra um AFND.

De acordo com [MEN1998], a capacidade do não-determinismo nos autômatos finitos


não aumenta seu poder computacional. Assim, para cada AFND, é possível construir um AFD
equivalente que realiza o mesmo processamento e vice-versa.
Figura 10 - AFND

1 f30
b
b

2
a

f40
a

2.4.1.2.7 AUTÔMATO FINITO COM MOVIMENTOS VAZIOS (AFεε)

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

representada por Q × (∑ ∪ {^}) → 2Q (sendo 2Q o conjunto de todos os subconjuntos de Q),


ou seja, a função de transição ao processar uma entrada composta pelo estado corrente e um
símbolo lido (que poderá ser o símbolo vazio (^)) tem como resultado um conjunto de novos
estados. Cada estado deste conjunto será novamente submetido a função de transição dando
origem a novos conjuntos de estados.

Um AFε difere de um AFND apenas na função de transição (δ). Todos os demais


elementos (∑, Q, q0, F) do AFε são como na definição de AFND. O movimento vazio do AFε
é representado pela aplicação da função de transição em um dado estado q com o símbolo
vazio (^) ([MEN1998]).

O processamento de um AFε é análogo ao de um AFND. Adicionalmente, o


processamento de uma transição para uma entrada vazia também é não-determinística. Assim,
um AFε ao processar uma entrada vazia assume simultaneamente os estados destino e origem,
ou seja, a origem de um movimento vazio sempre é um caminho alternativo ([MEN1998]).

Da mesma forma como o não determinismo, a característica dos movimentos vazios


nos autômatos finitos não aumenta sua capacidade computacional. Desta forma é possível
construir um AFND que realiza o mesmo processamento do AFε. O contrário também é
verdadeiro ([MEN1998]). A Figura 11 mostra um AFε.
Figura 11 - AFεε

b a
1 2
^

2.4.1.2.8 EQUIVALÊNCIA ENTRE AUTÔMATOS FINITOS


As várias classes de Autômatos Finitos permitem a equivalência entre si. Desta forma,
é possível realizar várias construções conforme mostra a Figura 12.
Figura 12 - Equivalência entre autômatos

AFD AFND AFε


20

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.

2.4.1.2.8.1 EQUIVALÊNCIA ENTRE AFD E AFND


A classe dos autômatos finitos determinísticos é equivalente a classe dos Autômatos
Finitos Não Determinísticos. Para provar que isto é verdadeiro, deve-se mostrar que, a partir
de um AFND, é possível construir um AFD que realiza o mesmo processamento. Esta
demonstração é feita através de um algoritmo cuja idéia central é a construção de estados do
AFD que simulem as diversas combinações de estados alternativos do AFND ([HOP1979]
[MAN1974]).

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.

A Figura 13 mostra o formato de uma Tabela de Transições para construção do AFD


equivalente, sendo dado ∑={a,b} como exemplo. A Tabela de Transições utilizada na
construção da equivalência é composta de duas colunas principais. A primeira representa a
coluna dos estados do AFD M' que está sendo construído. A segunda coluna representa o
conjunto de transições, por símbolo do alfabeto, do AFD M' que está sendo construído. Esta
segunda coluna é dividida em sub-colunas onde cada uma das sub-colunas é nomeada, sem
repetições, com um símbolo do alfabeto. O número de linhas da Tabela de Transições será
conforme o número de estados do AFD M'. Como no exemplo o alfabeto ∑={a,b} tem apenas
21

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}

Transições do AFD M'


Estados do por símbolo do ∑
AFD M'
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.

Para o exemplo da Figura 14, que representa um AFND, a tabela de transições é


conforme a Figura 15, considerando o ∑={a,b}.
22

Figura 14 - AFND

q0 q1 q2 fq0f
a a a
b

Fonte: [MEN1998]
Figura 15 - Tabela de transições

Estados do Transições do AFD M' por símbolo do ∑

A.F.D. M'
a b

{q0} {q0q1} {q0}

{q0q1} {q0q1q2} {q0}

{q0q1q2} {q0q1q2qf} {q0}

{q0q1q2qf} {q0q1q2qf} {q0}


Fonte: [MEN1998]

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.

2.4.1.2.8.2 EQUIVALÊNCIA ENTRE AFND E AFεε


A classe dos autômatos finitos não-determinísticos é equivalente a classe dos
autômatos finitos com movimentos vazios. Para provar que isto é verdadeiro, constrói-se um
AFND que realiza o mesmo processamento do AFε. Produz-se uma função programa sem
movimentos vazios onde o conjunto de estados de chegada de cada transição não vazia é
estendido com todos os estados possíveis de serem atingidos por transições vazias (^)
([HOP1979] [MEN1998]).

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'.

A Figura 17 mostra um exemplo de AFε. A Figura 18 mostra o AFND resultante,


aplicada a equivalência entre AFND e AFε.
24

Figura 17 - AFεε M

0 1 2
q0 q1 q22
^ ^

Fonte: [HOP1979]

Figura 18 - AFND M'

0 1 2
1 1
q0 q1 q22
0 2
0
1
2
Fonte: [HOP1979]

O conjunto de estados finais do AFND M' é {q0,q1,q2}, pois Fε(q0)={q0,q1,q2},


Fε(q1)={q1,q2} e Fε(q2)={q2} apresentam o estado final q2 do AFε M. Logo, q0, q1 e q2
fazem parte do conjunto de estados finais do AFND M'.

A construção da Tabela de Transições do AFND M', aplicando a função de transição


δ', é mostrada na Figura 19 em três fases. A primeira fase corresponde à resolução da função
δ'''(q,^) para cada estado do AFε M. Na segunda fase aplica-se a função δ''(δ'''(q,^),x) sobre o
resultado da primeira fase, para cada símbolo do ∑. Na terceira fase aplica-se a função
Fε(δ''(δ'''(q,^),x)), que resulta nas transições do AFND M'. A Figura 20 apresenta a Tabela de
Transições resultante dos passos da Figura 19. O AFND M' apresentado na Figura 18 é
resultante desta Tabela de Transições.
25

Figura 19 - Função de transição δ'=Fεε(δ


δ''(δ
δ'''(q,^),x)) por partes

δ'''(q,^)

q0 q1 q2

δ'''(q0,^)={q0,q1,q2} δ'''(q1,^)={q1,q2} δ'''(q2,^)={q2}

∑= δ''(δ'''(q,^),x)

0 δ''({q0,q1,q2},0)={q0} δ''({q1,q2},0)={} δ''({q2},0)={}

1 δ''({q0,q1,q2},1)={q1} δ''({q1,q2},1)={q1} δ''({q2},1)={}

2 δ''({q0,q1,q2},2)={q2} δ''({q1,q2},2)={q2} δ''({q2},2)={q2}

Fε(δ''(δ'''(q,^),x))

Fε({q0})={q0,q1,q2} Fε(∅)={} Fε(∅)={}

Fε({q1})={q1,q2} Fε({q1})={q1,q2} Fε(∅)={}

Fε({q2})={q2} Fε({q2})={q2} Fε({q2})={q2}

Figura 20 - Tabela de transições aplicando δ'=Fεε(δ


δ''(δ
δ'''(q,^),x))

Transições do AFND M' por símbolo do ∑


Estados do AFND M'
0 1 2

q0 {q0,q1,q2} {q1,q2} {q2}

q1 ∅ {q1,q2} {q2}

q2 ∅ ∅ {q2}
26

2.4.2 GRAMÁTICAS LIVRES DE CONTEXTO


Esta seção apresenta uma breve introdução ao conceito de gramáticas livres de
contexto. Isto se torna necessário devido a sua relação com as linguagens livres de contexto e,
principalmente, devido à aplicação destas linguagens nos algoritmos descritos por [HOP1979]
e [SIL2000].

As gramáticas livres de contexto definem, segundo a Hierarquia de Chomsky,


linguagens do tipo 2 ou linguagens livres de contexto (LLC). Para os estudiosos lingüistas, as
gramáticas livres de contexto são importantes para o estudo das linguagens naturais mas nem
sempre são adequadas. Isto se deve ao fato que nem sempre é possível representar uma
linguagem natural utilizando uma LLC. No estudo da ciência da computação, estas linguagens
são particularmente importantes na definição de linguagens de programação, na construção de
mecanismos léxicos e sintáticos e ferramentas de processamento de cadeias, conforme
apresentando em [HOP1979].

Uma gramática livre de contexto consiste de terminais, não-terminais, um símbolo


inicial e produções. Os terminais são os símbolos básicos de onde as cadeias são formadas. Os
não-terminais são as variáveis sintáticas que denotam as cadeias de caracteres e definem
conjuntos de cadeias que ajudam a definir a linguagem gerada pela gramática. Também impõe
uma estrutura hierárquica na linguagem que é útil tanto para a análise sintática quanto para a
tradução. Um símbolo inicial é um terminal a partir do qual dá-se inicio a análise sintática. As
produções descrevem a maneira como os terminais e os não-terminais podem ser combinados
na formação das cadeias. Cada produção é um não-terminal, seguido por uma seta (→) ou o
símbolo "::=", seguido por uma cadeia de terminais e não-terminais. A Figura 21 apresenta
um exemplo de produção que representa a soma de dois números.
Figura 21 - Produção soma de dois números

soma ::= número + número

Um outro exemplo de produção que especifica a sintaxe do comando if-else da


linguagem C possui a forma if (expressão) comando else comando. Usando-se a variável expr
a fim de denotar uma expressão e a variável cmd para um comando ou enunciado, esta regra
pode ser expressa conforme apresentado na Figura 22. Tal regra é chamada de produção.
27

Nesta produção, os elementos if e () são chamados de tokens. As variáveis expr e cmd


representam seqüências de tokens e são chamadas de não-terminais.
Figura 22 - Produção do comando if da linguagem C

cmd := if (expr) cmd else cmd

Fonte: [AHO1995]

No desenvolvimento de linguagens de programação uma forma de gramática


amplamente utilizada na especificação destas linguagens é a BNF (Backus-Naur Form). A
Figura 23 mostra um exemplo de BNF.
Figura 23 - BNF
<S> ::= a<S>|^
<E> ::= <E>+id|id
Fonte: [WAZ1994]

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 LINGUAGENS DE PROGRAMAÇÃO


A linguagem de programação é uma forma de comunicação do homem com a
máquina. As linguagens de programação são mais simples e restritas do que a linguagem
utilizada pelo homem. Isto é necessário, pois nem sempre a linguagem humana é clara e
objetiva para o entendimento do computador. Observando este aspecto, foram definidas
linguagens, as quais tentam aproximar-se das linguagens naturais, mas que possuem a
facilidade de expressarem algoritmos de uma forma mais clara e sem ambigüidades. Tais
linguagens são chamadas de linguagens de programação.

Uma linguagem de programação é uma notação formal para descrição de algoritmos


que serão executados em um computador ([GHE1987]). Esta notação é implementada em
programas chamados compiladores que realizam a análise léxica, sintática e semântica da
linguagem em questão. As linguagens de programação Pascal e C são exemplos de linguagens
de programação.
28

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]

Na fase da Análise Léxica, o compilador lê o programa-fonte, que é representado por


uma seqüência de caracteres, e os transforma em tokens que representam uma seqüência de
caracteres logicamente coesiva. Identificadores, palavras-chave (if, then, while), números e
outros são exemplos de tokens. A fase de Análise Sintática obtém a cadeia de tokens
proveniente da Análise Léxica e verifica se a mesma pode ser gerada pela gramática da
linguagem-fonte. Na fase de Análise Semântica, quando a sintaxe das sentenças do programa-
fonte está correta, o compilador gera o significado das sentenças, ou seja, cada sentença é
traduzida para a sua função específica. Desta forma obtêm-se o código intermediário. O
29

código intermediário gerado após a análise semântica é submetido ao processo de otimização


a fim de gerar um programa de computador mais eficiente. Terminada a otimização, a fase
final do compilador é a geração do código de máquina (programa executável), com base no
código disponibilizado pelo otimizador ([AHO1995]).
30

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.

Assim, os conjuntos regulares podem ser descritos por expressões regulares. Um


conjunto regular qualquer pode ser descrito por mais de uma expressão regular. Existem
conjuntos que não são regulares e portanto, segundo o Teorema de Kleene, não podem ser
reconhecidos por uma máquina de estados finitos. Tal característica sugere uma limitação do
uso de máquinas de estados finitos ([GER1995]).

A descrição de um conjunto regular em uma expressão regular é possível, mas


determinar a equivalência de expressões regulares pode ser muito difícil, mesmo quando elas
representam o mesmo conjunto regular. A equivalência de duas expressões pode ser aceita,
mas ainda não existe um algoritmo eficaz para esta comprovação ([GER1995]).

Ainda, segundo [MAN1974], o Teorema de Kleene define:


a) para todo grafo de transições T sobre o alfabeto (∑) existe uma expressão regular R
sobre o ∑ tal que o conjunto de todas as palavras reconhecidas por R é igual ao
conjunto de todas as palavras reconhecidas por T;
b) para toda expressão regular R sobre o ∑ existe um autômato finito M sobre o ∑ tal
que o conjunto de todas as palavras reconhecidas por M é igual ao conjunto de
todas as palavras reconhecidas por R.

Observa-se que [MAN1974] considera um grafo de transições como um grafo que


pode apresentar indeterminismo (vários nós iniciais e várias arestas com o mesmo símbolo
31

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.

Com base na definição do Teorema de Kleene é possível construir algoritmos que


transformam expressões regulares em autômato finitos equivalentes, tais como os descritos
por [HOP1979] e [SIL2000].

3.1 TRANSFORMAÇÃO DE EXPRESSÕES REGULARES EM


AUTÔMATOS FINITOS
Para toda expressão regular R sobre o ∑, existe um autômato finito M sobre o ∑, sendo
o conjunto de todas as palavras reconhecidas por M igual ao conjunto de todas as palavras
reconhecidas por R. Desta forma, aplicando a definição do Teorema de Kleene torna-se
possível à construção de algoritmos que transformam expressões regulares em autômatos
finitos determinísticos. Além dos algoritmos descritos por [HOP1979] e [SIL2000], pode ser
citado o algoritmo descrito por [MAN1974].

3.1.1 ALGORITMO DESCRITO POR [MAN1974]


O algoritmo descrito por [MAN1974] também utiliza a definição do Teorema de
Kleene para realizar a transformação de uma expressão regular (ER) em um autômato finito
determinístico (AFD). Neste algoritmo, primeiramente obtém-se um grafo de transições T tal
que o conjunto de todas as palavras reconhecidas por T é igual ao conjunto de todas as
palavras reconhecidas pela expressão regular R. O grafo de transições é iniciado conforme
mostra a Figura 25. Nesta figura, q0 representa o estado inicial e q1 representa o estado final
do grafo. R representa a expressão regular que será transformada no grafo de transições T.
Figura 25 - Grafo de transições

R
q0 q
2f
32

Em seguida a expressão regular R é submetida a "quebras" sucessivas até que todas


arestas estejam rotuladas somente por símbolos do alfabeto ou pelo símbolo vazio (^). Para
cada operação de concatenação, fechamento e união aplica-se a regra corresponde, conforme
Figuras 26, 27 e 28 respectivamente.
Figura 26 - Concatenação

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

Exemplificando esta primeira fase do algoritmo descrito por [MAN1974], aplicam-se


as regras referentes às operações de concatenação, união e fechamento sobre a expressão
regular R=(ba)*(ab)*(aa|bb)* e obtém-se o grafo de transições da Figura 29, sendo ∑={a,b}.
Este grafo poderá ser simplificado, resultando no grafo da Figura 30. O grafo apresenta 7
estados, onde o estado 1 é o estado inicial e o estado 7 é o estado final do grafo de transições.
33

Figura 29 - Aplicação da primeira etapa do algoritmo de [MAN1974]

q0
(ab)*(ba)*(aa|bb)* qf

GRAFO DE TRANSIÇÕES INICIAL

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

Figura 30 - Grafo de transições simplificado da Figura 29

a
1
^ 3
^ 7
b

b
2 4 6

Tendo-se obtido o grafo de transições (GT) da ER, inicia-se a segunda etapa do


algoritmo descrito por [MAN1974]. Nesta etapa, constrói-se uma tabela de transições. A
Figura 31 mostra a tabela de transições obtida a partir do grafo da Figura 30. A identificação
numerada dos estados do grafo é necessária para permitir as demais construções do algoritmo,
além de identificar cada estado.
Figura 31 - Tabela de transições obtida a partir do grafo da Figura 30

Mj
ESTADOS DO AUTÔMATO FINITO Mi
a b

Q1 {1,3,7} {2,5} {4,6}

Q2 {2,5} {7} {1,3,7}

Q3 {4,6} {3,7} {7}

Q4 {7} {5} {6}

Q5 {3,7} {5} {4,6}

Q6 {5} {7} {}

Q7 {6} {} {7}
35

Para a construção da tabela de transições da Figura 31 utilizam-se os passos descritos


na Figura 32.
Figura 32 - Passos para construção da tabela de transições a partir do GT
Para a construção da tabela de transições os seguintes passos deverão ser seguidos:
a) construir a tabela com as colunas:
- estados do autômato finito;
- coluna Mi;
- coluna Mj: dividida em sub-colunas que representam os símbolos do ∑;
b) a coluna Mi é iniciada, na primeira linha (M0), com o conjunto dos estados
iniciais e todos os estados atingíveis a partir dos estados iniciais pela palavra
vazia (^);
c) na coluna Mj, para cada símbolo do ∑, gerar o conjunto dos estados atingíveis a
partir dos estados do conjunto da coluna Mi com o símbolo do ∑. Se o estado
atingido pelo símbolo do ∑ atingir outros estados com o símbolo vazio (^), estes
outros estados atingidos também farão parte do conjunto;
d) se o conjunto obtido não tiver sido representado na coluna Mi, acrescentar uma
nova linha à coluna Mi com este conjunto;
e) repetir o passo c e d até que todos os conjuntos da coluna Mi tenham sido
processados;
f) gerar o autômato finito (AFD);
- cada conjunto da coluna Mi representa um estado do AFD;
- a primeira linha (M0) da coluna Mi representa o estado inicial do AFD;
- cada conjunto da coluna Mi que apresentar pelo menos um estado final do
grafo de transições será um estado final do AFD.

Aplicando o passo f da Figura 32 a tabela de transições da Figura 31, obtém-se o


AFD da Figura 33. Os estados são identificados para permitir outras construções a partir do
AFD e a diferenciação dos estados do AFD.
36

Figura 33 - AFD

a a
a
Q1 b Q2 Q4 Q6
a
b

b
b
Q3 a Q5 Q7
b
a

O AFD da Figura 33 apresenta 7 estados, sendo o estado Q1 o estado inicial. O


conjunto de estados finais do AFD é formado pelos estados Q1, Q4 e Q5, pois o estado final
do grafo de transições representado pelo estado 7 faz parte dos respectivos conjuntos,
formados pelos estados do grafo, na coluna Mi da tabela de transições da Figura 31.

3.1.2 ALGORITMO DESCRITO POR [HOP1979]


Da mesma forma como o algoritmo descrito por [MAN1974], o algoritmo que
[HOP1979] descreve também visa a transformação de expressões regulares em autômatos
finitos determinísticos pela aplicação do Teorema de Kleene. A Figura 34 apresenta as fases
deste algoritmo.
Figura 34 - Fases do algoritmo de [HOP1979]

AFND

AFD AFε

ER

Fonte: [HOP1979]
37

Dada uma expressão regular R, o primeiro passo do algoritmo é a obtenção de um


autômato finito com movimentos vazios (AFε) a partir da expressão regular. Isto é realizado
aplicando-se regras para as operações de união, concatenação e fechamento, conforme Figura
35 para expressões onde há uma ou mais operações. Quando a expressão não apresentar
operações, são aplicadas as propriedades mostradas na Figura 36. Neste caso também são
consideradas três situações: a primeira para uma expressão com apenas o símbolo vazio(^), a
segunda para uma expressão vazia e a terceira para uma expressão com apenas um símbolo do
alfabeto.
Figura 35 - Construção de um AFεε a partir de uma ER (operadores ≥ 1)

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

Figura 36 - Construção de um AFεε a partir de uma ER (zero operadores)

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

Figura 37 - Divisão das expressões ao nível de símbolo do alfabeto

expressão aa
a a

1 2 3 4
a a

1 2 3 4
a ^ a

CONCATENAÇÃO

Figura 38 - Construção da expressão a*

3 1 2 4
^ a ^

FECHAMENTO
40

Figura 39 - Construção da expressão aa|bb

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

Aplicando a operação de concatenação sobre as Figura 38 e 39, obtém-se o


AFε resultante da expressão regular a*(aa|bb), conforme a Figura 40.
Figura 40 - AFεε da ER a*(aa|bb)

5 6 7 8
^ a ^ a ^
^

3 1 2 4 13 14
^ a ^ ^

^ ^
^ 9 10 11 12
b ^ b
41

O estado inicial e o estado final do AFε da Figura 40 são, respectivamente, o estado 3


e o estado 14. O estado final do AFε será sempre o último estado acrescentado ao grafo. O
estado inicial será sempre o estado inicial da primeira operação da expressão regular que está
sendo transformada em AFε. No exemplo da Figura 40 o estado inicial é o estado 3, pois
estado é o estado inicial da primeira operação da ER a*(aa|bb). A primeira operação é a*.
Todos os estados do AFε, gerados a partir da expressão regular, são numerados em ordem
crescente.

A próxima etapa do algoritmo descrito por [HOP1979] é a construção de um AFND


equivalente a partir do AFε da etapa anterior. Isto é realizado aplicando-se a equivalência
entre o autômato finito com movimentos vazios (AFε) e o autômato finito não determinístico
(AFND). A Figura 41 mostra o AFND resultante da para a expressão regular a*(aa|bb). Na
Figura 41 é possível observar como o AFND gerado a partir de um AFε torna-se complexo.
Isto acaba por influenciar diretamente no desempenho do algoritmo. A Figura 42 mostra o
AFND na forma de uma tabela para simplificar o entendimento da Figura 41. Esta tabela
poderá ser vista mais adiante no protótipo que implementa o algoritmo descrito por
[HOP1979].

Terminada a construção do AFND, a etapa seguinte é a construção, a partir do AFND,


de um autômato finito determinístico (AFD) equivalente. Da mesma forma como na etapa
anterior, aplica-se a equivalência entre autômatos para se obter o AFD da Figura 44. Isto é
realizado através de equivalência entre o AFND e o AFD. As Figuras 43 e 44 mostram,
respectivamente, a Tabela de Transições e o AFD resultante. O estado inicial do AFD
resultante é o estado 1 e o conjunto de estados finais do AFD é formado pelos estados 4 e 5.
42

Figura 41 - AFND da ER a*(aa|bb) pelo algoritmo descrito por [HOP1979]

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

Figura 42 - AFND da Figura 41 na forma de tabela


estado de saída símbolo do ∑ estado de estado de saída símbolo do ∑ estado de
chegada chegada
1 a 2 3 a 7
1 a 1 3 b 10
1 a 4 3 b 11
1 a 13 4 a 6
1 a 9 4 a 7
1 a 5 4 b 10
2 a 2 4 b 11
2 a 1 5 a 6
2 a 4 5 a 7
2 a 13 6 a 8
2 a 9 6 a 14
2 a 5 7 a 8
2 a 6 7 a 14
2 a 7 9 b 10
2 b 10 9 b 11
2 b 11 10 b 12
3 a 2 10 b 14
3 a 1 11 b 12
3 a 4 11 b 14
3 a 13 13 a 6
3 a 9 13 a 7
3 a 5 13 b 10
3 a 6 13 b 11
43

A partir do AFND mostrado na Figura 41 gera-se a tabela de transições vista na


Figura 43. Esta tabela é obtida aplicando a equivalência entre AFD e AFND. Os passos para
construção de um AFD a partir de uma AFND são basicamente aqueles descritos na Figura
32 que apresenta a construção de um AFD a partir de um GT. Um GT é a representação
gráfica de um autômato finito, podendo ser determinístico ou não determinístico. Neste caso,
o AFND da Figura 41 é um GT não determinístico. Desta forma aplicando estes passos
obtêm-se o AFD da Figura 44. Para simplificar o entendimento, os números entre parênteses
na Figura 43 indicam os estados do AFD.
Figura 43 - Tabela de transições do AFD da ER a*(aa|bb)

Mj
ESTADOS DO AUTÔMATO
Mi
FINITO
a b

1 (Inicial) {3} {1,2,4,5,6,7,9,13}(2) {10,11}(3)

2 {1,2,4,5,6,7,9,13} {1,2,4,5,6,7,8,9,13,14}(4) {10,11}(3)

3 {10,11} {} {12,14}(5)

4 (Final) {1,2,4,5,6,7,8,9,13,14} {1,2,4,5,6,7,8,9,13,14}(4) {10,11}(3)

5 (Final) {12,14} {} {}

Figura 44 - AFD resultante da ER a*(aa|bb)

b b
b
1 2 3 4 5
a b
a a

3.1.3 ALGORITMO DESCRITO POR [SIL2000]


Um outro algoritmo que utiliza como base o Teorema de Kleene para transformar
expressões regulares em autômatos finitos determinísticos é o algoritmo descrito por
44

[SIL2000]. Uma aplicação deste algoritmo é apresentada em [AMB2000]. A Figura 45


apresenta as etapas do algoritmo de [SIL2000] para realizar a transformação de uma
expressão regular (ER) em um autômato finito determinístico (AFD).
Figura 45 - Etapas do algoritmo descrito por [SIL2000].

EXPRESSÃO
EXPRESSÃO TABELA DO GRAFO DE TABELA DE
REGULAR AFD
PÓS-FIXADA DESMONTE TRANSIÇÕES TRANSIÇÕES
IN-FIXADA

Dada uma expressão regular qualquer, a primeira etapa do algoritmo é a obtenção da


expressão pós-fixada. Para obter a expressão pós-fixada a partir de uma expressão regular,
[SIL2000] apresenta uma especificação em linguagem livre de contexto (LLC). A Figura 46
apresenta esta definição em LLC.
Figura 46 - LLC para gerar expressão pós-fixada pelo algoritmo descrito por [SIL2000]
^ → simbolo vazio
R → expressão regular
SR → simples expressão regular
RR → resto da expressão regular

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 '*'}

Tomando como exemplo a ER a(b|c)*|ca*|^, o resultado da aplicação da LLC da


Figura 46 será conforme é mostrado na Figura 47. Convencionou-se a utilização do símbolo
& para representar o operador de concatenação para simplificar o processo de entrada de
dados no protótipo que irá implementar o algoritmo descrito por [SIL2000].
45

Figura 47 - ER a(b|c)*|ca*|^ para sua forma pós-fixada por aplicação da LLC

a.(b|c)*|c.a*|^ EXPRESSÃO REGULAR

abc|*.ca*.|^| EXPRESSÃO PÓS-FIXADA

A etapa seguinte do algoritmo descrito por [SIL2000] é a construção da “Tabela do


Desmonte”, a partir da expressão pós-fixada da etapa anterior. A Figura 49 apresenta a
“Tabela do Desmonte". Para a construção desta tabela, realizam-se os passos descritos na
Figura 48.
Figura 48 - Passos para construção da "Tabela do Desmonte"
A construção da “Tabela do Desmonte” consiste dos seguintes passos:
a) determinação do número de estados do grafo. Para a obtenção do número de
estados do grafo conta-se um estado inicial, um estado final e o número de
ocorrências dos operadores de concatenação e fechamento. O exemplo da Figura
47 apresenta 6 estados;
b) a tabela da Figura 49 é iniciada marcando-se na primeira coluna, no campo
estado inicial, o estado inicial e na coluna próximo estado, o estado final;
c) logo após monta-se a tabela, começando a leitura da expressão pós-fixada de trás
para frente. Na expressão da Figura 47, o primeiro elemento a ser lido é o
símbolo "|". Em seguida os símbolos "^", "|", ".", "*", "a", "c", ".", "*", "|", "c",
"b" e por final o símbolo "a". Estes símbolos são acrescentados na tabela do
"Desmonte", na coluna símbolo, pesquisando do final para o início da tabela;
d) para cada operador de concatenação, união e "*" acrescentam-se novas linhas na
tabela:
- para o operador de união acrescentam-se duas novas linhas, sendo que o
estado de saída destas linhas é o estado de saída do último símbolo
acrescentado e o próximo estado de cada linha é o próximo estado do último
símbolo acrescentado;
46

- para o operador de concatenação acrescentam-se duas novas linhas. Na


primeira linha o estado de saída é o estado de saída do último símbolo
acrescentado e o próximo estado é o próximo estado disponível no grafo. Na
segunda linha, o estado de saída é o próximo estado da linha anterior incluída
e o próximo estado é o próximo estado do último símbolo acrescentado. A
coluna símbolo das linhas acrescentadas é deixada livre;
- para o símbolo "*" acrescenta-se três novas linhas. Na primeira linha, o estado
de saída é o estado de saída do último símbolo acrescentado e o próximo
estado é o próximo estado disponível no grafo. A segunda linha, o estado de
saída é o próximo estado da linha anterior incluída na tabela e o próximo
estado é o próximo estado do último símbolo acrescentado. A coluna símbolo
destas duas linhas é preenchida com o símbolo vazio (^). Na terceira linha, o
estado de saída e o próximo estado são o próximo estado da primeira linha
incluída nesta fase. A coluna símbolo é deixada livre.

Figura 49 – “Tabela do Desmonte”

estado de saída símbolo próximo estado


Linhas
Ilustrativas

Aplicando os passos para construção da “Tabela do Desmonte" sobre a expressão pós-


fixada da Figura 47 obtém-se uma tabela conforme mostra a Figura 50.
47

Figura 50 – “Tabela do Desmonte" da expressão pós-fixada da Figura 47


estado de saída símbolo próximo estado

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

Terminada a construção da “Tabela do Desmonte" a etapa seguinte do algoritmo


descrito por [SIL2000] é a obtenção do grafo de transições (GT) a partir da “Tabela do
Desmonte". Isto é realizado através dos seguintes passos:
a) constrói-se uma nova tabela no qual estão eliminadas todas as linhas que
apresentarem os símbolos "*", "|" e "&" na coluna símbolo da "Tabela do
Desmonte", conforme apresenta a Figura 51;
b) o estado inicial do GT é sempre o estado de saída da primeira linha da "Tabela do
Desmonte". Da mesma forma, o estado final do GT é sempre o próximo estado da
48

primeira linha da "Tabela do Desmonte". Assim, no exemplo da expressão da


Figura 47, o GT tem como estado inicial o estado 1 e estado final o estado 6.
Figura 51 - Tabela construída a partir da "Tabela do Desmonte" para obtenção do GT
Estado de saída símbolo próximo estado

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

A partir da tabela da Figura 51 constrói-se o GT da Figura 52.


Figura 52 - GT resultante da tabela da Figura 51

^
1 6

a
^
c

2 3
^
^
a

4 5
^
c

A etapa seguinte do algoritmo descrito por [SIL2000] é a construção da tabela de


transições a partir do GT. A construção da tabela de transições a partir do GT obedece aos
49

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

1 (-/+) {1,6} {4,5,6} {} {2,3,6}

2 (+) {4,5,6} {} {5,6} {5,6}

3 (+) {2,3,6} {1,3,7} {} {}

4 (+) {5,6} {} {5,6} {5,6}

5 (+) {3,6} {4,5} {} {}

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

3.1.4 TRANSFORMAÇÃO DE UM AFD PARA UM PROGRAMA


DE COMPUTADOR
Segundo [NUN1998] existem duas formas de implementação de um AFD em um
programa de computador:
50

a) implementação por código direto;


b) implementação por controle de tabela de transições.

3.1.4.1 IMPLEMENTAÇÃO POR CÓDIGO DIRETO


Na implementação por código direto, o AFD será traduzido diretamente para a
linguagem de programação que se está utilizando. A Figura 55 mostra uma implementação,
na linguagem de programação Pascal, por código direto do AFD da Figura 54. Este tipo de
implementação é mais eficiente, mas para qualquer alteração do AFD é necessária a alteração
do programa. Conforme mostra a Figura 55, cada estado do AFD é implementado
diretamente no programa. Cada transição do AFD é representada de forma direta pelas linhas
de código do programa.
Figura 55 - Código direto em Pascal do AFD da Figura 54
10: { TCC - RONALD GLATZ }
20: unit TCC;
30:
40: interface
50:
60: function ValidaER(Cadeia : string) : boolean;
70:
80: implementation
90:
100: function ValidaER(Cadeia : string) : boolean;
110: { Estados do AFD }
120: label S0,S1,S2,S3,S4,S5,S6,S7,S8;
130: var
140: I : integer;
150: begin
160: S0: I := 0;
170: S1: inc(I);
180: if length(Cadeia) < I then
190: goto S6;
200: if Cadeia[I] = 'a' then
210: goto S2;
220: if Cadeia[I] = 'c' then
230: goto S3;
240: goto S7;
250: S2: inc(I);
260: if length(Cadeia) < I then
270: goto S6;
280: if Cadeia[I] = 'b' then
290: goto S4;
300: if Cadeia[I] = 'c' then
310: goto S4;
320: goto S7;
330: S3: inc(I);
340: if length(Cadeia) < I then
350: goto S6;
360: if Cadeia[I] = 'a' then
370: goto S5;
380: goto S7;
390: S4: inc(I);
51

400: if length(Cadeia) < I then


410: goto S6;
420: if Cadeia[I] = 'b' then
430: goto S4;
440: if Cadeia[I] = 'c' then
450: goto S4;
460: goto S7;
470: S5: inc(I);
480: if length(Cadeia) < I then
490: goto S6;
500: if Cadeia[I] = 'a' then
510: goto S5;
520: goto S7;
530: S6: ValidaER := true;
540: goto S8;
550: S7: ValidaER := false;
560: S8: ;
570: end;
580: end.

3.1.4.1.1 A RELAÇÃO ENTRE O CÓDIGO DO PROGRAMA E O AFD


O programa da Figura 55 foi gerado através de um dos protótipos do presente
trabalho. Este programa está codificado na linguagem de programação Pascal, o qual é uma
biblioteca de funções (unit), onde existe apenas uma função, implementando o AFD da
Figura 54, que irá validar cadeias retornando verdadeiro, se a cadeia em questão for válida
para o AFD, e falso, caso contrário. Esta estratégia de codificação do AFD na função da
linguagem de programação Pascal, utilizando o recurso de labels, foi adotada no sentido de
simplificar ao máximo o código e o entendimento deste em relação ao AFD. Outras formas
diretas oferecidas pela linguagem poderiam gerar uma função mais complexa e menos clara
ou eficiente, além de não propiciar melhoras significativas ou eliminação de limitações
(considerando que elas possam existir).

O fato do programa da Figura 55 ser uma unit da linguagem de programação Pascal


determina que, se esta for compilada não haverá geração de código executável para ser
executado diretamente no sistema operacional. Existirá a necessidade de um segundo
programa que declare esta unit em seu código e utilize a função ValidaER. Um exemplo de
programa é apresentado na Figura 56. Este programa compilado permitirá ao usuário
informar cadeias e verificar se estas são aceitas ou não pelo AFD da Figura 54. A linha 30 do
programa indica a utilização da unit da Figura 55 e na linha 120 a utilização da função
ValidaER.
52

Figura 56 - Programa que declara a unit da função ValidaER.


10: program TCC_FIM;
20:
30: uses CRT, TCC;
40:
50: var
60: Cadeia : string;
70:
80: begin
90: clrscr;
100: write('Informe a Cadeia a ser avaliada : ');
110: readln(Cadeia);
120: if ValidaER(Cadeia) then
130: writeln('Cadeia Aceita !')
140: else
150: writeln('Cadeia Rejeitada !');
160: end.

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.

As linhas de 170 a 240 na Figura 55 compreendem ao estado 1 do AFD da Figura 54.


As linhas 250 a 320, de 330 a 380, de 390 a 460, de 470 a 520 compreendem,
respectivamente, aos estados 2, 3, 4 e 5 do AFD da Figura 54.

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.

3.1.4.2 IMPLEMENTAÇÃO POR CONTROLE DE TABELA DE


TRANSIÇÕES
Neste método não há mudança de programa. Apenas do conteúdo da tabela de
transições que o programa interpretador irá ler. Devido a esta característica, este método é
mais geral do que o método da implementação direta. Implementando uma forma para entrar
com a tabela de transições, qualquer AFD poderá ser processado sem a necessidade de
alterações no programa interpretador. A tabela de transições poderá, por exemplo, ser
armazenada em arquivo e este será acessado pelo programa interpretador que avaliará a cadeia
do usuário conforme a tabela de transições armazenada neste arquivo. O Anexo 2 apresenta
um programa que implementa o método por controle de tabela de transições. Este programa lê
a tabela de transições gerada pelos protótipos que implementam os algoritmos de [HOP1979]
e [SIL2000].

No desenvolvimento dos protótipos que implementam os algoritmos descritos por


[HOP1979] e [SIL2000] a função na linguagem de programação Pascal será gerada por
código direto.
54

4 ESPECIFICAÇÃO DOS PROTÓTIPOS


Este capítulo apresenta um projeto para cada um dos protótipos que irão implementar
os algoritmos descritos por [HOP1979] e [SIL2000]. Cada protótipo terá um projeto na forma
estruturada utilizando um enfoque top-down do problema. Os projetos serão representados por
fluxogramas e descrições textuais das partes de cada protótipo. Para cada projeto serão
seguidas as seguintes etapas:
a) visão geral do protótipo;
b) separação das estruturas de dados utilizadas;
c) separação dos procedimentos que compõe o protótipo;
d) descrição de cada procedimento e sua relação com as estruturas de dados.

Com base nestas etapas, serão apresentados projetos distintos dos protótipos que
implementam os algoritmos descritos por [HOP1979] e [SIL2000].

4.1 PROJETO DE PROGRAMA


Dada a necessidade de produzir programas com qualidade e que resolvam problemas
de forma correta, retornando os resultados corretos, a importância da etapa de projeto de
programa não pode deixar de ser enfatizada. É improvável que um programa adequado,
consistente e eficaz seja produzido sem a fase de projeto deste programa ou então se esta fase
de projeto tenha sido mal conduzida e estruturada.

Existem vários métodos para projetar programas de forma lógica antes do


desenvolvimento do código deste programa. Cada método é aplicado de acordo com o
problema em questão objetivando sempre uma gerência clara e objetiva do problema. Ainda,
poderá haver uma combinação de métodos de projeto a fim de obter resultados ainda
melhores. Os métodos mais amplamente utilizados para especificação de programas e
sistemas baseiam-se no enfoque top-down. De acordo com este enfoque um programa é
considerado inicialmente como um todo e então subdivido hierarquicamente em componentes
cada vez mais simples, em níveis mais baixos, até se atingir um nível onde, por razões
práticas, não seja conveniente continuar a divisão, tendo-se então definidas unidades
discretas, compreensíveis e controláveis.

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.

4.2 PROTÓTIPO DO ALGORITMO DESCRITO POR


[HOP1979]
Esta seção apresenta a especificação do protótipo que implementa o algoritmo descrito
por [HOP1979].

4.2.1 VISÃO GERAL DO PROTÓTIPO


A Figura 57 apresenta uma visão geral do protótipo que implementa a transformação
de uma expressão regular para uma função na linguagem de programação Pascal, utilizando
como base o algoritmo descrito por [HOP1979]. A Figura 58 apresenta um maior
detalhamento das partes observadas na Figura 57. A partir da separação das fases que irão se
suceder no protótipo é possível delinear procedimentos e estruturas de dados.
Figura 57 - Visão geral do protótipo

ENTRADA DE DADOS

APLICAÇÃO DO ALGORITMO DE [HOP1979]

GERAÇÃO DA FUNÇÃO PASCAL


56

Figura 58 - Procedimentos do protótipo

Entrada do Alfabeto e da FASE 1


ER

Geração do AFε FASE 2

Geração do AFND FASE 3

Geração do AFD FASE 4

Função Pascal FASE 5

4.2.2 ESTRUTURAS DE DADOS


Tendo como base a Figuras 58 e a teoria que trata do algoritmo descrito por
[HOP1979] no capítulo 3, é possível realizar a descrição do conjunto de estruturas de dados
utilizadas no protótipo.

A fase 1 na Figura 58 corresponde à entrada de dados do usuário. Nesta fase o usuário


irá fornecer ao protótipo o alfabeto e a expressão regular. Tanto o alfabeto como a expressão
regular serão do tipo cadeia ou string.

A fase 2 corresponde à geração do autômato finito não determinístico com


movimentos vazios (AFε). O AFε resultante será armazenado numa tabela que poderá estar
em disco ou na memória. No caso do protótipo deste trabalho será utilizada uma estrutura de
dados em memória, alocada dinamicamente, do tipo registro conforme mostra a Figura 59.
57

Figura 59 - Estrutura de dados do AFεε


Registro_Afe = ^Reg_Afe;
Reg_AFe = record
EstadoSaida,
EstadoChegada : integer;
Simbolo : char;
Prox_Trans : Registro_AFe;
Processado : boolean;
end;

Para a fase três é utilizada uma estrutura de dados idêntica a da fase 2.

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

Figura 61 - Tabela de transições do AFD

APONTADOR
COLUNA Mi
APONTADOR

APONTADOR

COLUNA Mj

A fase 5 do protótipo é a geração da função na linguagem de programação. A função


será gerada para um arquivo texto em disco. O usuário poderá escolher o nome do arquivo e
sua localização (pasta ou diretório). Este procedimento também é idêntico no protótipo que
implementa o algoritmo descrito por [SIL2000].

4.2.3 PROCEDIMENTOS DO PROTÓTIPO


O protótipo é dividido em cinco fases distintas (cada qual podendo ser representada
por um procedimento) conforme apresenta a Figura 58. Para cada procedimento haverá
regras conforme:
a) entrada do alfabeto e da expressão regular: nesta fase o protótipo solicitará ao
usuário a entrada do alfabeto e da expressão regular. O alfabeto poderá conter
qualquer símbolo que não seja um operador (concatenação, fechamento ou união),
parênteses ou a palavra vazia (^). A expressão regular deverá apenas apresentar
símbolos informados no alfabeto, operadores válidos, parênteses e o símbolo vazio
(^). Para os operadores de concatenação, fechamento e união serão utilizados os
59

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

Figura 64 - Procedimento do conjunto de estados finais do AFND

AFε da Etapa
Anterior

Enquanto houver estados para


leitura
Ler um estado do conjunto de
estados do AFε

Para o estado lido, formar


conjunto de todos os estados
atingidos a partir do estado lido
com a palavra vazia (^). O estado
lido também faz parte do conjunto

Algum dos estados do IGNORAR ESTADO LIDO.


NÃO FAZ PARTE DO
conjunto formado faz parte do
CONJUNTO DE
conjunto de estado finais do ESTADOS FINAIS DO
AFε? não AFND

sim

ARMAZENAR ESTADO
LIDO NO CONJUNTO DE
ESTADOS FINAIS DO
AFND
62

Figura 65 - Fases da geração do AFND


FASE 1 FASE 2 FASE 3

INICIO AFND

CONJUNTO DE ESTADOS DO CONJUNTO DE ESTADOS DO TABELA (ESTADO SAIDA,


AFND AFND CHEGADA E SÍMBOLO) DA
FASE 2

ENQUANTO HOUVER ESTADO ENQUANTO HOUVER ESTADO


A PROCESSAR A PROCESSAR PARA CADA ESTADO DO AFND
PEGAR ESTADO DO CONJUNTO PEGAR ESTADO DO CONJUNTO CRIAR UM CONJUNTO
DE ESTADOS DO AFND DE ESTADOS DO AFND FORMADO PELO ESTADO DE
SAÍDA E PELOS ESTADOS
ATINGIDOS A PARTIR DO
ESTADO DE CHEGADA COM O
SÍMBOLO ^.

PARA ESTE ESTADO


FORMAR CONJUNTO DE
PROCESSAR TODOS OS
ESTADOS ATINGIDOS COM ^.
ESTADOS DO CONJUNTO
ACRESCENTAR O ESTADO DO
FORMADO NA FASE 1 COM OS
AFND AO CONJUNTO SE O ESTADO ATINGIDO PELO
SÍMBOLOS DO ALFABETO
SÍMBOLO ^ ATINGIR OUTROS
COM O SÍMBOLO ^, ESTES
OUTROS ATINGIDOS TAMBÉM
FARÃO PARTE DO CONJUNTO.
O CICLO TERMINARÁ QUANDO
NÃO HOUVER MAIS ESTADO
ATINGIDO POR ^
PROCESSAR ESTADO COM A ARMAZENAR TODOS OS
PALAVRA VAZIA (^) ESTADOS ATINGIDOS PELOS
SIMBOLOS NUMA TABELA
(ESTADO SAIDA, CHEGADA,
SÍMBOLO)

FIM AFND

ESTADO ATINGIU OUTRO


COM ^
não

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.

4.3 PROTÓTIPO DO ALGORITMO DESCRITO POR


[SIL2000]
Esta seção apresenta os passos para construção do protótipo que implementa o
algoritmo descrito por [SIL2000].

4.3.1 VISÃO GERAL DO PROTÓTIPO


A Figura 66 apresenta uma visão geral do protótipo que implementa o algoritmo
descrito por [SIL2000]. A Figura 67 apresenta as 6 fases que compõe o escopo do protótipo.
Figura 66 - Visão geral do protótipo

ENTRADA DE DADOS

APLICAÇÃO DO ALGORITMO DE [SIL2000]

GERAÇÃO DA FUNÇÃO PASCAL


64

Figura 67 - Fases que compõe o protótipo

Entrada do Alfabeto e da ER
FASE 1

Geração da expressão pós-fixada através da LLC


FASE 2

Geração da "Tabela do Desmonte"


FASE 3

Geração do GT
FASE 4

Geração do AFD
FASE 5

Função Pascal FASE 6

4.3.2 ESTRUTURAS DE DADOS


Com base nas fases apresentadas na Figura 67 é possível fazer um levantamento das
estruturas de dados necessários para a implementação do protótipo. A fase 1 deste protótipo
apresenta as mesmas estruturas de dados utilizadas na fase 1 do protótipo que implementa o
algoritmo descrito por [HOP1979] (item 4.2.2).

A fase 2 do protótipo consiste na geração da expressão pós-fixada de acordo com a


especificação em LLC apresentada no capítulo 3, na Figura 46. A expressão pós-fixada será
armazenada numa cadeia ou string.
65

Na fase 3 acontece a geração da "Tabela do Desmonte" a partir da expressão pós-


fixada gerada na fase 2. A expressão será lida da direita para esquerda, símbolo a símbolo, e
cada símbolo será processado de acordo com o procedimento apresentado no capitulo 3, na
Figura 48. A estrutura de dados utilizada para armazenar a "Tabela do Desmonte" é uma
tabela, onde cada registro é da forma como apresenta a Figura 68. Este registro poderá ser
alocado dinamicamente em memória ou utilizado através de um array. O conteúdo da tabela
poderá ser descartado logo após o final da fase 4.
Figura 68 - Registro da "Tabela do Desmonte"
ArestaTabelaDesmonte = record
NoSaida,
ProximoNo : integer;
Simbolo : char;
end;

A fase 4 do protótipo será responsável pela geração do GT a partir da "Tabela do


Desmonte" obtida na fase 3. O GT também será armazenado numa tabela cujo registro, para
cada transição do GT, será da forma como apresentado na Figura 69. Este registro poderá ser
alocado dinamicamente em memória ou utilizado via um array, conforme a "Tabela do
Desmonte" da fase 2. No caso da Figura 69, o registro apresentado será alocado
dinamicamente em memória.
Figura 69 - Registro das transições do GT
Registro_GT = ^Reg_GT;
Reg_GT = record
EstadoSaida,
EstadoChegada : integer;
Simbolo : char;
Prox_Trans : Registro_GT;
Processado : boolean;
end;

Para a fase 5 do protótipo, que é responsável pela geração da tabela de transições do


AFD, a estrutura de dados é idêntica a estrutura apresentada na fase 4 (Figuras 60 e 61) do
protótipo que implementa o algoritmo descrito por [HOP1979].

Da mesma forma como a fase 5, a fase 6 é idêntica a fase 5 do protótipo que


implementa o algoritmo descrito por [HOP1979] (item 4.2.2).

4.3.3 PROCEDIMENTOS DO PROTÓTIPO


Este protótipo apresenta 6 fases que podem ser representadas na forma de
procedimentos. Alguns destes procedimentos são análogos aos utilizados no protótipo que
66

implementa o algoritmo descrito por [HOP1979]. Os procedimentos apresentam as seguintes


regras:
a) entrada do alfabeto e da expressão regular: idêntica a fase 1 do protótipo que
implementa o algoritmo descrito por [HOP1979] (item 4.2.3);
b) geração da expressão pós-fixada através da LLC: através da especificação em
linguagem livre de contexto apresentada no capítulo 3 (Figura 46) será gerada uma
expressão pós-fixada a partir da expressão regular. A expressão é lida símbolo a
símbolo, através de um procedimento de leitura descrito no item 4.4, e cada
símbolo é submetido à especificação em LLC. Ao final do processamento da
especificação em LLC estará formada a expressão pós-fixada. A Figura 47
apresenta uma expressão regular e sua expressão pós-fixada após ter sido
submetida à especificação em LLC;
c) geração da "Tabela do Desmonte": esta tabela é gerada a partir da expressão
pós-fixada obtida na fase 2 do protótipo. A geração obedece aos passos
apresentados no capítulo 3 (Figura 48). A expressão pós-fixada é lida, símbolo a
símbolo, da direta para esquerda e cada símbolo submetido aos passos do Figura
48;
d) geração do GT: este procedimento é simples. Consiste na eliminação das linhas da
"Tabela do Desmonte" que contenham os operadores de concatenação, fechamento
e união conforme visto no capítulo 3. Esta eliminação gera o GT;
e) geração do AFD: procedimento idêntico ao do realizado no protótipo que
implementa o algoritmo descrito por [HOP1979]. A diferença esta apenas na forma
de designar a entrada, que no caso do protótipo que implementa o algoritmo
descrito por [HOP1979] é um AFND, e neste caso é um GT (que não deixa de ser
um autômato finito, determinístico ou não);
f) geração da função equivalente na linguagem de programação Pascal: este
procedimento também é idêntico ao aplicado no protótipo que implementa o
algoritmo descrito por [HOP1979].

4.4 PROCEDIMENTO DE LEITURA SÍMBOLO A SÍMBOLO


Este procedimento é idêntico para os dois protótipos. É um procedimento simples de
leitura da expressão regular, símbolo a símbolo, e cada símbolo é passado para o
procedimento solicitante. A principal característica do procedimento é permitir que o usuário
67

dê entrada a uma expressão regular omitindo todos os operadores de concatenação. Este


operador é acrescentado automaticamente durante o processamento da expressão regular. O
símbolo utilizado é "&".

4.5 PARTICULARIDADES NO DESENVOLVIMENTO DOS


PROTÓTIPOS
Durante a fase de projeto dos protótipos foi observado que principalmente no protótipo
que implementa o algoritmo descrito por [HOP1979] ocorre uma geração elevada de
transições dos autômatos. Inicialmente optou-se em utilizar o tipo set (conjunto) da linguagem
de programação Pascal para implementar os conjuntos de estados dos diversos autômatos.
Durante a fase de programação viu-se que isto iria gerar uma limitação muito séria aos
protótipos, particularmente do protótipo que implementa o algoritmo descrito por [HOP1979].
O tipo set da linguagem de programação Pascal é limitado e não suporta a quantidade de
estados dos conjuntos de estados dos diversos autômatos. Além disto, o protótipo processava
completamente sem acusar qualquer erro ou estouro de capacidade quando excedia a
capacidade de uma variável do tipo set. Para sanar esta questão, foi utilizado um tipo object
conforme apresenta a Figura 70. Esta estrutura de dados permite aos protótipos trabalhar com
conjuntos formados por números inteiros. Alguns procedimentos básicos para permitir adição
de elementos, inicialização do conjunto e pertinência de estados no conjunto foram
implementados nesta estrutura de dados a fim de permitir as manipulações dos estados do
conjunto. A estrutura de dados tipo object é significativamente mais importante para
construções no protótipo que implementa o algoritmo descrito por [HOP1979]. No protótipo
que implementa o algoritmo descrito por [SIL2000] esta importância não é tão acentuado
visto que os conjuntos gerados são significativamente menores. Esta característica é de
fundamental importância mais adiante no capítulo que trata da avaliação de complexidade dos
algoritmos de [HOP1979] e [SIL2000], pois permite afirmar que o algoritmo descrito por
[SIL2000] é mais eficiente e menos complexo (no aspecto de implementação de conjuntos) do
que o algoritmo descrito por [HOP1979].
68

Figura 70 – Estrutura tipo object para representação de conjuntos


Elementos = ^Reg_Elem;
Reg_Elem = record
dado : integer;
prox : Elementos;
end;

Cjto = object
Prim,
Ult,
Valor : Elementos;
Tam : integer;
procedure Inicia;
procedure Limpa;
procedure Adiciona(D : integer);
function Verifica(D : integer) : boolean;
procedure Lista;
end;

Os métodos Inicia, Limpa, Adiciona, Verifica e Lista do objeto da Figura 70 são


responsáveis respectivamente pela inicialização do conjunto, eliminação do conteúdo dos
conjuntos, adição de elementos ao conjunto, verificação de pertinência de um elemento e
listagem dos elementos do conjunto. Prim, Ult, Valor e Tam indicam respectivamente o inicio
do conjunto, fim do conjunto, registro do conjunto e tamanho do conjunto. A declaração de
uma variável para o tipo da Figura 70 é conforme mostra a Figura 71.
Figura 71 – Declaração do tipo Cjto

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

5 APRESENTAÇÃO E UTILIZAÇÃO DOS


PROTÓTIPOS
Neste capítulo serão apresentados os protótipos que implementam os algoritmos
descritos por [HOP1979] e [SIL2000]. Esta apresentação consiste na demonstração das
funcionalidades de cada protótipo. Primeiramente serão apresentadas algumas características
válidas para ambos os protótipos. Logo após será feita uma explanação individual.

5.1 VISÃO GERAL DOS PROTÓTIPOS


Tanto o protótipo que implementa o algoritmo descrito por [HOP1979], como aquele
que implementa o algoritmo descrito por [SIL2000] foram desenvolvidos obedecendo aos
seguintes critérios:
a) ambiente de programação DELPHI versão 3.0. O ambiente de programação
DELPHI utiliza a linguagem Object Pascal para codificação de programas;
b) os protótipos foram compilados para execução nos sistemas operacionais Windows
95, 98 e Windows NT 4;
c) nome dos programas: hopcroft.exe (para o protótipo que implementa o algoritmo
descrito por [HOP1979]) e desmonte.exe (para o protótipo que implementa o
algoritmo descrito por [SIL2000]);
d) no ambiente gráfico do Windows, os ícones da Figura 73 identificam cada
protótipo;
e) a operação dos protótipos é similar.
Figura 73 - Ícones do protótipos
70

5.2 PROTÓTIPO QUE IMPLEMENTA O ALGORITMO


DESCRITO POR [HOP1979]
Este protótipo implementa o algoritmo descrito por [HOP1979]. Executa-se
hopcroft.exe através dos recursos do Windows. O programa poderá ser identificado através de
seu ícone conforme mostra a Figura 73. O item 5.5 deste capítulo mostra a localização do
protótipo no CD que acompanha o presente trabalho.

5.2.1 TELA PRINCIPAL DO PROTÓTIPO QUE IMPLEMENTA O


ALGORITMO DESCRITO POR [HOP1979]
A Figura 74 apresenta a tela inicial e principal deste protótipo.
Figura 74 - Tela principal de hopcroft.exe

5.2.1.1 ENTRADA DO ALFABETO E DA EXPRESSÃO REGULAR


A Figura 75 mostra os campos onde o usuário irá informar o alfabeto e a expressão
regular. Para informar o alfabeto e a expressão regular, o usuário precisa apenas preencher os
campos Alfabeto e Expressão Regular indicados na Figura 75.
71

O alfabeto permitirá qualquer símbolo, excetuando os operadores (união, concatenação


e fechamento), os parênteses e o símbolo vazio (^). A expressão regular poderá ter
operadores, parênteses, símbolos vazios (^) e símbolos do alfabeto. Este procedimento é
idêntico para o protótipo que implementa o algoritmo descrito por [SIL2000].
Figura 75 - Entrada do alfabeto e da expressão regular

5.2.1.2 PROCESSAMENTO DA EXPRESSÃO REGULAR


Após a entrada de dados, o próximo passo é o processamento da expressão regular.
Isto é realizado através de um clique simples do mouse sobre o botão "Processa Expressão
Regular" ou através da seqüência de teclado "Alt-P". A Figura 77 apresenta este
procedimento.
72

Figura 76 - Processamento da expressão regular

5.2.1.3 RESULTADOS DO PROCESSAMENTO


A Figura 77 apresenta os resultados do processamento da expressão regular informada
pelo usuário. Os resultados que podem ser observados são:
a) o usuário poderá ver a função Pascal gerada a partir da expressão regular;
b) o usuário poderá gravar o AFD;
c) o usuário poderá ver o AFε, o AFND e o AFD gerados durante o processamento;
d) para o AFND e o AFD o usuário terá a opção de ver os detalhes do processamento;
e) estados iniciais e finais dos autômatos.

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

Figura 77 - Resultados do processamento

5.2.1.3.1 VISUALIZAR A FUNÇÃO PASCAL GERADA


Para visualizar a função Pascal gerada pelo processamento da expressão regular o
usuário deverá dar um clique simples do mouse sobre o botão Visualizar Função Pascal
conforme mostra a Figura 78. A Figura 79 apresenta a tela que mostra a função Pascal
gerada a partir da expressão regular informada pelo usuário. Nesta tela o usuário tem a
possibilidade de salvar a função Pascal em um arquivo e numerar as linhas de código da
função. Quando as linhas estiverem numeradas a função será salva em formato de texto com
extensão txt. Desta forma a função não poderá ser compilada por um compilador da
linguagem de programação Pascal. Se as linhas não estiverem numeradas, a função será salva
com a extensão .pas da linguagem de programação Pascal. Esta operação é idêntica para o
protótipo que implementa o algoritmo descrito por [SIL2000].
74

Figura 78 - Visualizar a função Pascal

Figura 79 - Função Pascal


75

5.2.1.3.2 SALVAR O AFD


A opção Salvar AFD está disponível na tela principal do protótipo conforme mostra a
Figura 78. Esta opção salva o AFD resultante em um arquivo cujo registro (tipo da
linguagem de programação Pascal) é conforme mostra a Figura 80. Esta opção é útil para
permitir a demonstração da equivalência de Moore (Anexo 1) e do interpretador universal de
autômatos (Anexo 2). O usuário poderá informar o nome do arquivo para salvar o AFD. A
extensão padrão que o protótipo utiliza é .afd e esta é colocada automaticamente se o usuário
não informar outra. Este procedimento é idêntico ao do protótipo que implementa o algoritmo
descrito por [SIL2000].
Figura 80 - Registro do arquivo para salvamento do AFD
AFD_Reg = record
Alg : string[10];
NoSaida : integer;
ProximoNo : integer;
Simb : char;
Final : boolean;
Expressao : string[MaxSimb];
Alfabeto : string[MaxSimb];
end;

Os campos que formam este registro são:


a) campo Alg: contém o nome do algoritmo que gerou o AFD. Este campo é utilizado
apenas para identificação;
b) campo NoSaida: este campo identifica um estado de saída no AFD;
c) campo ProximoNo: este campo identifica um estado de chegada no AFD;
d) campo Simb: identifica o símbolo de uma transição no AFD;
e) campo Expressão: identifica a ER que gerou o AFD;
f) campo Alfabeto: identifica o alfabeto da ER que gerou o AFD.

5.2.1.3.3 DETALHES DO PROCESSAMENTO


O usuário tem ainda a possibilidade de acompanhar algumas das etapas que sucedem
no processamento do AFND e do AFD. Para acessar os detalhes de processamento do AFND,
o usuário deverá dar um clique-duplo sobre a grade do AFND conforme indica a Figura 81.
A Figura 82 mostra a janela com as fases que se sucedem para obter o AFND a partir do
AFε.
76

Figura 81 - Acessar detalhes do processamento do AFND

Figura 82 - Acessar detalhes do processamento do AFND


77

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 (^).

5.2.1.3.3.1 VISUALIZANDO TABELA DE TRANSIÇÕES DO AFD


Além do AFND, é possível ver a tabela de transições do AFD. O usuário deverá dar
um clique-duplo sobre a grade do AFD, conforme indicado na Figura 83. A Figura 84
apresenta a tabela de transições do AFD. Este procedimento é válido também para o protótipo
que implementa o algoritmo descrito por [SIL2000].
Figura 83 - Acessar tabela de transições do AFD

O usuário poderá dimensionar as colunas da tabela de transições quando houver


necessidade. Este procedimento é realizado através do mouse conforme indicado na Figura
84.
78

Figura 84 - Tabela de transições

5.3 PROTÓTIPO QUE IMPLEMENTA O ALGORITMO


DESCRITO POR [SIL2000]
Este protótipo é similar em termos operacionais ao protótipo que implementa o
algoritmo descrito por [HOP1979]. Executa-se desmonte.exe através dos recursos do
Windows. O programa poderá ser identificado através de seu ícone conforme mostra a Figura
73. O item 5.5 deste capítulo mostra a localização do protótipo no CD que acompanha o
presente trabalho.

5.3.1 TELA PRINCIPAL DO PROTÓTIPO QUE IMPLEMENTA O


ALGORITMO DESCRITO POR [SIL2000]
A Figura 85 apresenta a tela principal do protótipo.
79

Figura 85 - Tela principal do protótipo que implementa [SIL2000]

5.3.1.1 ENTRADA DO ALFABETO E DA EXPRESSÃO REGULAR


O procedimento de entrada do alfabeto e da expressão regular é idêntico ao
procedimento adotado no protótipo que implementa o algoritmo descrito por [HOP1979]. Isto
poderá ser verificado no item 5.2.1.1 deste capítulo.

5.3.1.2 PROCESSAMENTO DA EXPRESSÃO REGULAR


Após a entrada de dados, o próximo passo é o processamento da expressão regular.
Isto é realizado através de um clique simples do mouse sobre o botão "Processar Expressão
Regular" ou através da seqüência de teclado "Alt-P".

5.3.1.3 RESULTADOS DO PROCESSAMENTO


A Figura 75 apresenta os resultados do processamento da expressão regular informada
pelo usuário. Os resultados que podem ser observados são:
a) o usuário poderá ver a função Pascal gerada a partir da expressão regular;
b) o usuário poderá gravar o AFD;
80

c) o usuário poderá ver a Tabela do Desmonte, o Grafo de Transições e AFD gerados


durante o processamento;
d) para o AFD o usuário terá acesso a tabela de transições (item 5.2.1.3.3.1);
e) estado inicial e final do AFD e GT;
f) expressão pós-fixada gerada pela especificação em LLC do algoritmo.

Estes procedimentos estão disponíveis no item 5.2.1.3 deste capítulo.

5.3.1.3.1 VISUALIZAR A FUNÇÃO PASCAL GERADA


Este procedimento é similar ao descrito no item 5.2.1.3.1 que trata da visualização da
função Pascal no protótipo que implementa o algoritmo descrito por [HOP1979].

5.3.1.3.2 SALVAR AFD


Este procedimento é idêntico ao descrito no item 5.2.1.3.1 do protótipo que
implementa o algoritmo descrito por [HOP1979].

5.3.1.3.3 EXPRESSÃO PÓS-FIXADA


O protótipo apresenta ao usuário a expressão pós-fixada gerada a partir da expressão
regular durante o processamento do algoritmo. A expressão poderá ser vista na Figura 85.

5.3.1.3.4 TABELA DE TRANSIÇÕES DO AFD


Este protótipo permite ao usuário visualizar a tabela de transições do AFD gerado. Este
procedimento é idêntico ao item 5.2.1.3.3.1.

5.4 INFORMAÇÕES GERAIS SOBRE OS PROTÓTIPOS


Tanto o protótipo que implementa o algoritmo descrito por [HOP1979], como também
aquele que implementa o algoritmo descrito por [SIL2000] apresentam, após o término do
processamento da expressão, o tempo de processamento da expressão. O usuário será
informado conforme mostra a Figura 86.

Além disto, os protótipos também informam a quantidade de transições (Figura 87)


que cada autômato atingiu. Isto poderá ser útil para verificar a utilização de memória de cada
algoritmo. O AFε da Figura 87 apresenta 28 transições.
81

Figura 86 – Tempo de execução do algoritmo

Figura 87 - Contagem de transições


82

6 COMPARAÇÃO DOS PROTÓTIPOS


Neste capítulo será apresentada uma comparação dos algoritmos descritos por
[HOP1979] e [SIL2000]. Através da utilização de algumas questões essenciais da
complexidade de algoritmos, este capítulo visa apresentar uma comparação dos algoritmos
implementados com o objetivo de identificar qual destes apresenta maior eficiência. Objetiva-
se também identificar características que beneficiam ou que comprometem a eficiência destes
algoritmos.

6.1 COMPLEXIDADE DE ALGORITMOS


Segundo [LUC1979] a preocupação com a eficiência de algoritmos é provavelmente
tão antiga quanto a própria noção de algoritmo. Apesar disto, um estudo sistemático deste
assunto é relativamente recente. O estudo da complexidade de algoritmos visa identificar
soluções mais eficientes para os algoritmos. Aspectos como a correção e a descrição de
programas que implementam algoritmos são importantes, pois é fundamental que os
programas estejam escritos de forma correta e que calculem corretamente as funções dos
algoritmos neles implementados. Apesar disto, não basta garantir que um algoritmo forneça as
respostas corretas em um número finito de passo. Para que um algoritmo possa ser útil, é
necessário que número de passos seja não somente "finito", mas "muito finito". Desta forma,
o objeto de estudo da complexidade de algoritmos é a identificação de um algoritmo que
possa resolver um determinado problema com a máxima eficiência possível. É importante
observar que, no contexto eficiência de um algoritmo está englobada não somente a
velocidade com que este algoritmo executa, mas também a quantidade de recursos de
computador que este utiliza para realizar a tarefa. Também não somente a quantidade de
recursos computacionais possa ser suficiente para a análise de um algoritmo quanto ao objeto
de estuda da Complexidade de Algoritmos. Existem situações onde há necessidade de se
estudar uma classe de algoritmos para que se possa alcançar um resultado adequado. E ainda,
existem casos em que se deseja estudar o algoritmo para verificar o quanto este ainda pode ser
melhorado, tornando necessário o estabelecimento de limites de recursos computacionais
para realizar o seu processamento. Todas estas são questões de fundamental importância no
estudo da complexidade de algoritmos.

Resumidamente, conforme apresentado por [NUN1998], os critérios vitais para


construção e julgamento de um programa de computador são:
83

a) o programa faz exatamente aquilo que sua especificação prevê?;


b) o programa executa corretamente, de acordo com as especificações originais da
tarefa?;
c) há uma documentação que descreva como usá-lo e como ele trabalha?;
d) as sub-rotinas são criadas de tal modo que realizem sub-funções lógicas?;
e) o código é legível?;
f) ele é executado em tempo ótimo de execução?;
f) ele usa espaço de memória mínimo necessário para sua execução?;

Quanto ao desempenho, um programa pode ser avaliado segundo os critérios:


a) tempo ótimo de execução;
b) quantidade mínima de memória que este utiliza para executar.

De uma forma geral, qualquer trabalho no contexto de estudo da complexidade de


algoritmos necessita de:
a) uma avaliação prévia do contexto do algoritmo: isto é importante pois podem
existir situações a serem otimizadas mesmo antes da implementação em um
programa de computador;
b) verificação e análise de outros algoritmos que implementam a mesma solução
para o problema do algoritmo em questão: um outro algoritmo pode,
eventualmente, ser mais eficiente do que este que se está utilizando;
c) estabelecimento de limites de processamento: para comparar o algoritmo com
outros que realizam a mesma tarefa e concluir qual deste é o mais ideal para a
solução do problema;
d) estimativas: a eficiência de execução de um algoritmo pode ser previamente
estimada com base nos recursos computacionais utilizados na implementação do
algoritmo em um programa de computador e do próprio computador que irá
executar o programa;
e) testes pós-implementação do algoritmo: os testes podem determinar novas
otimizações do algoritmo;
f) conclusões e reavaliações tanto do método de análise utilizado como também
do algoritmo: os resultados devem ser avaliados, pois tanto o método empregado
como também o algoritmo e os recursos computacionais utilizados podem não ser
adequados para a situação que se deseja resolver da forma mais eficiente possível.
84

Quanto aos recursos computacionais, a eficiência de um programa que implementa um


algoritmo para resolver um determinado problema pode ser previamente estimada com base
nos seguintes elementos:
a) capacidade de processamento da máquina;
b) conjunto de instruções da máquina;
c) tempo de execução de cada instrução;
d) características do compilador que se está utilizando para traduzir o programa-fonte
para a linguagem da máquina que será utilizada.

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]).

6.2 ESTUDOS DOS ALGORITMOS DESCRITOS DE


[HOP1979] E [SIL2000]
Nesta seção é mostrado um estudos dos algoritmos descritos por [HOP1979] e
[SIL2000]. Para este estudo serão consideradas as seguintes etapas:
a) contextualização dos algoritmos: um levantamento detalhado das características
de cada algoritmo;
b) mecanismos de comparação dos algoritmos: explanação dos métodos que serão
aplicados para a comparação dos algoritmos;
c) aplicação dos mecanismos: fase de execução da comparação;
d) conclusões: apresentação dos resultados alcançados.

6.2.1 CONTEXTUALIZAÇÃO DOS ALGORITMOS


Primeiramente, o objetivo tanto do algoritmo descrito por [HOP1979] como aquele
descrito por [SIL2000] é a transformação de uma expressão regular (ER) para uma função
85

equivalente (que realiza o mesmo processamento) na linguagem de programação Pascal.


Apesar disto ser muito genérico é necessário conhecer o objetivo dos algoritmos que se deseja
avaliar, pois a partir deste já é possível se ter uma primeira noção sobre o problema. Para uma
avaliação dos algoritmos é necessária uma análise das partes que compõe cada um. A partir
desta avaliação será possível estabelecer alguns critérios que serão utilizados na comparação.
A Figura 88 apresenta cada uma das etapas dos algoritmos.

Figura 88 - Fases dos algoritmos descritos por [HOP1979] e [SIL2000]

Entrada do Alfabeto e da ER Entrada do Alfabeto e da ER

Geração do AFε Geração da expressão pós-fixada pela definição em


LLC

Geração do AFND Geração da tabela do "Desmonte"

Geração do AFD Geração do GT

Função Pascal Geração do AFD

Função Pascal

ALGORITMO DESCRITO POR [HOP1979] ALGORITMO DESCRITO POR [SIL2000]


86

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.

Inicialmente tanto o protótipo que implementa o algoritmo descrito por [HOP1979]


como aquele descrito por [SIL2000] apresentam a etapa de entrada de dados. A entrada de
dados é igual para ambos e não determina um ponto importante para a avaliação dos
algoritmos. Apesar disto, é importante ressaltar que na entrada de dados existem as
consistências para verificação da correta entrada das informações.

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

Figura 89 - AFεε e “Tabela do Desmonte”

estado saída simbolo estado chegada

Linhas
ilustrativas

A estrutura de dados utilizada para armazenar o AFD em ambos os algoritmos é igual.


Esta estrutura é apresentada nas Figura 60 e Figura 61. A geração da função Pascal a partir
do AFD é idêntica nos dois algoritmos.

6.2.2 MECANISMOS DE COMPARAÇÃO


A avaliação de complexidade necessita de um delineamento dos tópicos de
complexidade que serão utilizados na avaliação. Para realizar a avaliação dos algoritmos
implementados será verificada a quantidade de memória necessária para a realização do
processamento e o tempo de processamento. Uma avaliação superficial do escopo dos
algoritmos pode determinar que o algoritmo descrito por [SIL2000] é mais complexo quanto a
sua implementação do que aquele descrito por [HOP1979], visto que este último utiliza-se de
um método mais tradicional para tratamentos de expressões regulares e autômatos finitos. O
método adotado no algoritmo descrito por [SIL2000] não é tradicional e não existem
referências sobre implementações similares. Um outro tópico de complexidade a ser
considerado na avaliação dos algoritmos é a qualidade do resultado de cada um dos
algoritmos, ou seja, como se apresenta o AFD e a função Pascal e se existem diferenças de
otimização.

Eventualmente, dependendo da necessidade ou da aplicabilidade dos algoritmos


implementados, outros tópicos de complexidade poderiam ser utilizados na avaliação.
Resumidamente serão considerados os seguintes:
a) utilização de memória: para verificar este item será considerada a quantidade de
estados e transições entre estados dos autômatos gerados pelos protótipos;
88

b) tempo de processamento: neste tópico será considerado o tempo que cada


protótipo leva para processar amostras de expressões regulares;
c) grau de otimização dos resultados de cada algoritmo: este tópico será avaliado
através da comparação dos Autômatos Finitos Determinísticos gerados pelos
protótipos.

6.2.3 APLICAÇÃO DOS MECANISMOS

6.2.3.1 UTILIZAÇÃO DE MEMÓRIA


Tendo como premissa que as estruturas de dados utilizadas pelos protótipos são
basicamente iguais é possível fazer algumas observações quanto ao uso de memória nestes
protótipos. A Figura 90 mostra as equivalências entre estruturas de dados que podem ser
estabelecidas.
Figura 90 – Equivalência entre estruturas de dados

PROTÓTIPO [HOP1979] PROTÓTIPO [SIL2000]

Estrutura que armazena o AFε A Estrutura que armazena a Tabela do A


"Desmonte"

Estrutura que armazena o AFND B Estrutura que armazena o GT B

Estrutura que armazena o AFD C Estrutura que armazena o AFD C

As estruturas de dados da Figura 90 determinam a quase totalidade dos dados tratados


pelos protótipos. Apesar de consideradas equivalentes vale ressaltar que foram consideradas
apenas estruturas de saída (aquelas fornecidas ao usuário como resultado). As estruturas
intermediárias (temporárias) utilizadas basicamente no protótipo que implementa o algoritmo
descrito por [HOP1979] foram consideradas desprezíveis. Se fossem consideradas as
estruturas intermediárias, a estrutura do AFND do protótipo hopcroft.exe é muito mais
complexa do que a estrutura do GT do protótipo desmonte.exe. Assim, avaliando a quantidade
de dados que serão armazenados nestas estruturas será possível determinar qual dos protótipos
é o mais eficiente em termos de utilização de memória. Aplicando algumas expressões
regulares sobre os protótipos, os resultados obtidos são aqueles mostrados na Figura 91. Estes
dados foram obtidos dos próprios protótipos conforme apresenta a Figura 92.
89

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

Figura 91 – Utilização de memória nos protótipos


PERCENTUAL DE UTILIZAÇÃO DE MEMÓRIA
[SIL2000] [HOP1979]

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

TOTAIS: 122 71 40 167 1367 44

UTILIZAÇÃO DE MEMÓRIA (%): 14,77% 100,00%

[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

TOTAIS: 74 43 27 100 537 30

UTILIZACAO DE MEMÓRIA (%): 21,59% 100,00%

[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

UTILIZACAO DE MEMÓRIA (%): 25,77% 100,00%


91

Figura 92 – ER a(b|c)*|ca*|^ e os dados obtidos para a Figura 91

6.2.3.1.1 AVALIAÇÃO FINAL DA UTILIZAÇÃO DE MEMÓRIA


Os resultados apresentados na Figura 91 são conclusivos quanto à utilização de
memória. Estes resultados foram obtidos com base na contagem de transições das três
principais estruturas de dados mostradas na Figura 90. O algoritmo descrito por [SIL2000] é
mais eficiente, neste tópico, do que aquele descrito por [HOP1979]. Os percentuais de
utilização de memória do algoritmo descrito por [SIL2000] são substancialmente inferiores
em relação ao algoritmo descrito por [HOP1979]. Foi observado também que, à medida que a
expressão regular submetida aos algoritmos se torna mais complexa, maior é a diferença
percentual entre os algoritmos.

6.2.3.2 TEMPO DE PROCESSAMENTO


Este tópico apresenta o tempo de processamento de algumas expressões regulares
submetidas aos protótipos, fazendo uma comparação entre os números obtidos. Os dados
foram obtidos a partir dos próprios protótipos que apresentam ao final de cada processamento
o tempo que o algoritmo levou para transformar a expressão regular em AFD (Figura 86).
92

Desta forma, aplicando várias expressões regulares aos protótipos, obteve-se o


resultado apresentado na Figura 93.
Figura 93 – Tempos de processamento
EFICIÊNCIA POR TEMPO DE EXECUÇÃO EM SEGUNDOS
TEMPO (s) EFICIÊNCIA (%)

EXPRESSÃO REGULAR: ALFABETO: HOP1979 SIL2000 HOP1979 SIL2000

1 aa a 0,17 0,11 100,00% 154,55%

2 ba* ab 0,19 0,16 100,00% 118,75%

3 (a|b)* ab 0,22 0,17 100,00% 129,41%

4 (a|b)*aa(a|b)* ab 0,39 0,22 100,00% 177,27%

5 a*ba*ba* ab 0,27 0,17 100,00% 158,82%

6 (a|b)*(aa|bb) ab 0,28 0,17 100,00% 164,71%

7 (a|^)(b|ba)* ab 0,27 0,17 100,00% 158,82%

8 (a|b)*abb ab 0,27 0,16 100,00% 168,75%

9 a(b|c)*|ca*|^ abc 0,33 0,17 100,00% 194,12%

10 (ba)*(ab)*(aa|bb)* ab 0,33 0,22 100,00% 150,00%

11 abc|aa|bb|cc abc 0,27 0,22 100,00% 122,73%

12 abcdef*a(bcd)*ef abcdef 0,33 0,27 100,00% 122,22%

13 ((abb)*(ba)*(b|aa)) ab 0,38 0,22 100,00% 172,73%

14 cab*ab*|cab*|a*(aa|bb)*|(a*|c)* abc 0,72 0,35 100,00% 205,71%

15 abc**cba**|ab*(ca|cb*)**|ab*abc*bca**a|b(ab(ba*|a))*a abc 1,26 0,55 100,00% 229,09%

16 bca*(c|aa|bb)*c|a*(ab|ba**)** abc 0,99 0,38 100,00% 260,53%

17 ((+|-|^)nn*)|((+|-|^)n*.nn*)|((+|-|^)nn*.n*)|(((+|-)n|^)n*((+|-|^).n|^|.)n*e(+|-|^)nn*) ne.-+ 1,98 0,51 100,00% 388,24%

18 0(00)*(^|0)1|1 01 0,27 0,17 100,00% 158,82%

19 ((0|1)(0|1))*|((0|1)(0|1)(0|1))* 01 0,55 0,22 100,00% 250,00%

20 (1|01|001)*(^|0|00) 01 0,44 0,22 100,00% 200,00%

21 abcde*(fgh|hgf)*|abcdefgh abcdefgh 0,46 0,33 100,00% 139,39%

22 a***bb***c***abc abc 0,79 0,27 100,00% 292,59%

TOTAL: 11,16 5,43 100,00% 205,52%

Uma outra forma de visualização do comportamento dos protótipos quanto ao tempo


de processamento é através do gráfico apresentado na Figura 94. Neste gráfico é possível
observar o comportamento do algoritmo descrito por [HOP1979] em relação àquele descrito
por [SIL2000]. Os números de 1 a 22 do eixo “X” do gráfico representam as expressões
regulares vista na Figura 94, da primeira a última.
93

Figura 94 – Eficiência de processamento


% de Eficência

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

6.2.3.2.1 ANÁLISE FINAL DO TEMPO DE PROCESSAMENTO


Da mesma forma como no tópico de utilização de memória, neste tópico o algoritmo
descrito por [SIL2000] também se mostrou consideravelmente superior ao algoritmo descrito
por [HOP1979]. A quantidade de registros produzidos que acaba por tornar o algoritmo
descrito por [HOP1979] menos eficiente no tópico de utilização de memória resulta também
numa influência direta sobre o tempo de processamento do algoritmo já que este precisa
processar uma maior quantidade de informações. Também foi observada uma diferença mais
acentuada em expressões regulares mais complexas. Os valores apresentados na Figura 93
foram obtidos num computador Pentium de 200 Mhz e 64 MB de memória, executando o
sistema operacional Windows 98.

6.2.4 GRAU DE OTIMIZAÇÃO DO RESULTADO DE CADA


ALGORITMO
O resultado dos algoritmos é representado pelo AFD gerado a partir da expressão
regular. Contando o número de estados que cada AFD apresenta quando da sua obtenção
94

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

6.3 RESULTADO DA COMPARAÇÃO DOS ALGORITMOS


Todos os resultados se mostraram amplamente favoráveis ao algoritmo descrito por
[SIL2000]. Isto desde a utilização de memória, tempo de processamento e qualidade do
resultado final (otimização). Aspectos como a complexidade das operações realizadas pelos
algoritmos e o grau de dificuldade de desenvolvimento dos protótipos não foram avaliados,
mas numa análise superficial é possível afirmar que o algoritmo de [SIL2000] também é
superior nestes itens. Fazendo uma avaliação detalhada dos procedimentos dos protótipos,
verifica-se que as operações realizadas pelo algoritmo descrito por [SIL2000] são mais diretas
do que as do algoritmo descrito por [HOP1979]. Além disto, o algoritmo descrito por
[HOP1979] gera estruturas intermediárias (AFND, por exemplo) muito complexas e que
95

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).

Ambos os protótipos implementam suas estruturas de dados principais dinamicamente


em memória. Isto acaba tornando a programação mais complexa, mas no caso do algoritmo
descrito por [SIL2000] poder-se-ia optar pelo tipo array para declarar estas estruturas o que
simplificaria o desenvolvimento do protótipo. Os testes realizados com as expressões
regulares apresentadas neste trabalho mostraram que o protótipo utilizando o tipo array
apresentou-se funcional no caso do algoritmo descrito por [SIL2000]. No algoritmo descrito
por [HOP1979], com a utilização do tipo array, o protótipo apresentou-se demasiadamente
lento e limitado no processamento.

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.

Este trabalho apresentou dois algoritmos, um descrito por [HOP1979] e outro


desenvolvido e apresentado por [SIL2000], que objetivam a transformação de expressões
regulares em autômatos finitos. Os algoritmos foram implementados em protótipos e
avaliados segundo alguns aspectos de complexidade e eficiência. O algoritmo descrito por
[HOP1979] segue um modelo padrão adotado para realizar a transformação. Já o algoritmo
desenvolvido e apresentado por [SIL2000] traz uma nova solução (não foram encontradas
referências de outro algoritmo com as características do algoritmo de [SIL2000]) para o
processo de transformação de expressões regulares em autômatos finitos determinísticos.

O algoritmo de [SIL2000] apresenta-se como uma nova solução para minimizar


problemas como falta de desempenho e alto consumo de recursos computacionais. As
avaliações realizadas neste trabalho comprovaram que o algoritmo descrito por [SIL2000] é
amplamente superior ao algoritmo descrito por [HOP1979], tendo como base os quesitos
avaliados no capítulo 6. Talvez no futuro outros algoritmos venham a ser implementados e
que tragam soluções ainda melhores para o contexto da transformação de expressões regulares
em autômatos finitos.

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

8 SUGESTÕES PARA TRABALHOS FUTUROS


Este capítulo apresenta algumas sugestões que podem ser viabilizadas no sentido de
melhorar este ou gerar outros trabalhos.

Inicialmente pode-se melhorar os protótipos implementados neste trabalho, dando a


estes recursos gráficos para visualização dos autômatos finitos na forma de grafos. Pode-se
também implementar uma ferramenta que agregue ambos os algoritmo e que permita ao
usuário selecionar entre o algoritmo desejado. Funções para a equivalência de Moore e o
interpretador universal de tabela de transições podem ser acrescentadas a esta ferramenta.
Pode-se ainda permitir que a ferramenta realize comparações automáticas dos algoritmos,
informando resultados ao usuário na forma de tabelas e gráficos comparativos.

No campo de minimização de autômatos finitos, utilizando a teoria relativa a este


assunto, pode-se implementar a minimização dos autômatos resultantes.

Pode-se ainda realizar um estudo mais aprofundado da complexidade dos algoritmos,


utilizando para isto métodos matemáticos ao invés da avaliação prática utilizada neste
trabalho.

Outros trabalhos aplicados sobre o estudo do algoritmo de [SIL2000] no contexto dos


autômatos finitos e expressões regulares podem ser elaborados. Outros algoritmos podem ser
comparados a este e novas ferramentas de comparação e estudo podem ser implementadas.
Uma ferramenta didática para demonstrar passo a passo cada uma dadas passagens dos
algoritmos poderá ser implementada.

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

ANEXO 1 - EQUIVALÊNCIA DE MOORE

Através da equivalência de Moore é possível determinar se dois autômatos finitos


determinísticos são equivalentes ou não. O processo consiste em construir uma tabela de
comparação de estados dos autômatos. O Figura 96 apresenta os passos para construção
da tabela, sendo dado o AFD A e o AFD B.
Figura 96 – Passo para equivalência de Moore
a) representação de equivalência: AFD A ≡ AFD B;
b) representação de não-equivalência: AFD A AFD B;
c) numerar os estados de A e B com números distintos. Seja n0 o estado inicial
de A e n0' o nó inicial de B;
d) construção da tabela de comparação:
- primeira coluna : pares não repetidos de estados dos autômatos;
- demais colunas: símbolo σi ∈ Σ
- linhas das colunas: (n,n') onde n é um estado de A e n' é um estado de B;
e) sob a coluna do σi ∈ ∑ e linha (n, n') escreve-se o par (nσi, nσi') onde nσi é o
estado alcançado a partir de n com a transição σi (no AFD A) e nσi' é o estado
a partir de n' com com σi (no AFD B);
f) a primeira linha é (n0,n0') representando os estados iniciais dos autômatos. A
cada novo par (n,n') ainda não representado na primeira coluna, acrescenta-se
uma nova linha na tabela;
g) se todos os pares da primeira coluna da tabela foram processados e a tabela
esta completa então AFD A ≡ AFD B;
h) se em algum par (n,n') n é um estado final e n' não é um estado final (ou vice-
versa) então AFD A AFD B.

O procedimento da Figura 96 foi implementado em um programa com o objetivo de


testar os resultados dos protótipos. Este programa é identificado pelo ícone apresentado
na Figura 97.
99

Figura 97 – Ícone de moore.exe

EXECUTANDO A EQUIVALÊNCIA DE DOIS AUTÔMATOS

O primeiro passo é a execução do programa moore.exe. A tela principal deste


programa é mostrada na Figura 98.
Figura 98 – Tela principal de moore.exe

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

Figura 99 – Abertura de autômatos em moore.exe

Figura 100 - Abertura de autômatos em moore.exe

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

Figura 101 – Processamento da equivalência


102

ANEXO 2 - INTERPRETADOR DE TABELAS

Segundo [NUN1998] existem duas formas de implementar um autômato finito através


de um programa de computador:
a) implementação por código direto dos estados e transições do autômato;
b) implementação de um interpretador de tabelas de transições;

A implementação por código direto dos estados e transições do autômato é realizada


automaticamente pelos protótipos deste trabalho. Para demonstrar o interpretador de tabelas
de transição foi implementado um programa (afd.exe) que lê o AFD gerado e salvo em
arquivo e desta forma permite a avaliação de cadeias. O programa afd.exe está disponível no
CD que acompanha o presente trabalho. A execução do programa é realizada através dos
recursos do Windows e a tela principal deste programa é conforme mostra a Figura 102.
Figura 102 – Interpretador de tabelas
103

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.

[AMB2000] AMBROSI, Cleison V. Transformação de gramáticas livres do contexto em


expressões regulares. 2000. 55 f. Trabalho de Conclusão de Curso
(Bacharelado em Ciências da Computação) - Centro de Ciências Exatas e
Naturais, Universidade Regional de Blumenau, Blumenau.

[DAV1994] DAVIS, Martin., et al. Computability, complexity and languages :


fundamentals of theorical computer science. 2 Ed. San Francisco :
Morgan Kaufmann, 1994.

[GER1995] GERSTING, Judith L. Fundamentos matemáticos para ciência da


computação. 3. Ed. Rio de Janeiro : LTC, 1995.

[GHE1987] GHEZZI, Carlo; MEHDI Jazayeri. Conceitos de linguagens de programação.


2. Ed. Rio de Janeiro : Campus, 1987.

[HOP1979] HOPCROFT, J. E. & ULLMANN, J.D. Introduction to automato theory,


languages and computation. Massachusets : Addison Wesley, 1979.

[LEW2000] LEWIS, Harry R. & PAPADIMITRIOU, Christos H. Elementos de teoria da


computação. 2. Ed. São Paulo : Bookman, 2000.

[LON1985] LONGWORTH, G. Padrões em programação : métodos e procedimentos.


Tradução de Alfredo Veiga Carvalho. Rio de Janeiro : Campus, 1985.

[LUC1979] LUCCHESI, C. I., et al. Aspectos teóricos da computação. Rio de Janeiro :


IMPA - CNPq, 1979.

[MAN1974] MANNA, Zohar. Mathematical theory of computation. New York :


McGraw-Hill, 1974.
104

[MEN1998] MENEZES, Paulo Blauth. Linguagens formais e autômatos. Porto Alegre :


Sagra Luzzatto, 1998.

[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.

[SAL1992] SALIBA, Walter Luiz Caram. Técnicas de programação : uma abordagem


estruturada. São Paulo : McGraw-Hill, 1992.

[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.

[WAZ1994] WAZLAWICK, Raul Sidnei. Linguagens Formais e Compiladores. Apostila.


4a. Versão. Florianópolis : Universidade Federal de Santa Catarina, 1994.

Você também pode gostar