Вы находитесь на странице: 1из 17

Documentação para o Programa que tem

como entrada uma Gramática Livre de


Contexto (GLC) e saída uma GLC Isenta de
Produções Unitárias

Juazeiro,
Junho de 2010

1
Documentação para o Programa que tem
como entrada uma Gramática Livre de
Contexto (GLC) e saída uma GLC Isenta de
Produções Unitárias

Trabalho solicitado pelo professor Marcus


Ramos como critério de avaliação da
disciplina Linguagens Formais e Autômatos
que tem como finalidade apresentar a
documentação do software relacionado com
o projeto proposto.

Juazeiro,
Junho de 2010

2
Sumário

1. Resumo.........................................................................................................................4

2. Fundamentação Teórica.............................................................................................5

3. Material e Método
3.1 Materiais Utilizados..............................................................................................6
3.2 Metodologia Utilizada..........................................................................................6
3.3 Estrutura do Programa........................................................................................6

4. Manual de Instalação..................................................................................................8

5. Manual do Usuário......................................................................................................8

6. Resultados.....................................................................................................................9

7. Referências.................................................................................................................11

8. Anexo..........................................................................................................................12

3
1. Resumo

Este trabalho apresenta a documentação referente ao desenvolvimento do projeto


que implementa um programa que tem como objetivo receber como entrada uma GLC e
obter como saída uma GLC isenta de produções unitárias. Para tanto, sua
implementação tem como base o algoritmo que elimina produções unitárias em uma
GLC.
O programa é destinado a docentes e discentes em que o objeto de estudo seja as
Gramáticas Livres de Contexto Isentas de Produções Unitárias.

4
2. Fundamentação Teórica

Uma GLC com produções unitárias é aquela gramática cuja suas produções são da
forma A→B, em que A e B são não-terminais. Essas produções costumam ser
descartadas das gramáticas livres de contexto porque nada acrescentam às formas
sentenciais às quais são aplicadas, constituindo mera renomeação de símbolos (no caso,
de A para B).
De acordo com o teorema para eliminação de produções unitárias encontrado em
[1], toda linguagem livre de contexto pode ser gerada por uma gramática livre de
contexto isenta de produções unitárias.
O Algoritmo abaixo mostra como transformar gramáticas livres de contexto
arbitrárias em outras equivalentes sem produções unitárias.
Eliminação de produções unitárias em gramáticas livres de contexto:
• Entrada: uma gramática livre e contexto G = (V, Σ, P, S).
• Saída: uma gramática livre de contexto G′ = (V, Σ, P′, S), tal que L(G′)= L(G) e
G′ não contém produções unitárias.
• Método:
1. Para cada A ∈ N, constrói-se NA tal que NA = {B ∈ N | A ⇒* B1} da
seguinte forma:
a) N0 ← {A};
b) i ← 1;
c) Ni ← Ni−1 ∪ {C | B → C ∈ P e B ∈ Ni−1};
d) Se Ni = Ni−1, então:
1. i ← i+1;
2. Desviar para (1.c);
Caso contrário:
NA ← Ni−1;

2. P′ ← {A → α ∈ P | α N};
3. Para todo B ∈ NA, se B → α ∈ P, e α N, então P' ← P' ∪ {A → α}.

O funcionamento deste algoritmo baseia-se inicialmente na identificação e na


associação de subconjuntos de N a cada não-terminal X da gramática, sendo que cada
elemento Y desse subconjunto satisfaz à condição X ⇒* Y. Esses subconjuntos são
construídos no passo (1) do algoritmo.
Nos passos (2) e (3) é construído P′, o conjunto de regras de produções da
gramática livre de contexto isenta de produções unitárias. Ele é obtido pela eliminação
de todas as produções unitárias de P, preservando-se as demais e criando novas
produções do tipo X ⇒* α, onde Y ∈ NX e Y → α ∈ P. Isso implica “abreviar” uma
sequência de derivações, substituindo cada derivação que utilize produções unitárias
pela derivação utilizando as novas produções que produz o efeito final pretendido.

1
Devem-se considerar apenas as derivações que são obtidas pela aplicação exclusiva de regras unitárias.

5
3. Material e Método

3.1 Materiais utilizados

O programa proposto foi implementado na linguagem de programação Java


usando o ambiente de desenvolvimento NetBeans IDE versão 6.8.
O computador utilizado para implementar e executar as classes envolvidas no
projeto tem instalado o Java Development Kit (JDK) na versão 6 juntamente com o
NetBeans IDE versão 6.8 e as seguintes características: possui um Processador Intel
Pentium Dual Core (2.67 Ghz), sistema operacional Windows XP e 1GB de memória
RAM.

3.2 Metodologia Utilizada


O projeto de construir um programa para receber como dados de entrada uma
GLC e ter como saída uma GLC isenta de produções unitárias teve como referência a
fundamentação teórica descrita na seção 2.
Algumas regras gerais foram obedecidas na implementação do programa tais
como:
* Os não-terminais da gramática são representados por letras maiúsculas {A,
B, ..., Z};
* O alfabeto da gramática deve pretencer ao conjunto {a, b, ..., z};
* A regra vazia é representada somente pelo símbolo não-terminal;
* As regras são separadas por #.

Desse maneira, o programa tem o cuidado de só aceitar gramáticas na forma que


respeitem as regras acima citadas, como por exemplo, a gramática com regras de
produção SaSa#S#Sb#Sc.
Para facilitar a manipulação dos dados no desenvolvimento da implementação
do projeto, o programa fez uso da tabela ASCII, de tal modo que cada símbolo digitado
pelo usuário ou lido de um arquivo corresponda a um número inteiro definido pela
tabela.
O programa implementa uma interface com o objetivo de facilitar a interação
com o usuário, possibilitando assim uma maior clareza quanto ao seu objetivo. Desse
maneira, o usuário poderá digitar a GLC inicial e visualizar a saída, no caso uma GLC
isenta de produções unitárias, na própria interface do programa. Opcionalmente, caso o
usuário não queira digitar a GLC, ele poderá abrir um arquivo de texto (extensão .txt)
contendo uma GLC. Assim, a GLC contida no arquivo será automaticamente carregada
para o programa que poderá fazer toda a manipulação na mesma, obtendo como
resposta uma GLC isenta de produções unitárias que gera a mesma linguagem da
referida GLC contida no arquivo.
O usuário pode também, se desejar, salvar a GLC isenta de produções unitárias
em um arquivo de texto localizado no mesmo diretório em que se encontra o programa.

3.3 Estrutura do Programa

O projeto como todo está dividido em quatro classes todas no mesmo pacote
(package automotos) que juntas regem todo o funcionamento do programa. A classe
Utils.java foi criada para somente devolver a classe que a chama a extensão dos
arquivos existentes no computador do usuário. Para tanto, ela implementa o método
getExtension(File f) que obtém o tipo de extensão do arquivo correspondente passado

6
como argumento. Já a classe ExtencaoTxt.java estende a classe FileFilter que é usada
para indicar quais arquivos no sistema do usuário são mostrados na caixa de diálogo de
navegação de arquivos, com isso a classe ExtencaoTxt.java usa um objeto da classe
Utils.java a fim de comparar todas as extensões dos arquivos e mostrar somente os
diretório e os arquivos .txt do sistema.
Outra classe que compõe o projeto é a classe GramaticaLC.java. É nessa classe
que se encontra toda lógica programacional envolvida no projeto. Nela foi desenvolvida
toda a interface que auxilia o usuário a interagir com o programa para obter a gramática
livre de contexto isenta de produções unitárias que gera a mesma linguagem da
gramática digitada ou carregada de um arquivo pelo usuário. A classe usa os respectivos
métodos para abrir um arquivo de texto solicitado pelo usuário, obter a gramática livre
de contexto sem produções unitárias e salvar a referida gramática, bem como métodos
para quando o usuário solicitar ajuda e desejar sair do sistema.
O método booleano validarGramatica() da classe GramaticaLC.java retorna
true se a gramática é válida ou false se inválida, ou seja, o método analisa a entrada
digitada pelo usuário e mostra uma mensagem de erro se a gramática for inválida. Só é
aceita as gramática que obedecerem a forma definida pelo projeto, conforme visto
anteriormente na Metodologia Utilizada. Na seção 9 é mostrada sua implementação.
Ainda dentro da classe GramaticaLC.java, um método bastante útil para a
implementação do projeto e que tem como base o algoritmo descrito na seção 2
(Fundamentação Teórica) para eliminação de produções unitárias é o método
eliminarProducoesUnitarias(). Esse método declara duas matrizes bidimensionais do
tipo char (p e p2) para guardar as respectivas regras de produções das gramáticas livres
de contexto, onde em cada linha da matriz tem-se os caracteres com os símbolos de cada
regra de produção composta pela gramática. Na matriz p é atribuída os símbolos
(caracteres) das regras de produção da gramática digitada pelo usuário ou lida de um
arquivo texto. Já a matriz p2 é utilizada para guardar os símbolos das regras da
gramática livre de contexto isentas de produções unitárias, ou seja, em cada linha de p2
vai ter todos os caracteres representando os símbolos de cada regra da gramática sem
produções unitárias.
O método eliminarProducoesUnitarias() utiliza o método booleano
isUnitaria(char[] producao) que é usado para verificar se a regra de produção passada
como argumento para esse método é unitária. O método eliminarProducoesUnitarias()
se encontra em anexo na seção 9.
Por fim, a classe Main.java que é responsável pela execução do projeto. Nela é
criada um objeto do tipo GramaticaLC chamando o construtor dessa classe, onde é
iniciado toda execução do projeto.

7
4. Manual de instalação
Primeiramente, para usufruir das facilidades oferecidas pelo programa, o usuário
deve ter o Sistema Operacional Windows (XP/Vista/7/98/2000/2003) com o Java
Runtime Environment (JRE) instalado (JRE significa Ambiente de Tempo de
Execução Java, e é utilizado para executar as aplicações da plataforma Java). Se caso o
usuário não tiver o JRE em seu sistema, poderá fazer o download em:
http://javadl.sun.com/webapps/download/AutoDL?BundleId=38663&file=/jre-6u19-
windows-i586-s.exe.
Após verificar que o JRE está devidamente instalado, o usuário poderá copiar
para o seu computador no diretório a sua escolha o arquivo com o nome
ProjetoAutomatos que se encontra no cd de instalação, logo após ter instalado o arquivo
no computador o usuário poderá remover o cd, chegando ao fim o processo de
instalação.

5. Manual do usuário
Obedecidas as orientações descritas na seção Manual de Instalação, o usuário
poderá executar o programa clicando no arquivo ProjetoAutomatos que se encontra no
diretório em que o usuário instalou o programa, assim uma janela abrirá para começar a
interação com o usuário, como mostra a Figura 01.
O usuário poderá digitar no campo “Digite a Gramática Livre de Contexto que
gera a linguagem:” uma GLC e clicar no botão “Ok”, logo após, se a garmática digitada
estiver na forma correta, conforme as regras da subseção 3.2, uma GLC isenta de
produções unitárias que gera a mesma linguagem da referida GLC digitada pelo usuário
aparecerá no campo “Gramática Livre de Contexto sem Produções Unitárias:”, como
ilustra a Figura 2.
Além disso, o usuário poderá abrir um arquivo texto que tem como conteúdo
uma GLC na barra de menu “Arquivo” no item “Abrir” da interface para ser carregado
para o programa.
Se o usuário quiser salvar a GLC isenta de produções unitárias em um arquivo
texto é só clicar no botão “Salvar Gramática”, desse modo é criado um arquivo com o
nome GLCsemRUnitarias.txt no diretório onde se encontra o programa. O arquivo
gerado contém as regras de produções da GLC inicial de entrada e as regras de
produções isenta de regras unitárias da GLC de saída que gera a mesma linguagem da
gramática inicial.

6. Resultados

Na tela inicial do programa apresentada na Figura 01, foi digitado uma GLC no
campo “Digite a Gramática Livre de Contexto que gera a linguagem:”, logo após
clicou-se em “Ok”, como a gramática está de acordo com as regras da subseção 3.2, no
campo “Gramática Livre de Contexto sem Produções Unitárias:” será visualizada uma
GLC isenta de produções unitárias que gera a mesma linguagem da referida GLC
digitada pelo usuário.
Assim, o programa garante que sendo digitada uma GLC, uma GLC isenta de
produções unitárias que gera a mesma linguagem é produzida, conforme o teorema
citado na seção 2.

8
Figura 01: Interface do programa com sua configuração inicial.

Figura 02: Resposta do programa a correspondente entrada do usuário.

9
7. Referências

[1] Ramos, M. V. M.; T. H.; Neto J. J.; Vega, I. S.. Linguagens Formais: Teoria,
Modelagem e Implementação. Bookman, 2009.

1
8. Anexo

/* método responsável por validar a gramática digitada pelo usuário ou lida


de um arquivo texto */
public boolean validarGramatica()
{
d = linha1.getText(); /* atribui a variável da gramática digitada pelo
usuário ou lida de um arquivo texto */

if(d.equals("")) // verifica se o usuário não digitou nenhuma gramática


{
return false;
}

s = d.toCharArray(); /* transforma a string contida na variável d em


um vetor s do tipo char com os caracteres da string */

int n = (int) s[0], r; /* a variável n recebe o inteiro correspondente


da tabela ASCII do primeiro símbolo da gramática (primeiro caracter)*/

// Testa a gramática digitada ou lida do arquivo

if(n < 65 || n > 90) /* testa se o primeiro símbolo da primeira regra


de produção não pertence aos não-terminais */
{
return false;
}
else
{
for(int i = 1; i < s.length; i++) /* verifica os símbolos restantes
da gramática */
{
n = (int) s[i]; /* n recebe cada símbolo (terminais e
não-terminais) da gramática */

/* verifica se o usuario digitou algum caracter inválido na gramática */


if(!(n >= 65 && n <= 90) && !(n >= 97 && n <= 122) && n != 35)
{
return false;
}

if(n == 35) // se encontrar # o próximo símbolo deve ser não-terminal


{
if((i + 1) != s.length) /* testa se o usuário não digitou ao
final da gramática o símbolo # */
{
r = (int) s[i + 1]; // a variável r recebe o símbolo após #

if(r < 65 || r > 90) {


return false; /* se o usuário não digitou um símbolo
não-terminal a gramática é inválida */

1
}
} else
{
return false; /* se o usuário digitou ao final da gramática
o símbolo # a gramática é inválida */
}
}
}
}

return true; // se o usuário digitou uma gramática válida o método retorna true
}

/* método que proporciona a eliminação das regras de produções unitárias das regras da
gramática */
public void eliminarProducoesUnitarias()
{
int i = 0, j = 0, k = 0, nl = 0, achouIgual = 0, mp = 0;
/* nl corresponde a quantidade de não-terminais na matriz de não-terminais N,
a variável mp significa a quantidade de símbolos da maior regra de produção da
gramática de entrada, sendo usada para definir o tamanho da coluna da matriz de
regras de produções usada para manipular a lógica do método */

/* o for é usado para contar quantas produções existem na gramática digitada pelo
usuário ou lida de um arquivo texto */
for(; i < s.length; i++)
{
if(s[i] == 35) /* compara se o símbolo corrente é o caracter '#', pois cada
regra é separada por '#' */
{
contp++; // incrementa o número de regras de produções

if(k > mp) // verifica se ocorreu uma regra com maior número de símbolos
{
mp = k; // a variável mp recebe a maior quantidade de símbolos
}

k = 0; // k recebe 0, pois vai ocorrer uma nova contagem de símbolos


}
else
{
k++; /* incrementa a quantidade de símbolo de cada regra de produção da
gramática */
}
}

if(k > mp) // verifica o tamanho da última regra


{
mp = k; // a variável mp recebe a maior quantidade de símbolos
}

1
/*esta parte (contp++ e mp++) esta garantindo que nenhum vetor seja indexado
fora da faixa, acrescetando um símbolo ou uma produção a mais */
contp++;
mp++;

char[][] p = new char[contp][mp]; /* usado para guardar os símbolos de cada


regra da gramática de entrada */
int[] nc = new int[contp]; // usado para controle de N
int[][] N = new int[contp][contp]; /* contém os não-terminais com produções
em vazio */
char[][] pnu = new char[contp][mp]; // contém as produções não unitárias
char[][] p2 = new char[contp * contp][mp]; /* contém os símbolos de cada regra
da gramática sem produções unitárias*/
String str;

/* for usado para varrer o vetor de símbolos de cada regras de produção da


gramática de entrada e atribuir a matriz p as regras de produções da gramática de
entrada */
for(i = k = 0; i < contp && j < s.length; j++) {
if(s[j] != 35) // compara se o símbolo corrente é o caracter '#'
{
p[i][k] = s[j]; /* cada símbolo é colocado na matriz p da correspondente
regras de produção */
k++; /* incrementa a fim de passar para o próximo símbolo da
correspondente regra */
} else {
i++; // passa para a próxima regra da gramática
k = 0; /* k recebe 0, pois é uma nova regra de produção com outros
símbolos */
}
}

/* aqui é gerada a matriz N com não terminais com produções unitárias


de acodo com o passo 1 do algoritmo 4.4 do livro */
for (i = 0, k = 0; i < contp; i++) {
// verifica se p[i] é uma regra unitária e armazena em N
if (isUnitaria(p[i]))
{
// primeiro terminal colocado na matriz N
if (nl == 0)
{
N[0][0] = p[i][0];
N[0][1] = p[i][1];
nl = 1;
nc[0] = 2;
} else
{
achouIgual = 0;
/* aqui é verificado se a produção já existe na matriz N

1
caso seja verdadeiro achouigual =1 */
for (j = 0; j < nl; j++)
{
if (p[i][0] == N[j][0])
{
N[j][nc[j]] = p[i][1];
achouIgual = 1;
nc[j]++;
}
}
/*caso o terminal com produção unitária ainda não esteja
em N ela será copiada*/
if (achouIgual == 0)
{
N[nl][0] = p[i][0];
N[nl][1] = p[i][1];
/*nc é para contar quantos não terminais o não terminal
em questão contém, por exemplo SA#SB#SC então nc[S]=4,
contando com ele mesmo */
nc[nl] = nc[nl] + 2;
nl++;
}
}
} //coloca as produções não unitárias na matriz pnu
else {
for (j = 0; j < mp; j++)
{
pnu[k][j] = p[i][j];
}
k++;
}
}

/* aqui é feita o rastreamento para produções unitárias indiretas,


como por exemplo SA#AB então N[S] = [S,A,B] */
for (i = 0; i < nl; i++)
{
for (j = 0; j < nl; j++)
{
for (k = 1; k < nc[i]; k++)
{
if (N[j][0] == N[i][k])
{
for (int w = 1; w < nc[j]; w++)
{
achouIgual = 0;
for (int y = 1; y < nc[i]; y++)
{
if (N[i][y] == N[j][w])
{
achouIgual = 1;

1
}
}

if (achouIgual == 0)
{
N[i][nc[i]] = N[j][w];
nc[i]++;
}
}
}
}
}
}

/*aqui é colocada as produções em p2 de acordo com N, como mostra o


passo 3 do algoritmo 4.4 do livro*/
achouIgual = 0;
for (i = 0; i < nl; i++)
{
for (j = 0; j < nc[i]; j++)
{
for (k = 0; k < contp; k++)
{
if (N[i][j] == pnu[k][0])
{
p2[achouIgual][0] = (char) N[i][0];
for (int y = 1; y < mp; y++)
{
p2[achouIgual][y] = pnu[k][y];
}
achouIgual++;
}
}
}
}

//coloca na matriz p2 as produções não unitárias

/* achou igual vai "apontar" para a última linha da matriz p2 para colocar as
produções não unitárias */
for (i = 0, achouIgual = 0; i < contp * contp; i++)
{
if (p2[achouIgual][0] >= 65 && p2[achouIgual][0] <= 90)
{
achouIgual++;
}
}
//copia as produções não unitárias contida em pnu para p2
int aux = 0;
for (i = 0; i < contp; i++)
{

1
aux = 0;

//verifica se a producao já está em p2, caso sim aux = 1


for (j = 0; j < nl; j++) {
if (pnu[i][0] == N[j][0]) {
aux = 1;
break;
}
}

//se aux = 0 a produção é copiada para p2


if (aux == 0 && achouIgual < contp * contp) {
//copia a produção
for (k = 0; k < mp; k++) {
p2[achouIgual][k] = pnu[i][k];
}
achouIgual++;
}
}

String str1, str2;

/*eliminar as produções repetidas


contp < 1 - se tiver apenas uma produção não precisa verificar se há repetição*/
for (i = aux = 0; p2[i][0] != 0 && contp>1; i++)
{
str1 = new String(p2[i]);
for (j = i + 1; p2[j][0] != 0 ; j++)
{
str2 = new String(p2[j]);
if (str1.compareTo(str2) == 0) //compara se as produções são iguais
{
for (k = 0; k < mp; k++) {
p2[j][k] = (char) (i + 130); /* nas produções repetidas coloca valores
inválidos, ou seja, números que não representam letras na tabela ASCII */
}
aux--;
}
}
}

//aux irá conter a quantidade de caracteres válidos em p2


for (i = 0; p2[i][0] != 0; i++, aux++)
{
for (j = 0; j < mp; j++)
{
if ((p2[i][j] >= 65 && p2[i][j] <= 90) || (p2[i][j] >= 97 && p2[i][j] <= 122))
{
//conta valores apenas válidos
aux++;
}

1
}
}

//caso haja pelo menos uma produção em p2


if (p2[0][0]!=0)
aux--;

s2 = new char[aux];

/* aqui é passado para o vetor char s2 a GLC isenta de produções unitárias que está
em p2 */
for (i = k = 0; p2[i][0] != 0; i++, k++)
{
//copia as produções para s2
for (j = 0; j < mp; j++)
{
//copia para s2 apenas valores válidos
if ((p2[i][j] >= 65 && p2[i][j] <= 90) || (p2[i][j] >= 97 && p2[i][j] <= 122))
{
if (k < aux)
{
s2[k] = p2[i][j];
k++;
}

}
}

//coloca '#' no final de cada produção


if (k < aux && s2[k - 1] != '#')
{
s2[k] = '#';
}

if (s2[k - 1] == '#') /* tira o caracter '#' repetidos que ficam com a retiradas das
produções repetidas */
{
k--;
}
}

str = new String(s2); /* str recebe o vetor de caracter s2 transformado em string


pelo construtor da clase String. str contém uma string representando a gramática isenta
de produções unitárias */

linha2.setText(str); /* atribui ao texto visível do componente "linha2" o texto


correspondendo a gramática para ser exibida na interface */
}