desenvolvimento de analisadores lexico e...
TRANSCRIPT
UNIVERSIDADE DO ESTADO DO AMAZONAS - UEA
ESCOLA SUPERIOR DE TECNOLOGIA
ENGENHARIA DE COMPUTACAO
MARCOS ANTONIO DE OLIVEIRA PENA
DESENVOLVIMENTO DE ANALISADORES
LEXICO E SINTATICO PARA PORTUGOL EM
JAVA
Manaus
2010
MARCOS ANTONIO DE OLIVEIRA PENA
DESENVOLVIMENTO DE ANALISADORES LEXICO E SINTATICO
PARA PORTUGOL EM JAVA
Trabalho de Conclusao de Curso apresentado
a banca avaliadora do Curso de Engenharia
de Computacao, da Escola Superior de
Tecnologia, da Universidade do Estado do
Amazonas, como pre-requisito para obtencao
do tıtulo de Engenheiro de Computacao.
Orientador: Prof. M. Sc. Jucimar Maia da Silva Junior
Manaus
2010
ii
Universidade do Estado do Amazonas - UEA
Escola Superior de Tecnologia - EST
Reitor:
Carlos Eduardo de Souza Goncalves
Vice-Reitor:
Marly Guimaraes Fernandes Costa
Diretor da Escola Superior de Tecnologia:
Mario Augusto Bessa de Figueiredo
Coordenador do Curso de Engenharia de Computacao:
Danielle Gordiano Valente
Coordenador da Disciplina Projeto Final:
Aurea Hileia Melo
Banca Avaliadora composta por: Data da Defesa: 07/12/2010.
Prof. M.Sc. Jucimar Maia da Silva Junior (Orientador)
Prof. Salvador Ramos Bernardino da Silva,
Prof. M.Sc. Flavio Jose Mendes Coelho
CIP - Catalogacao na Publicacao
P397d PENA, Marcos
Desenvolvimento de Analisadores Lexico e Sintatico para Portugol em Java
/ Marcos Pena; [orientado por] Prof. MSc. Jucimar Maia da Silva Junior -
Manaus: UEA, 2010.
240 p.: il.; 30cm
Inclui Bibliografia
Trabalho de Conclusao de Curso (Graduacao em Engenharia de Computa-
cao). Universidade do Estado do Amazonas, 2010.
CDU: 004
iii
MARCOS ANTONIO DE OLIVEIRA PENA
DESENVOLVIMENTO DE ANALISADORES LEXICO E SINTATICO
PARA PORTUGOL EM JAVA
Trabalho de Conclusao de Curso apresentado
a banca avaliadora do Curso de Engenharia
de Computacao, da Escola Superior de
Tecnologia, da Universidade do Estado do
Amazonas, como pre-requisito para obtencao
do tıtulo de Engenheiro de Computacao.
Aprovado em: 07/12/2010BANCA EXAMINADORA
Prof. Jucimar Maia da Silva Junior, Mestre
UNIVERSIDADE DO ESTADO DO AMAZONAS
Prof. Flavio Jose Mendes Coelho, Mestre
UNIVERSIDADE DO ESTADO DO AMAZONAS
Prof. Salvador Ramos Bernardino da Silva, Especialista
UNIVERSIDADE DO ESTADO DO AMAZONAS
iv
Agradecimentos
Primeiramente a Deus, que deu-me forcas
para seguir no curso de graduacao. Aos meus
pais sra Marlene Seda de Oliveira e sr. Rai-
mundo Miguel Lisboa Pena pelo apoio e in-
centivo nesse caminho. Ao meu orientador
MSc. Jucimar Maia da Silva Jr. que dispos
do seu tempo para me auxiliar no desenvolvi-
mento desse trabalho. Ao professor Salvador
Ramos Bernadino que deu dicas importantes
e contribuiu com seu conhecimento para o de-
senvolvimento desse trabalho. A minha famı-
lia que me apoiou nesse tempo em que estive
no curso de graduacao. Aos companheiros de
graduacao que compartilharam seus conheci-
mentos durante o curso.
v
Resumo
Esse trabalho descreve o desenvolvimento de analisadores lexico e sintatico para pseu-
docodigo em portugues. Sao abordados do ponto de vista teorico e pratico as fases do
desenvolvimento de um compilador. Primeiramente e dada uma introducao a linguagem
de pseudocodigo em portugues. E feito um levantamento conceitual onde e descrita a es-
trutura basica de um compilador, demonstrando cada parte de um compilador basico. E
demonstrado o funcionamento dos automatos de pilha estruturados e sua utilizacao no de-
senvolvimento de reconhecedores sintaticos. E feita entao a descricao do desenvolvimento
dos analisadores propostos nesse trabalho, demonstrando cada passo do planejamento do
projeto, ate sua implementacao e a construcao da aplicacao. E feita a descricao doo funci-
onamento do software, os resultados obtidos e a proposta para trabalhos futuros com base
nesse trabalho.
Palavras Chave: Compiladores, Analisadores, Lexico, Sintatico, Automatos, Gramatica
vi
Abstract
This work describes the development of lexical and syntactic analyzers for pseudocode
in Portuguese. Are addressed from the standpoint of theoretical and practical phases of
the development of a compiler. First is given an introduction to the language pseudocode
in Portuguese. It is a survey which describes the conceptual structure of a Basic compiler,
demonstrating each part of a basic compiler. It demonstrated the working of structured
pushdown automata and their use in the development of syntactic recognizers. It is made
then the description of the proposed development of analyzers that work, demonstrating
each step of project planning, through implementation and construction of the application.
It made the description doo software operations, results and proposal for future work based
on that work.
Keywords: Compilers, Analyzers, Lexical, Syntactic, Automata, Grammar
vii
Sumario
Lista de Tabelas ix
Lista de Figuras x
Lista de Codigos x
1 Introducao 1
1.1 Portugol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Justificativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 Organizacao do Trabalho . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Compiladores 6
2.1 Analise Lexica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 Analise Sintatica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Analise Semantica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 Geracao do Codigo Intermediario e Otimizacao de Codigo . . . . . . . . . . 12
2.5 Geracao de Codigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Automatos de Pilha Estruturados 14
3.1 Descricao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Formalizacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4 Desenvolvimento do projeto 21
4.1 Definicao da Primeira Versao da Linguagem . . . . . . . . . . . . . . . . . 22
4.2 Analisador Lexico Portugol . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2.1 Criacao dos Automatos para a Analise Lexica . . . . . . . . . . . . 23
4.2.2 Implementacao do Analisador lexico em Java . . . . . . . . . . . . . 26
viii
4.3 Analisador Sintatico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.3.1 Simplificacao da Gramatica em EBNF . . . . . . . . . . . . . . . . 32
4.3.2 Modelagem Utilizando Automatos de Pilha Estruturados . . . . . . 34
4.3.3 Formalizacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.3.4 Implementacao do Analisador Sintatico em Java . . . . . . . . . . . 37
4.4 Juncao dos Analisadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.5 Expansao da Linguagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.5.1 Expansao da Gramatica em EBNF . . . . . . . . . . . . . . . . . . 41
4.5.2 Analise Lexica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.5.3 Analise Sintatica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5 Conclusao 53
Referencias Bibliograficas 55
ix
Lista de Tabelas
2.1 Exemplo de tabela de sımbolos [da Silva2010a] . . . . . . . . . . . . . . . . 9
4.1 Exemplo de como o analisador lexico percorre o arquivo fonte . . . . . . . 31
x
Lista de Figuras
2.1 Estrutura tıpica de um compilador [Aho et al.2007] . . . . . . . . . . . . . 7
2.2 Exemplo de Arvore Sintatica [Aho et al.2007] . . . . . . . . . . . . . . . . 10
2.3 Exemplo de trecho de um automato para um analisador sintatico . . . . . 11
4.1 Automatos de cada atomo da linguagem feitos isoladamente . . . . . . . . 24
4.2 Juncao dos automatos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3 Automato para a primeira versao do analisador lexico . . . . . . . . . . . . 26
4.4 Exemplo de saıda gerada pelo analisador lexico . . . . . . . . . . . . . . . 31
4.5 Automato Algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.6 Automato Comando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.7 Exemplo de execucao da primeira versao da aplicacao para o algoritmo usado
como base para a linguagem . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.8 Exemplo de arquivo com a tabela de sımbolos gerado pela analise lexica . . 40
4.9 Exemplo de arquivo gerad na analise lexica com as informacoes dos identi-
ficadores do codigo fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.10 Segunda versao dos automatos para os tokens . . . . . . . . . . . . . . . . 43
4.11 Segunda versao da juncao dos automatos . . . . . . . . . . . . . . . . . . . 44
4.12 Automato para a Segunda Versao do Analisador Lexico . . . . . . . . . . . 45
4.13 Arquivo contendo as informacoes dos tokens do codigo 4.5.2 . . . . . . . . 46
4.14 Versao final do automato Comando . . . . . . . . . . . . . . . . . . . . . . 49
4.15 Automatos Expressao e Expressao1 . . . . . . . . . . . . . . . . . . . . . . 50
xi
Lista de Codigos
4.1.1 Aloritmo base para a primeira versao da gramatica da linguagem. . . . . . 22
4.1.2 Representacao da gramatica da linguagem em EBNF . . . . . . . . . . . . 23
4.2.1 Metodos da classe scanner1 . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.2.2 Metodo numero() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.2.3 Metodo analisar() da classe lex.java . . . . . . . . . . . . . . . . . . . . . . 29
4.3.1 EBNF comas regras numerads . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.3.2 Regras de transicao para os automatos dos analisadores sintaticos . . . . . 37
4.3.3 Metodo analisa() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.4.1 Primeira versao da classe Portugol . . . . . . . . . . . . . . . . . . . . . . 39
4.5.1 Gramatica da linguagem expandida . . . . . . . . . . . . . . . . . . . . . . 42
4.5.2 Algoritmo contendo os padroes da segunda versao da linguagem . . . . . . 46
4.5.3 Regras da segunda versao da gramatica enumerada . . . . . . . . . . . . . . 47
4.5.4 Regra que define o nao terminal Comando . . . . . . . . . . . . . . . . . . 47
4.5.5 Regras que define os nao termnais Expressao e Literal . . . . . . . . . . . . 47
4.5.6 Regra que define o simbolo nao terminal Expressao . . . . . . . . . . . . . 48
4.5.7 Formalizacao do automato Algoritmo . . . . . . . . . . . . . . . . . . . . . 50
4.5.8 Formalizacao do automato Comando . . . . . . . . . . . . . . . . . . . . . 51
4.5.9 Formalizacao do automato Expressao . . . . . . . . . . . . . . . . . . . . . 51
4.5.10Formalizacao do automato Expressao1 . . . . . . . . . . . . . . . . . . . . 52
Capıtulo 1
Introducao
Nesse capıtulo ha uma breve introducao sobre a linguagem Portugol e apresenta a justifi-
cativa, objetivos e metodlogia de desenvolvimento, bem como a organizacao do trabalho.
1.1 Portugol
O Portugol [Guimaraes et al.1985] e uma linguagem utilizada por estudantes de Com-
putacao para o aprendizado de Logica de Programacao, pois atraves dela pode-se simular
a execucao de algoritmos. Trata-se de uma linguagem estruturada parecida com o Al-
gol [Guimaraes et al.1985] utilizando comandos em portugues, daı o nome Portugol. Essa
linguagem [Aho et al.2007] esta presente em muitos materiais didaticos de programacao,
em Portugues, sobre o assunto, porem nao existe um padrao lexico ou sintatico para ela.
A princıpio Portugol simula um interpretador e essa simulacao pode ser feita manual-
mente. Os algoritmos podem ser escritos em uma folha de papel e o estudante percorre
o codigo linha a linha para realizar seus testes. E um processo penoso e, dependendo do
algoritmo, complexo, pois o estudante tem que testar as mais diversas possibilidades de
“execucao” do algoritmo. O ensino dessa linguagem, em geral no inıcio de um curso de
computacao, se da para que o aluno possa ter o conhecimento da estrutura de uma lin-
guagem de programacao e tenha seu primeiro contato com os algoritmos, antes ter contato
direto com linguagens de programacao mais conhecidas como C ou Pascal.
Justi�cativa 2
Atualmente existem softwares, como o VisualG [Informatica2010] e GPortugol
[Silva2010a], que interpretam algoritmos escritos em Portugol e que sao utilizados por
instituicoes academicas para o ensino de Linguagem ou Logica de Programacao, mas os
mesmos nao tem a mesma projecao de linguagens como C e Java por exemplo. Porem
alguns nao sao multiplataforma, o que faz com que seu usuario seja obrigado a ter um
Sistema Operacional compatıvel com o software; e/ou possuem codigo fechado, o que im-
possibilita a realizacao de otimizacoes e/ou correcoes por outros desenvolvedores, pois estes
nao possuem acesso ao codigo.
1.2 Justificativa
O desenvolvimento dos analisadores em JVM (Java Virtual Machine) possibilita o seu
uso independente de plataforma, ja que a JVM funciona em diversas plataformas. Assim
para utilizar os analisadores o usuario precisa apenas ter instalado a JVM em seu com-
putador, nao precisando instalar um sistema operacional inteiro para o uso da aplicacao,
por exemplo. Dessa forma pretende-se atender a diferentes tipos de usuarios - estudantes,
professores e desenvolvedores - das mais diversas plataformas, de modo a auxiliar o apren-
dizado da logica de programacao para o maior numero de usuarios possıvel. O codigo e
aberto, e de acesso livre para que outros desenvolvedores e pessoas interessadas nessa area
de estudo possam fazer eventuais mudancas e melhorias no software, podendo a partir dos
mesmos continuar o desenvolvimento de um compilador para a linguagem.
1.3 Objetivos
O objetivo desse projeto e o desenvolvimento de analisadores lexico e sintatico para a
linguagem Portugol.
A aplicacao deve ser multiplataforma e rodar na maquina virtual do Java (Java Virtual
Machine).
Metodologia 3
1.4 Metodologia
Para o desenvolvimento deste trabalho foi feita uma revisao bibliografica, onde foi
feito um estudo geral sobre os fundamentos de compiladores. Nessa revisao estudou-se
a estrutura e os princıpios do desenvolvimento de cada parte da estrutura basica de um
compilador com foco na analise lexica e sintatica. Esse estudo serviu como base para
o desenvolvimento dos analisadores deste trabalho, pois a partir deles pode-se montar a
metodologia de desenvolvimento do software. Fez-se um estudo da estrutura da linguagem
Portugol e uma analise de sua gramatica [Silva2010b], pois para o desenvolvimento de
analisadores para uma linguagem, e vital que se tenha conhecimento de sua estrutura.
Nesse estudo foi feito a analise do funcionamento da linguagem, seus comandos, funcoes,
o formato das palavras, enfim o lavantamento do maximo de elementos para a partir dela
montar a estrutura e a gramatica que descreve a linguagem referente a este trabalho.
Fez-se um estudo do funcionamento de interpretadores ja existentes para Portugol -
VisualG [Informatica2010] e GPortugol [Silva2010a]. Nesse estudo verificou-se o funci-
onamento dos mesmos, os problemas apresentados por eles.
Fez-se tambem um estudo sobre formalizacao de linguagens, e para o uso nesse trabalho
especificamente a notacao EBNF (Extended Backus-Naur Form) [Tucker and Noonan2009]
que e utilizada para a representacao da gramatica da linguagem. Foi feito um estudo da
estrutura, e as regras para a construcao de gramaticas usando essa notacao.
Foi feito tambem um estudo sobre o fundamento de automatos, o que foi de grande
importancia para o desenvolvimento da aplicacao, pois a modelagem de projeto dos anali-
sadores lexico e sintatico e basicamente feita utilizando automatos. Para esse trabalho os
estudos tiveram foco na construcao de AFDs (automatos finitos determinısticos), utiliza-
dos para a modelagem do analisador lexico. E de automatos de pilha estruturados [Ramos
et al.2009], utilizado na modelagem do analisador sintatico.
Com base nesses estudos foi feito a modelagem do projeto dos analisadores utilizando
os fundamentos estudados na revisao bibliografica e nos estudos descritos anteriormente.
Primeiramente foi definida a gramatica da linguagem utilizando EBNF, coma a gramatica
pronta, foi feita a modelagem do analisador lexico utilizando AFDs. A proxima etapa foi o
desenvolvimento e implementacao do analisador lexico para a primeira linguagem proposta.
Feito o analisador lexico foi feta a modelagem do analisador sintatico utilizando auto-
Organização do Trabalho 4
matos de pilha estruturados, com base na gramatica montada em EBNF. A partir dessa
modelagem, foi feita a implementacao do analisador e por fim a juncao dos analisadores
em um unico software.
Para o desenvolvimento desse trabalho foram feitas duas versoes da linguagem. Uma
utilizando uma gramatica simplificada e menor. E depois foi feita a expansao dessa lin-
guagem para que fosse atingido a linguagem final porposta nesse trabalho.
1.5 Organizacao do Trabalho
Alem deste capıtulo introdutorio, este trabalho esta dividido em 5 capıtulos. Sao esses:
1. Compiladores
Esse capıtulo da uma breve abordagem sobre compiladores, descrevendo as partes
que compoem a estrutura basica de um compilador [Aho et al.2007]:
• Analise Sintatica
• Analise Semantica
• Geracao do Codigo Intermediario e Otimizacao de Codigo
• Geracao do de Codigo
Essa abordagem retrata a estrutura na qual foi baseado o desenvolvimento deste
trabalho. Esta focada principalmente nas duas primeiras etapas, as quais trata-se do
tema deste trabalho.
O objetivo desse capıtulo e introduzir ao leitor a estrutura basica de um compilador,
de modo que se tenha o conhecimento de como funciona um compilador e o que
acontece em cada fase da compilacao.
2. Automatos de Pilha Estruturados
Esse capıtulo aborda o fundamento dos automatos de pilha estruturado. Nele e
abordado como e o comportamento desse tipo de automato, e como ele pode ser
utilizado para o reconhecimento de estruturas sintaticas. Pretende nesse capıtulo
Organização do Trabalho 5
demonstrar a importancia da utilizacao desse conceito na implementacao da analise
sintatica principalmente.
3. Desenvolvimento do Projeto
Esse capıtulo descreve o desenvolvimento do projeto referente a esse trabalho. Nele
e mostrada cada fase do desenvolvimento desde o inıcio do projeto ate a conclusao
da aplicacao. Os pontos abordados nesse capıtulo sao:
• Definicao da primeira versao da linguagem
• Definicao da gramatica da linguagem
• Desenvolvimento do analisador lexico para Portugol
• Modelagem do analisador lexico utilizando AFDs
• Implementacao do analisador lexico
• Desenvolvimento do analisador sintatico para Portugol
• Simplificacao da gramatica EBNF
• Modelagem do analisador sintatico utilizando automatos de pilha estruturados
• Formalizacao dos automatos
• Implementacao do analisador sintatico
• Juncao dos analisadores
• Expansao da linguagem
Capıtulo 2
Compiladores
Compilador e um software que mapeia um arquivo fonte escrito em uma linguagem de
programacao e gera um codigo semanticamente equivalente executavel pelo computador.
A compilacao segue diversas etapas desde a leitura do arquivo ate a geracao do codigo,
podendo ser divididas em duas partes: analise e sıntese [Aho et al.2007].
Na analise o compilador divide codigo fonte em partes e institui uma estrutura gra-
matical para eles. Nessa fase o compilador elimina partes de codigo irrelevantes para a
compilacao, como espacos em branco e comentarios. Ele utiliza entao essa estrutura para
criar uma representacao intermediaria do codigo. E nessa fase que o compilador verifica se
o codigo fonte esta de acordo com a sintaxe e a semantica da linguagem, emitindo ao usua-
rio eventuais discrepancias no codigo fonte. Na analise o compilador guarda informacoes
sobre o codigo em uma estrutura denominada tabela de sımbolos [Aho et al.2007]. Essa
estrutura e utilizada em todas as fases do compilador.
Na sıntese o compilador constroi o codigo objeto com base nas informacoes contidas na
tabela de sımbolos e na representacao intermediaria [Aho et al.2007].
Essas duas partes compoem uma serie de etapas que constituem a estrutura basica de
um compilador. A figura 2.1 demonstra a decomposicao tıpica dessas etapas. A primeira
e o analisador lexico, que recebe como entrado o codigo fonte escrito em uma lingagem
de programacao. O analisador sintatico analisa a estrutura do codigo fonte em relacao a
estrutura da linguagem em que ele esta escrito. O analisador semantico e responsavel pela
analise da semantica da linguagem. O gerador de codigo intermediario e responsavel por
7
gerar representacoes intermediarias do codigo fonte. O otimizador de codigo e responsavel
por melhorias no codigo antes que chegue ao gerador de codigo, onde e gerado o codigo a
ser executado pelo computador. A tabela de sımbolos e responsavel por guardar informa-
coes relativas aos atomos encontrados no codigo fonte. Na pratica algumas dessas etapas
podem ser agrupadas em uma so e as representacoes intermediarias entre os grupos nao
necessariamente sao construıdas explicitamente [Aho et al.2007].
Figura 2.1: Estrutura tıpica de um compilador [Aho et al.2007]
Alguns compiladores possuem uma fase de otimizacao de codigo entre a analise e a
sıntese. Essa otimizacao e feita para que a qualidade do codigo gerado seja maior em
diversos aspectos, porem essa nao e uma etapa presente em todos os compiladores.
Análise Léxica 8
2.1 Analise Lexica
Nesta fase o compilador converte o texto em elementos basicos da linguagem denomi-
nados tokens. Os tokens sao as unidades lexicas mais simples de uma linguagem, como
palavras reservadas (for,while em linguagens como C e Java, por exemplo), numeros, no-
mes e sımbolos (>,=,+,<=). A sequencia de caracteres que casa com um padrao de token
e chamada lexema, ou seja, numero e o token, 1 o lexema. Tokens como nome e numero
podem possuir varios lexemas, enquanto palavras reservadas corresponderao sempre um
lexema que geralmente e o proprio token como ocorre na tabela 2.1. Em geral, os to-
kens sao representados pelo analisador lexico por inteiros ou tipos enumerados [Holb1994],
no entanto para o compilador, tanto os tokens como os lexemas sao requeridos durante o
processo de compilacao.
O funcionamento do analisador lexico consiste em percorrer o codigo analisando cada
caractere, de modo a reconhecer os lexemas que correspondem ao padrao dos tokens da
linguagem relativa ao compilador. A cada lexema encontrado o analisador gera seus res-
pectivos tokens e os insere na tabela de sımbolos. A tabela e composta pelas informacoes
relativas aos tokens como: lexemas, tokens e seus respectivos valores extraıdos da analise.
A geracao desses dados e importante para as proximas fases do compilador, o nome do
token e uma abstracao que sera utilizada na analise sintatica, e o lexema servira para a
analise semantica e a geracao do codigo.
Nesse trabalho os tokens sao representados na forma
<nome,valor>
No trecho de codigo
if a>b then b=a+2
, por exemplo o analisador lexico convertera esse codigo na estrutura
<if> <id,1> < > > <id,2> <then> <id,2> <=> <id,1> <+> <id,2>
O exemplo demonstra que o analisador lexico nao leva em consideracao espacos em
branco ou quebras de linha. A partir desses dados sera gerada a tabela 2.1.
Análise Sintática 9
Lexema Token Atributo (valor do token ) Tabela de Sımbolosif ifa id 1 a> Op Log GT (ou >)b id 2 b
thenb id 2= esp =a id 1+ op mat +2 num 2
Tabela 2.1: Exemplo de tabela de sımbolos [da Silva2010a]
Observa-se na tabela 2.1 que os identificadores tem como atributos inteiros unicos,
incrementados de acordo com o surgimento dos lexemas no codigo essa e uma forma de
atribuir valores aos identificadores. Ja as palavras reservadas possuem como tokens os
proprios lexemas.
O objetivo dessa fase e eliminar eventuais erros lexicos, como identificadores, expressoes
e/ou sımbolos nao correspondentes aos padroes da gramatica da linguagem, porem esse tipo
de erro nao em muito comum de se ocorrer; eliminar partes do codigo nao relevantes para
o compilador, tratamento de identificadores e identificacao de palavras reservadas.
2.2 Analise Sintatica
Esta e a segunda fase do compilador, denominada tambem parsing [Aho et al.2007]. A
partir dos dados que compoem a tabela de tokens gerada pela analise lexica, o analisador
sintatico verifica se a estrutura do codigo esta de acordo com a gramatica da linguagem,
ou seja, se a sequencia em que os tokens e lexemas aparecem no codigo estao de acordo
com a estrutura da gramatica da linguagem. Conceitualmente dessa analise e gerada uma
representacao intermediaria conhecida como arvore sintatica. Esta arvore e uma forma
conceitual de demonstrar a estrutura do codigo. Nela cada no representa uma operacao, e
os nos “filhos” os argumentos da operacao. A arvore demonstra tambem a ordem em que
as operacoes devem ser executadas em uma expressao, por exemplo. A arvore da expressao
a = b+c*3
Análise Sintática 10
e mostrada na figura2.2.
Figura 2.2: Exemplo de Arvore Sintatica [Aho et al.2007]
Nele ha um no interior definido como *. O no filho a esquerda e o token <id,3> e
a direita o valor inteiro 3. Esse no demonstra que o compilador devera primeiramente
multiplicar o valor do token <id,3> ao inteiro 3. O no + demonstra que o token <id,2>
devera ser somado ao valor do resultado dessa multiplicacao e esse resultado devera ser
atribuıdo ao token <id,1>. Em alguns compiladores essa representacao e utilizada na
analise semantica, essa abordagem e no entanto conceitual, nem todos os compiladores
geram esse tipo de representacao intermediaria.
Outra abordagem para a analise sintatica e a utilizacao de automatos de pilha estru-
turados para o reconhecimento de gramaticas [Ramos et al.2009]. Nessa abordagem sao
construıdos automatos a partir da gramatica da linguagem. As transicoes de um estado
para o outro correspondem aos tokens. O analisador percorre o automato utilizando os
tokens da tabela de sımbolos, desse modo cada vez que o token nao satisfizer a uma tran-
sicao de estados significa que a estrutura do codigo nao esta de acordo com a gramatica
proposta. A figura 2.3 demonstra um trecho de um automato correspondente ao comando
de atribuicao de uma determinada linguagem. Observa-se que as transicoes correspondem
a tokens previamente determinados na linguagem. Porem ha uma transicao denominada
EXPRESSAO em que a seta esta tracejada. Essa parte corresponde a chamada de uma
submaquina de estados correspondente as expressoes da linguagem, como expressoes ma-
Análise Semântica 11
tematicas e logicas. Nesse tipo de chamada o analisador guarda o estado seguinte do
automato – no exemplo da figura 2.3 o estado 4 da maquina demonstrada - em uma pi-
lha percorre a submaquina. Quando o analisador termina a analise em uma submaquina
ele retorna ao estado empilhado na submaquina que a chamou e desempilha o estado da
submaquina.
Figura 2.3: Exemplo de trecho de um automato para um analisador sintatico
Nessa fase da compilacao ocorre a deteccao de erros de sintaxe como, por exemplo,
sentencas que nao estejam de acordo com os padroes da gramatica da linguagem analisada,
e as emite ao usuario. O analisador sintatico deve ser capaz de, a cada erro de sintaxe
encontrado, ressincronizar-se e continuar a analise. Isso ocorre devido aos primordios da
programacao, quando se demorava dias para se obter o resultado de um software. Os
compiladores entao deveriam ter o poder de detectar todos os erros possıveis, para evitar-
se que o programador tivesse que esperar ate o dia seguinte, por exemplo, para saber se o
programa teria outro erro. Isso ocorre ate os compiladores atuais, a maioria detecta varios
erros de uma so vez. Em alguns compiladores mais sofisticados ha mecanismos para alterar
o texto fonte, corrigindo erros no codigo [da Silva2010b].
2.3 Analise Semantica
O analisador semantico recebe como entrada a arvore sintatica e atraves da tabela de
sımbolos verifica se o codigo fonte esta de acordo com a semantica da linguagem. Essa fase
e um pouco mais complexa do que a analise sintatica, pois analisa se a estrutura esta dentro
do contexto da linguagem. Uma parte importante da analise semantica e a verificacao de
tipos, onde o operador verifica compatibilidade de operandos [Aho et al.2007]. Por exemplo,
se uma variavel do tipo inteiro receber um valor float o analisador devera reportar o erro.
Geração do Código Intermediário e Otimização de Código 12
Essas informacoes sao salvas na tabela de sımbolos ou na arvore sintatica para uso posterior,
na geracao do codigo intermediario.
Na expressao a=b*23; admitindo que a e b foram declaradas como float, percebe-
se que o lexema 23 tem a forma de um valor inteiro. O analisador verifica entao que o
operador * esta sendo aplicado a um ponto flutuante e um inteiro. Nesse caso o analisador
pode converter o valor inteiro para um numero em ponto flutuante.
2.4 Geracao do Codigo Intermediario e Otimizacao de
Codigo
No processo de traducao do codigo-fonte para o codigo-objeto, o compilador pode gerar
representacoes intermediarias do programa fonte. Essas representacoes podem ter varias
formas, como a arvore utilizada na analise sintatica e semantica [Aho et al.2007].
Apos a analise semantica o compilador pode gerar um codigo em uma linguagem para
uma maquina abstrata como representacao intermediaria. Essas representacoes devem ser
faceis de ser geradas a partir do programa fonte, bem como a geracao do codigo objeto a
partir delas [Aho et al.2007].
Antes da geracao do codigo-objeto, o codigo intermediario pode passar por um processo
de melhorias na qualidade do codigo. Essa fase, conhecida como otimizacao de codigo, tem
como objetivo melhorar a representacao intermediaria do programa fonte de modo a gerar
um codigo de objeto menor, de pouco consumo e com execucao mais eficiente e veloz. Para
obtencao de melhores resultados, ha compiladores que executam nao apenas uma, mais
diversas otimizacoes antes da geracao do codigo objeto.
2.5 Geracao de Codigo
O gerador de codigo recebe como entrada a representacao intermediaria do codigo fonte,
e o mapeia para a linguagem objeto, em geral uma linguagem de maquina como Assembly.
Nesse caso sao alocados espacos de memoria para cada variavel usada no programa, e as
instrucoes do codigo intermediario sao traduzidos para instrucoes de maquina.
Geração de Código 13
Essa e a ultima parte do compilador, pois o codigo gerado na linguagem objeto e o
codigo a ser executado pela maquina. Esse processo desde a analise lexica ate a geracao de
codigo e o que conhecemos como compilacao. Apos esse processo o programa esta pronto
para ser executado.
Capıtulo 3
Automatos de Pilha Estruturados
3.1 Descricao
Trata-se de um modelo classico composto por um conjunto de automatos denomina-
dos submaquinas do automato de pilha estruturado, onde cada submaquina deve fazer o
reconhecimento de classes de subcadeias que compoem uma cadeia de entrada para ana-
lise [Ramos et al.2009].
Transicoes podem ocorrer em uma mesma submaquina sem que ocorra movimentacao
na pilha. Essas transicoes podem ocorrer com ou sem consumo de atomos da cadeia de
entrada. Tais transicoes sao denominadas transicoes internas a submaquina. As transicoes
podem tambem ocorrer entre os estados das submaquinas com a movimentacao da pilha do
automato. Tais transicoes sao denominadas transicoes entre submaquinas. A figura 2.3 do
capıtulo 2, demonstra um trecho do automato onde ha esse tipo de transicao. A chamada
ocorre na transicao do estado 3 para a submaquina denominada EXPRESSAO. Nesse caso
o estado 4 iria para o topo da pilha, e o automato executaria a submaquina chamada. Ao
fim da execucao o automato executaria a transicao que, no caso, desempilharia o estado 4,
e continuaria a analise no automato a partir do estado 4.
Esse modelo de automatos ha dois tipos de transicoes executados entre submaquinas.
Ha as transicoes que provocam empilhamento, denominadas transicoes de chamada de
submaquina e transicoes que provocam desempilhamento, denominadas transicoes de
Descrição 15
retorno de submaquina.
A operacoes de transicao entre submaquinas ocorre de forma similar ao que ocorre
em um computador quando se executa a chamada ou retorno de uma sub-rotina em um
programa [Ramos et al.2009]. Nesses casos ha uma pilha que armazena informacoes que
tornam possıvel que o programa prossiga a execucao a partir de um ponto correto apos a
execucao de uma sub-rotina. Nesses casos os endereco de retorno e empilhado quando ha
chamada de uma sub-rotina, e desempilhado na ocasiao do retorno do controle da execucao
ao correspondente programa de computador.
No automato de pilha estruturado o estado de retorno e empilhado quando ocorre uma
transicao de chamada de submaquina. Apos a execucao da submaquina, o estado de retorno
e desempilhado e a partir dele segue a execucao do automato. Isso garante que cada retorno
de submaquina promova uma transicao para o estado correto de onde devera prosseguir a
analise da cadeia de entrada.
O automato de pilha reconhece uma cadeia quando, a partir de uma situacao inicial,
com a cadeia toda para ser analisada, o automato no estado inicial, e a pilha vazia, a
maquina chegue a uma situacao final, com a pilha vazia e um dos estados finais do automato
alcancados apos uma serie de transicoes.
Nesse esquema o tratamento de aninhamentos atraves de uma transicao de chamada de
submaquina seguida da execucao completa da submaquina chamada, ate o retorno para a
submaquina chamadora atraves da chamada de retorno de submaquina.
Em caso de aninhamentos sintaticos multiplos ha uma serie de transicoes de chamada
de submaquinas antes que ocorra a chamada de retorno de submaquina. Nesse tipo de
tratamento o conteudo da pilha aumenta a cada chamada de submaquina. E a cada retorno
de submaquina, ocorre o desempilhamento do estado referente a submaquina chamadora,
ate o retorno para a submaquina que realizou a primeira chamada de submaquina quando
a pilha devera ser esvaziada.
No caso de varias ocorrencias de uma mesma cosntrucao sintatica - um comando que
contem uma serie de comandos em uma linguagem, por exemplo, como um for dentro de
um if - as transicoes de chamada de sunbmaquina devem ser feitas de forma recursiva.
Um reconhecedor desenvolvido utilizando automatos de pilha estruturados possui alta
eficiencia, pois esses automatos operam como automatos finitos e durante uma susbtan-
Formalização 16
cial parcela de reconhecimento da maioria das linguagens usuais de programacao [Ramos
et al.2009].
3.2 Formalizacao
Com a finalidade de tornar mais visıveis as atividades de reconhecimento pelas di-
versas partes dos automatos, criou-se uma convencao, atraves da qual sao separadas as
transicoes que se destinem a resolver aspectos diferentes do reconhecimento sintatico e
mantendo agrupadas aquelas que colaborem para resolver problemas semelhantes. Inicial-
mente particiona-se as transicoes do automato desejado. Em cada particao serao agrupadas
as transicoes que se refiram ao conhecimento de uma mesma construcao sintatica [Ramos
et al.2009].
Quando uma construcao sintatica depende de outra, ou recursivamente de si mesma,
devera conter transicoes capazes de guardar em uma pilha, informacoes sobre o ponto de
construcao sintatica envolvente em que se fizer necessario o reconhecimento de sintaxe de
que aquela depende, para entao ativar a particao do automato responsavel pelo reconheci-
mento de tal construcao sintatica envolvente.
Cada uma das particoes do automato deve apresentar uma particao especial para que,
uma vez terminado o reconhecimento da sintaxe que se refere a particao, utilize a infor-
macao previamente guardada na pilha para, a partir dela, prosseguir o reconhecimento da
construcao sintatica envolvente.
Cada uma dessas particoes corresponde a uma submaquina referente a uma construcao
sintatica representada por um nao terminal. Ou seja, a maquina e suas submaquinas.
Transicoes normais nao movimentam a pilha. Elas ocorrem de modo semelhante as
transicoes em automatos finitos. Porem, esse tipo de transicao consome sımbolos da cadeia
de entrada. Ja as transicoes de chamada e de retorno de submaquina movimentam a pilha,
porem nao consomem sımbolos da cadeia de entrada [Ramos et al.2009].
Apos essas consideracoes passa-se a formalizar o automato de pilha estruturado.
Um automato de pilha estruturado pode ser representado atraves da octupla
(Q,A,Σ,Γ, P, Z0, q0, F ) [Ramos et al.2009] onde:
• A: um conjunto finito nao vazio de submaquinas ai definidas adiante;
Formalização 17
• Q: um conjunto finito nao vazio de todos os estados qi,j das submaquinas ai ∈ A;
• Σ: um conjunto finito nao vazio de sımbolos de entrada σk que podem ser consumidos
pelas submaquinas am ∈ A
• Γ: um conjunto finitio nao vazio de sımbolos de pilha γk = (m,n), correspondentes
aos estados finais qm,n ∈ Qm das diversas submaquinas am ∈ A;
• P : um conjunto finitio nao vazio de regras de transicao pi,j, com i identificado a
submaquina e j, a regra especıfica;
• Z0 ∈ Γ: marcador de pilha vazia;
• q0 ∈ Q: estado inicial do automato, q0 ≡ q0, 0;
• F ⊆ Q: conjunto nao vazio de estados finais do automato.
Cada uma das submaquinas e representada por ai = (Qi,Σi, Pi, qi,0, Fi), [Ramos
et al.2009] onde:
• Qi ⊆ Q e o conjunto dos estados ai,j de ai;
• Σi ⊆ Σ e o conjunto dos sımbolos de entrada σ de ai;
• Pi ⊆ P e o conjunto das regras de transicao pi,m de ai;
• qi,0 ∈ Qi e o estado de entrada de ai;
• Fi ⊆ Qi e o conjunto de estados de saıda qi,f de ai.
Cada uma das regras de transicao pi.m assume a forma de uma sextupla (λ, s, ρ, λ′, s′, ρ′)
[Ramos et al.2009], e e denotada na forma
pi,m : λsp→ λ′s′ρ′
onde
• λ ∈ Γ∗: situacao da pilha antes da aplicacao de pi,m;
Formalização 18
• s ∈ Q: estado do automato antes da aplicacao de pi,m;
• ρ ∈ Σ∗: representa cadeia de entrada a processar, antes da aplicacao de pi,m;
• λ′ ∈ Γ∗: situacao da pilha depois da aplicacao de pi,m;
• s′ ∈ Q: estado do automato depois da aplicacao de pi,m;
• ρ′ ∈ Σ∗: representa cadeia de entrada restante, depois da aplicacao de pi,m;
O conjunto Q de estados de um automato e a uniao de todos os estados Qi de estados
de suas submaquinas ai. Logo um estado qualquer do automato sera sempre algum estado
qi,m da submaquina correspondente ai [Ramos et al.2009].
Os conjuntos Pi das transicoes das submaquinas ai devem ser disjuntas como demonstra
Pi ∩ Pj = �, i 6= j
e a uniao de todos eles devem compor o conjunto P
∪i(Pi) = P
Na denotacao de regras de transicao ha algumas convencoes a se destacar:
• Se nao for explicitado nenhum sımbolo especıfico de entrada em ρ e ρ′, subtende-se
que a aplicacao de tal regra de transicao independe da entrada, assim como se nao
for explicitado nenhum sımbolo especıfico de pilha em λ e λ′, a regra independe do
conteudo da pilha [Ramos et al.2009].
• Se λ = λ′, a transicao nao resultara em movimentacao da pilha. Nessa situacao se λ
assumir a forma γg, sendo g uma cadeia nao vazia de elementos de Γ, a transicao so
sera aplicavel caso g coincida, nessa situacao, com o conteudo do topo da linha. Se
g for omitido, entao a transicao sera aplicavel independete de aplicacao de testes em
ralacao ao topo da pilha [Ramos et al.2009].
• Se ρ = ρ′ = α, omitindo-se a explicitacao de sımbolos de entrada, isso denotara em
uma transicao em vazio. Se ρ assumir a forma σa, sendo σ uma cadeia de sımbolos
de Σ, e ρ′ = α, a regra de transicao consumira a cadeia σ sempre que for aplicada.
Formalização 19
Caso ρ = ρ′ = σα, a transicao so sera aplicada caso seja encontrado um sımbolo σ
iniciando a cadeia de entrada a ser processada sem que σ seja consumida [Ramos
et al.2009].
Ha regras de transicao sao definidas para os tres tipos de transicao que ocorrem em
automatos de pilha estruturados:: transicoes internas, transicoes de chamada de subma-
quina e transicoes de retorno de submaquina.
• Transicoes internas
A forma tıpica desse tipo de transicao e a seguinte
Pi,m : γqi,jσα→ γqi,kα
Reescrevendo essa transicao de forma abreviada e mnemonica obtem-se
Pi,m : qi,jσ → qi,k
Em vazio, essas transicoes tem a forma
Pi,m : γqi,jα→ γqi,kα
Na forma abreviada e mnemonica
Pi,m : qi,jε→ qi,k
• Transicoes de chamada de submaquina
Forma tıpica das regras de transicao:
Pm,t : γqm,rα→ γ(m,n)qv,0α
A forma abreviada e mnemonica
Pm,t : qm,rav → EMPILHAqm,n
Formalização 20
representa uma transicao envolvendo uma chamada da submaquina av originada no
estado qm,r e com retorno para o estado qm,n.
• Transicoes de retorno de submaquina
Forma tıpica das regras de transicao:
Pm,t : γ(m,n)qv,uα→ γqm,nα
Forma abreviada e mnemonica:
Pm,t : qv,uε→ RETORNA
Quando se trata da ultima transicao da submaquina principal, a forma desse tipo de
transicao e
Pm,t : γZ0qv,uα→ γqm,nα
Reescrevendo:
Pm,t : qv,uε→ ACEITA
Capıtulo 4
Desenvolvimento do projeto
A proposta deste trabalho e o desenvolvimento de uma aplicacao que receba como entrada
um arquivo fonte contendo um codigo escrito em Portugol. O software deve fazer a analise
lexica percorrendo todo o arquivo fonte identificando os tokens contidos no codigo e gerar a
tabela de sımbolos, onde ficarao armazenadas as informacoes referentes a esses tokens, alem
de emitir eventuais erros lexicos encontrados no codigo. Feita a analise lexica o software
executa o segundo passo que e a analise sintatica. O analisador sintatico recebe como
entrada a tabela gerada na analise lexica. O analisador deve percorrer essa tabela de modo
a verificar se a ordem em que os tokens aparecem no codigo estao de acordo com a estrutura
da gramatica proposta. Esse analisador devera identificar os erros de sintaxe e emiti-los
para o usuario.
Feitas as duas analises, se o codigo nao contiver erros lexicos ou sintaticos, o que significa
que ele esta correto, o software deve emitir uma mensagem informando ao usuario que as
analises foram realizadas com sucesso.
O desenvolvimento deste trabalho e baseado nos fundamentos do desenvolvimento de
compiladores e a modelagem do projeto do software e basicamente feito utilizando automa-
tos. A importancia do uso de automatos nesse trabalho e bastante relevante, pois agiliza e
torna implementacao dos analisadores menos complexa, tornando o codigo bastante escala-
vel, ou seja, torna-se mais simples as modificacoes a serem posteriormente feitas no codigo.
Partindo da ideia de se criar uma linguagem inicial para posteriormente expandı-la, o uso
da abordagem com automatos auxilia nessa expansao tornando-a menos complexa.
De�nição da Primeira Versão da Linguagem 22
4.1 Definicao da Primeira Versao da Linguagem
Para o inıcio do desenvolvimento de um compilador ou de analisadores para uma ligua-
gem, primeiramente deve-se definir a estrutura da mesma, ou seja, definir sua gramatica.
A ideia desse trabalho foi, inicialmente, a criacao de uma gramatica pequena para uma
linguagem com poucos comandos. Foi feito o projeto, modelagem e implementacao dos
analisadores para essa primeira gramatica, e a partir daı foi feita a ampliacao da mesma
para a gramatica da linguagem desejada utilizando a ideia e a estrutura do projeto pronta.
Para a criacao da estrutura inicial da linguagem tomou-se como base o seguinte algo-
ritmo em Portugol do codigo 4.1.1, onde ha apenas a descricao do algoritmo, declaracao
de variavel e os comandos de atribuicao e escrita.
1 algoritmo teste2 int a;3
4 a:=2;5 escreva(a);6 fimalgoritmo
Codigo 4.1.1: Aloritmo base para a primeira versao da gramatica da linguagem.
Com base nesse algoritmo foi construıda a primeira versao da aplicacao e a partir dela,
posteriormente expandida a linguagem.
Com base no algoritmo foi construıda a gramatica da linguagem. Para isso foi utilizada
a notacao EBNF(Extend Backus-Naur Form) [Tucker and Noonan2009]. O codigo 4.1.2
demonstra um modo formal de representar a gramatica da linguagem a ser desenvolvida,
e e com base nessa gramatica que serao feitas as analises em um compilador. A descricao
da gramaticas em EBNF, e feita utilizando um conjunto de regras referentes a estrutura
da linguagem. A codigo 4.1.2 demonstra que essas regras possuem um lado esquerdo e
um lado direito separados pelo sinal -> compostos por sımbolos denominados terminais
e nao terminais. O sımbolo terminal -> indica que o sımbolo do lado esquerdo deve ser
substituıdo pelos sımbolos do lado direito. Essa representacao da estrutura da linguagem
sera usada na construcao dos analisadores.
Analisador Léxico Portugol 23
1 Algoritmo->DeclaracaoAlgoritmo {Declaracao} {Comando} "fim_algoritmo"2 DeclaracaoAlgoritmo->"algoritmo" Identificador3 Declaracao-> Tipo Identificador {, Identificador};4 Tipo->int5 Comando->Atribuicao|Escrita6 Atribuicao->Identificador ":=" Identificador|numero ";"7 Escrita->"escreva (" Numero|Identificador ");"8 Identificador-> Letra {Letra|Digito|_}9 Letra->a|..|z|A|..|z
10 Digito->0|..|911 Numero->Digito{Digito}
Codigo 4.1.2: Representacao da gramatica da linguagem em EBNF
4.2 Analisador Lexico Portugol
Para o desenvolvimento do analisador lexico, foram extraıdos da gramatica do codigo
4.1.2 os atomos mais basicos da linguagem, como identificadores, numeros, sımbolos. Esses
atomos representam os lexemas que serao encontrados no codigo.
4.2.1 Criacao dos Automatos para a Analise Lexica
A partir das regras na gramatica que definem tais atomos, foram construıdos os auto-
matos relativos a cada um deles. Nesse trabalho utilizou-se os ADFs (automatos finitos
determinısticos), o que simplifica a modelagem e consequentemente a implementacao do
analisador.
Na gramatica proposta no codigo 4.1.2 levou-se em conta as regras para Identificador e
Numero, alem dos sımbolos da linguagem. Primeiramente foram construıdos os automatos
de cada um deles isoladamente.
A figura 4.1 demonstra os automatos referentes a cada atomo levantada na linguagem
inicialmente proposta. O automato Palavra foi feito baseado na regra da gramatica do
codigo 4.1.2 de mesmo nome. Na figura 4.1 o automato define a partir do estado q0, uma
palavra sera composta por apenas uma letra ou uma letra seguida de letras, numeros ou o
sımbolo . Ou seja com esse automato define-se que identificadores e palavras reservadas
devem sempre comecar com uma letra, palavras como: 2a, casa+z caracterizam erros
lexicos. O segundo automato, o Numero, indica que um numero e composto por um
dıgito seguido de nenhum ou mais dıgitos, ou seja, a linguagem proposta aceita como
numero apenas numeros inteiros. Nessa primeira parte do projeto ainda nao foram levados
em consideracao os numeros reais. E o automato Simbolo caracteriza os sımbolos especiais
Analisador Léxico Portugol 24
da linguagem. Percebe-se que ha apenas um sımbolo formado por 2 sinais, que e o sımbolo
de atribuicao :=, observa-se que nessa linguagem o sımbolo : devera ser seguido por =. E
os demais sao sımbolos simples: parenteses e o ; .
Figura 4.1: Automatos de cada atomo da linguagem feitos isoladamente
Feitos os automatos para cada atomo isoladamente, o proximo passo foi a unificacao
dos automatos demonstrado na figura 4.2.
Na juncao dos automatos a partir de um estado inicial pode-se percorrer os demais esta-
dos referentes aos atomos. Nessa juncao monta-se uma base para a estrutura do automato
a ser utilizado na implementacao do analisador lexico. Depois da juncao dos automatos
eliminam-se os estados finais e obtem-se o automato da figura 4.3. Foi com base nesse
automato que deu-se a construcao do algoritmo do analisador lexico.
Analisador Léxico Portugol 25
Figura 4.2: Juncao dos automatos
No automato da figura 4.3 os antigos estados finais (automato da figura 4.1) sao ligados
ao estado inicial. Esse automato deve receber uma serie de cadeias de caracteres do arquivo
fonte e fazer analisar se essas cadeias correspondem aos padroes de tokens da linguagem.
As transicoes λ representam as transicoes em vazio como espacos em branco ou quebras de
linha. Cada vez que o analisador depara-se com esse tipo de transicao, ele verifica se a cadeia
corresponde a um padrao da linguagem e esta pronto para a analise da proxima cadeia de
caracteres. Outro ponto a se observar e a transicao dos estados que representavam estados
finais de palavra e numero (estados q1 e q2) para estados q3 e q4. Essas transicoes
ocorrem porque na linguagem descrita, as palavras e numeros podem aparecer no codigo
seguidas de um sımbolo especial sem que haja espaco em branco entre eles, por exemplo no
trecho a:=3; ha 4 tokens sem que haja espaco entre eles. Nesse caso o analisador devera
ser capaz de tratar separadamente cada um deles. Nesse trecho a um identificador seguido
de um sımbolo especial, seguido de um numero, seguido de outro sımbolo especial. O
analisador devera portanto ver esse trecho de codigo na forma <id,1><:=><3><;>.
Essa questao e resolvida no automato com as transicoes citadas, pois dessa forma
Analisador Léxico Portugol 26
Figura 4.3: Automato para a primeira versao do analisador lexico
quando o analisador esta verificando uma cadeia com as caracterısticas de uma palavra
e encontra um sımbolo especial, o analisador trata a cadeia referente a palavra e segue a
analise da parte da cadeia referente ao sımbolo especial como se fosse uma nova cadeia. No
trecho de codigo citado como exemplo, apesar de compor uma unica cadeia, o analisador
devera trata-lo como 4 cadeias diferentes, pois tratam-se de elementos lexicos diferentes.
4.2.2 Implementacao do Analisador lexico em Java
Com os automatos construıdos o proximo passo foi o inıcio da implementacao dos
mesmos em Java. Primeiramente foi criada a classe scanner 1 onde se faz o tratamento
de cada atomo isoladamente, conforme os automatos da figura 4.1. Na classe Scanner1
demonstrada no codigo 4.2.1. encontram-se os metodos que recebem como entrada cadeias
de caracteres e verificam se correspondem aos padroes da gramatica da linguagem.
Os metodos recebem como entrada uma cadeia de caracteres e fazem o tratamento
dessas cadeias, verificando se seguem algum padrao da linguagem seguindo rigorosamente
os automatos construıdos para o analisador.
Analisador Léxico Portugol 27
1 class scanner12 {3 public boolean palavra(String str)4 {5 ...6 }7
8 public boolean numero(String str)9 {
10 ...11 }12 public boolean eletra(char chr)13 {14 ...15 }16 public boolean edigito(char chr)17 {18 ...19 }20 ...21
22 public boolean reservada(String palavra)23 {24 ...25 }26
27 public boolean simbEspecial(String esp)28 {29 ...30 }31 }
Codigo 4.2.1: Metodos da classe scanner1
O codigo 4.2.2 mostra que o metodo percorre cada da cadeia de entrada. O metodo
verifica o caractere e o estado atual da cadeia de entrada e analisa se o caractere corresponde
a alguma transicao do automato. Se corresponder ele passa para o estado correspondente
a transicao no automato, senao seta com o estado de rejeicao. Ao fim da cadeia analisada
verifica se ela pode ser aceita ou nao, tudo controlado pelos estados definidos no automato.
Verifica-se que o estado final a ser alcancado no metodo numero() e o 1, estado esse definido
no automato de mesmo nome. Os outros metodos foram implementados na mesma linha
de ideia, recebendo como entrada uma cadeia de caracteres ou um caractere simples, e
verificando se a cadeia ou o caractere pode ser aceito ou nao pela linguagem baseando-se
nos automatos construıdos na modelagem do analisador.
Nessa classe ficam armazenados dados importantes para a implementecao do analisador
sintatico, como as palavras reservadas e os sımbolos especiais da linguagem. Esses dados
ficam armazenados em arrays dentro da classe e deverao ser comparados como os respec-
tivos tokens encontrados no codigo. Por exemplo, ao verificar que determinada cadeia de
Analisador Léxico Portugol 28
1 public boolean numero(String str)2 {3 final int reject = 3; // estado de rejei4 int state = 0; // estado atual5 char current; // caracter atual6 int index = 0 ; // ice do caracter atual7 //continua enquanto haracteres de entrada e nfor setado o estado de rejei8 while ( index < str.length() && state != reject )9 {
10 current = str.charAt( index++ ) ;11 // Estado 012 if ( state==0 && current>=’0’ && current <=’9’)13 state=1;14 // Estado 115 else if ( state==1 && current>=’0’ && current<=’9’)16 state = 1;17 // sem transi18 else19 state = reject ;20 }21 // se ao fim da String o estado reject nncontrado ela ceita22 if ( index == str.length() && state == 1 )23 return true;24 else25 return false;26 }
Codigo 4.2.2: Metodo numero()
caracteres encontrada no arquivo fonte trata-se de uma palavra, o analisador devera verifi-
car se e uma palavra reservada ou um identificador. Essa verificacao e feita comparando-se
a cadeia as palavras contidas no array de palavras reservadas da linguagem. A classe
scanner1 e implementada na classe lex. A ultima trata-se do analisador lexico em si. E
nessa classe que o arquivo fonte escrito na pequena linguagem proposta ate aqui e lido. Ela
recebe como entrada o arquivo fonte, o percorre caractere a caractere. Analisa os tokens
encontrados no codigo e gera as tabelas de sımbolos e de tokens.
Na classe lex encontra-se o metodo analisar(). Esse metodo implementa o automato
da figura 4.3, ou seja, o automato do analisador lexico. E nesse metodo que ocorre a
analise.
O codigo 4.2.3 demonstra o metodo analisar(). Ele recebe como entrada o arquivo fonte
e le cada linha verificando cada caracter e a cada cadeia de caracter encontrada, verifica se
os lexemas fazem parte da gramatica proposta ou nao. Se o lexema for aceito o analisador
o insere em um array da classe TabSimb, que foi criada somente para armazenar os dados
dos lexemas encontrados no arquivo fonte. Seus atributos correspondem aos atributos de
um token:lexema, token, valor e a linha do arquivo onde encontra-se o token. Apos o metodo
Analisador Léxico Portugol 29
analisar() percorrer e fizer o tratamento de todos os tokens do arquivo-fonte, o analisador
percorre esse array com as informacoes dos tokens armazenadas e inseri-las na tabela de
sımbolos. Optou-se por guardar os dados dos tokens primeiramente em um array e somente
depois inserir os dados na tabela de sımbolos, desse o arquivo contendo a tabela de sımbolos
nao precisa ser aberto varias vezes para inserir cada grupo de informacoes, o que acarretaria
em uma queda no desempenho da aplicacao equivalente ao tamanho do arquivo fonte. Caso
a cadeia analisado nao satisfaca a nenhum padrao de token existente na linguagemm o
analisador nao insere os dados do lexema no array de sımbolos, consequentemente nao
insere tambem na tabela de sımbolos, e emite a mensagem de erro adequada ao usuario.
1 public void analisar(String arquivo)2 {3 ...4 try5 {6 //Abre o arquivo fonte para leitura7 BufferedReader in = new BufferedReader(new FileReader(arquivo));8 ...9 //percorre cada linha do arquivo
10 while ((abc = in.readLine()) != null)11 {12 ...13 }14 }15
16 }17
18 //Switch implementado no mdo analisar():19 switch(this.estado)20 {21 case 0:22 {23 if(fsa.eletra(car))24 {25 this.estado=1;26 str=Character.toString(car);27 }28 else29 ...30 }31 case 1:32 {33 ...34 }35 ...36 case n:37 { }38 }
Codigo 4.2.3: Metodo analisar() da classe lex.java
Com a abordagem de automatos a implementacao do automato lexico da figura 4.3
no metodo analisar resume-se a um switch, que recebe como argumento o estado atual
Analisador Léxico Portugol 30
do automato. Em cada case o analisador verifica se o caractere atual lido do arquivo
corresponde a alguma transicao do estado corrente. Se corresponder ele avanca para o
proximo estado e concatena o caractere a cadeia atual de caracteres.
Na tabela 4.2.2 ha um exemplo de como o analisador lexico percorre o arquivo fonte, e
pega as informacoes dos tokens encontrados. A tabela descreve o tratamento sendo feito
na parte inicial de um arquivo escrito na linguagem proposta, onde e definido o nome
do algoritmo. O analisador inicia com no estado q0. Ao iniciar a analise ele encontra o
primeiro caracter do arquivo, o “a” que trata-se de uma letra. Conforme o automato da
figura 4.3, esse caracter corresponde a transicao de q0 para q1. O caractere e inicia entao
uma cadeia, e o estado muda para q1. O proximo caractere e o “l’, que trata-se de uma
letra. Seguindo o automato, o caractere corresponde a transicao de q1 para q1. O “l” e
entao concatenado a cadeia atual, e o estado permanece em q1. Esse tratamento e feito ate
que e encontrado algo equivalente a transicao λ no arquivo. No exemplo da tabela trata-se
de um espaco em branco Quando isso ocorre o analisador verifica a cadeia formada. No
exemplo da tabela 4.2.2, trata-se de uma palavra reservada. Esse e entao o primeiro token
encontrado no arquivo, logo e inerido no array de tokens para posteriormente ser inserido
na tabela de sımbolos. Quando o espaco em branco e encontrado o analisador realiza a
transicao para o estado q0, conforme a transicao descrita no automato lexico, e verifica o
proximo caracter. No exemplo e encontrado o caractere “t”, este dara inıcio a formacao da
cadeia correspondente ao lexema teste, que trata-se de um identificador, que no caso e o
nome do algoritmo. Esse tratamento e feito para todos os tokens encontrados no decorrer
do arquivo fonte.
Uma situacao a se destacar e a de tokens diferentes, que nao sao separados por espacos
em branco ou quebra de linha, conforme citado na criacao do automato lexico. Se apos o
ultimo caracter de uma palavra reservada, por exemplo, for encontrado um“(”, o analisador
iria fazer a transicao para o estado q4, e faria o tratamento do token referente a palavra
reservada.
Apos percorrer todo o arquivo fonte o analisador percorre o array com os lexemas e os
insere em dois arquivos denominados tokens.txt e simbolos.txt. A figura 4.4. demonstra
como os tokens sao armazenados nos arquivos. A figura representa a saıda do analisador
para o algoritmo base descrito no codigo 4.1.1.
Analisador Léxico Portugol 31
Estado Caracterq0 a letraq1 l letraq1 g letraq1 o letraq1 r letraq1 i letraq1 t letraq1 m letraq1 o letraq1 λ = espaco em brancoq0 t letraq1 e letra... ... ...
Tokenalgoritmo palavra reservada
Tabela 4.1: Exemplo de como o analisador lexico percorre o arquivo fonte
Figura 4.4: Exemplo de saıda gerada pelo analisador lexico
Como demonstrado na figura 4.4 o arquivo tokens.txt guarda os tokens, seus respec-
tivos valores e a linha onde cada encontram-se no arquivo. Essas informacoes aparecem
exatamente na ordem em que os tokens sao encontrados no arquivo fonte, por isso alguns
ids aparecem repetidos. Essa ordem e de importancia relevante para a analise sintatica e
Analisador Sintático 32
para futuramente um possıvel desenvolvimento de um analisador semantico. A importancia
na ordem dos tokens e melhor explicada na secao 4.3 onde e descrito o desenvolvimento do
analisador sintatico. A primeira coluna desse arquivo corresponde aos tokens ; a segunda
contem seus respectivos valores e a terceira a linha em que se encontram no arquivo fonte.
Observa-se que cada palavra reservada possui token e valor igual, pois tratam-se do pro-
prio lexema. Os numeros e sımbolos especias (descritos na tabela pelos tokens num e esp
respectivamente) possuem como valores, tambem, os proprios lexemas. Ja tokens descritos
como id correspondem aos identificadores/variaveis presentes no codigo. Observa-se que
seus valores sao incrementados na medida em que novos identificadores aparecem.
O arquivo sımbolos.txt armazena os valores e lexemas referentes aos identificadores
encontrados no codigo. Nesse arquivo os ids nao se repetem, esse arquivo tem sua im-
portancia na analise sintatica e semantica, pois ele guarda informacoes importantes dos
identificadores. Nessa tabela a primeira coluna corresponde ao valor e a segunda os lexe-
mas correspondentes a cada identificador.
Esses dois arquivos gerados pelo analisador lexico serao utilizados como entrada para
o analisador sintatico e posteriormente no caso de desenvolvimento das outras etapas do
compilador, essas tabelas deverao ser usadas como entrada.
4.3 Analisador Sintatico
O analisador sintatico desenvolvido neste trabalho recebe como entrada as tabelas de
sımbolo e tokens. O objetivo deste analisador e verificar se a sequencia em que os tokens
aparecem no arquivo fonte estao de acordo com a gramatica descrita.
4.3.1 Simplificacao da Gramatica em EBNF
Para o desenvolvimento do analisador sintatico utiliza-se novamente a gramatica des-
crita em EBNF. Dessa vez a importancia da gramatica esta na descricao da estrutura da
linguagem. O primeiro passo para o desenvolvimento do analisador sintatico e a simpli-
ficacao da gramatica em EBNF de modo a adquirir as regras que definem os sımbolos
nao terminais essenciais [Ricarte2008]. O codigo 4.3.1 demonstra cada regra da gramatica
enumerada.
A simplificacao da gramatica e feita com substituicoes. No analisador sintatico desse
Analisador Sintático 33
trabalho foram escolhidos os sımbolos nao terminais essenciais e foram feitas as devidas
substituicoes dos demais sımbolos nelas. Para essa primeira linguagem foram escolhidas
como expressoes principais as relativas aos sımbolos Algoritmo e Comando. A partir
desses dois sımbolos foram feitas as substituicoes das demais expressoes correspondentes
aos demais sımbolos nao terminais da linguagem. Primeiramente enumerou-se as regras da
gramatica e EBNF para iniciar as substituicoes, conforme o codigo 4.3.1.
(1)Algoritmo->DeclaracaoAlgoritmo{Declaracao}{Comando}"fim_algoritmo
(2)DeclaracaoAlgoritmo->"algoritmo" Identificador
(3)Declaracao->Tipo Identificador {, Identificador};
(4)Tipo->int
(5)Comando->{Atribuicao|Escrita}
(6)Atribuicao->Identificador ":=" Identificador|numero ";
(7)Escrita->"escreva (" Numero|Identificador ");"
Codigo 4.3.1: EBNF comas regras numerads
Primeiramente foi feita a substituicao de (4) em (3) do codigo 4.3.1. Com essa substi-
tuicao obteve-se a expressao (8)
(8)Declaracao->int Identificador {, Identificador};
Fazendo a substituicao de (8) e (2) em (1) obtem-se
Algoritmo->"algoritmo"Identificador {"int"Identificador {,Identificador};} {Comando} "fim_algoritmo
e substituindo (6) e (7) em (5) obtem-se
Comando-> (Identificador ":="Identificador|Numero ;)|"escreva("Numero|Identificador ");
Analisador Sintático 34
4.3.2 Modelagem Utilizando Automatos de Pilha Estruturados
Conceitualmente um analisador sintatico deve gerar uma arvore de sintaxe. Essa re-
presentacao, no entanto, nem sempre e feita de forma explıcita como citado no capıtulo 2.
Para esse trabalho foi utilizada uma abordagem que utiliza automatos de pilha estruturados
para o desenvolvimento do analisador sintatico. O objetivo e percorrer o automato de modo
semelhante a analise lexica, porem na analise sintatica o arquivo de entrada para o anali-
sador e a tabela de sımbolos gerada na analise lexica. O analisador percorre os automatos
utilizando como transicao entre os estados os tokens da tabela de sımbolos. Dessa forma
e verificada se a estrutura do codigo fonte esta de acordo com a gramatica da linguagem
proposta. A utilizacao deste metodo torna a implementacao do codigo menos complexa, e
com uma boa escalabilidade, o que e de vital importancia nesse projeto em que a partir
de uma linguagem simples foi feita a expansao da linguagem ate a obtencao da estrutura
completa da linguagem. Os automatos foram construıdos com base na simplificacao da
gramatica. Eles representam os sımbolos nao terminais determinados anteriormente.
O automato da figura 4.5 corresponde ao sımbolo Algoritmo. Esse e o automato prin-
cipal do analisador sintatico, pois descreve a estrutura principal da gramatica da linguagem
proposta. As transicoes COMANDO representadas pelas setas tracejadas descrevem a
chamada do sub-automato Comando mostrado na figura 4.6. Esse automato foi cons-
truıdo com base na expressao simplificada do sımbolo nao terminal Comando.
Quando ocorre a chamada de submaquina, a aplicacao empilha o proximo estado da
submaquina que fez a chamada e faz o tratamento da submaquiina. Por exemplo na
transicao Q13 → Q16 da submaquina A1, o estado Q16 e empilhado. A aplicacao vai
para o estado Q2, 1 da submaquina A2, percorre o submaquina e quando chega a um dos
estados finais, retorna a submaquina A1 para o estado empilhado - no exemplo citado o
estado Q16 - o desempilha e continua a analise.
O analisador sintatico percorre as submaquinas utilizando como transicao os tokens
contidos na tabela de sımbolos. O objetivo e que o analisador percorra as submaquinas ate
que se chegue ao estado final da submaquina principal - a submaquina A1 - com a pilha
vazia. Se isso ocorrer sem que o analisador encontre erros sintaticos, pode-se considerar
que a analise foi realizada com sucesso.
O analisador deve ser capaz de, ao encontrar um erro, ressincronizar-se e continuar
Analisador Sintático 35
Figura 4.5: Automato Algoritmo
Analisador Sintático 36
Figura 4.6: Automato Comando
a analise. No analisador desenvolvido nesse trabalho, isso e feito quando ha transicoes
simples nos automatos, ou seja, quando um estado pode fazer transicao para apenas um
estado. Nesses casos, quando o token encontrado nao satisfaz a transicao esperada pelo
analisador, e gerada uma mensagem de erro para o usuario e o analisador passa para o
estado seguinte, continuando a analise.
4.3.3 Formalizacao
Construıdos os automatos a serem utilizados na implementecao do analisador sintatico,
foi feita a formalizacao utilizando as convencoes para formalizacao de automatos de pilha
estruturado descritos no capıtulo 3.
Nessa fase do trabalho foram criadas duas particoes p1 e p2, demonstradas no codigo
4.3.2, referentes aos automatos A1 e A2 respectivamente.
Observando a transicao p1,5 nota-se ha a chamada de submaquina para o automato
A2. O estado q1,6 e empilhado e e iniciada a rotina da particao p2. Quando se chega
ao estado final do automato referente a particao p2, o estado q16 e desempilhado e a
rotina retorna ao automato A1. Esses dois tipos de transicao – de chamada e retorno de
submaquina – movimentam a pilha de modo que ao chamar uma submaquina, o analisador
possa prosseguir com a analise do ponto correto apos a analise na submaquina. As demais
transicoes tratam de transicoes internas as submaquina. Elas nao movimentam a pilha,
mas consomem sımbolos da cadeia de entrada ao contrario das transicoes de chamada e de
retorno de submaquina, que sempre sao feitas em vazio.
Essa formalizacao define a estrutura do algoritmo a ser implementado. A partir dessa
formalizacao inicia-se a implementacao do analisador sintatico em Java.
Analisador Sintático 37
p1{
p1,1:q1,1 algoritmo -> q1,2p1,2: q1,2 id -> q1,3p1,4:q1,3 tipo -> q1,4p1,5:q1,3 a2 -> empilha q1,6p1,6:q1,4 id ->q1,5p1,7:q1,5 ,-> q1,4p1,8:q1,5 ; -> q1,3p1,9:q1,6 fimalgoritmo ->q1,7p1,10:q1,6 a2 -> empilha q1,6p1,11:q1,7 # -> retorna
}
p2{
p2,1:q2,1 id-> q2,2p2,2:q2,1 escreva->q2,6p2,3:q2,2 := ->q2,3p2,4:q2,3 id ->q2,4p2,5:q2,3 numero->q2,4p2,6:q2,4 ; ->q2,5p2,7:q2,5 # ->retornap2,8:q2,6 (->q2,7p2,9:q2,7 numero->q2,8p2,10:q2,7 id -> q2,8p2,11:q2,8 )->q2,9p2,12:q2,9 ; -> q2,10p2,13:q2,10 # -> retorna
}
Codigo 4.3.2: Regras de transicao para os automatos dos analisadores sintaticos
4.3.4 Implementacao do Analisador Sintatico em Java
Para o analisador sintatico foi criada a classe sintatico e a base dessa classe e o metodo
analisa() representada no codigo 4.3.3. Este metodo recebe como entrada o token, seu
respectivo valor e a linha onde situa-se no codigo fonte.
Este metodo segue cada passo das regras de transicao dos automatos. Cria-se um
switch() onde e passado como parametro o automato e dentro de cada case um outro
switch(), este recebendo como parametro os estados de cada automato. Desse modo na
medida que a linguagem e expandida, e novas transicoes nos automatos forem surgindo,
as mudancas no codigo podem simplesmente representar um novo case tornando a imple-
mentacao da aplicacao menos complexa.
O metodo analisa os tokens e/ou seus respectivos valores, verificam se satisfazem as
regras de transicao. Se o fazem, o algoritmo passa para o proximo estado, senao emite
mensagem de erro e, nos casos menos complexos, faz a ressincronizacao do analisador e
continua a analise. No trecho mostrado ha um caso de ressincronizacao. O analisador
Junção dos Analisadores 38
1 void analisa(String token, String valor,String linha) throws IOException2 {3 ...4 switch(this.maquina)5 {6 //AUTOMATO PROGRAMA7 case 1:8 {9 switch(this.estado)
10 {11 case 1:12 if(token.equals("algoritmo"))13 {14 this.estado=2;15 return;16 }17 else18 {19
20 this.emiteErro(1, linha, " ","");21 this.estado=2;22 this.analisa(token, valor,linha);23 //this.erro++;24 return;25 }26 ...27
28 }29 }
Codigo 4.3.3: Metodo analisa()
verifica o erro, simula a satisfacao da condicao passando para o que seria o proximo estado
e continua a analise. Se ao fim da analise o codigo chegar ao estado final do automato com
a pilha vazia e o algoritmo e nao apresentar erros de sintaxe, entao pode-se considerar que
o codigo satisfaz a estrutura do codigo.
4.4 Juncao dos Analisadores
As classes lex e sintatico, sao chamadas na classe principal denominada Portugol
demonstrada no codigo 4.4.1. A classe principal recebe como parametro o arquivo fonte
escrito na linguagem proposta. A aplicacao executa o analisador lexico, e a partir dos
arquivos gerados pela analise lexica, inicia a analise sintatica.
Na classe principal, e executado primeiro o analisador lexico. E criado um objeto da
classe Lex e e feita a execucao do metodo analisar() da classe lex, cujo funcionamento
e descrito na secao 4.2.2. Esse metodo e aplicado ao arquivo fonte que e passado como
parametro da aplicacao. Feita a analise lexica e criado um objeto da classe sintatico
instanciado com automato 1, estado 1. A partir daı e iniciada a analise sintatica que
Junção dos Analisadores 39
1 import java.io.BufferedReader;2 import java.io.FileReader;3 import java.util.StringTokenizer;4
5 class Portugol6 {7 public int maxId;8
9 public static void main (String args[])10 {11 lex objLexico = new lex();12
13 objLexico.analisar(args[0]);14 //inicializa do analisador em seu estado inicial15 sintatico analisador=new sintatico(1,1);16
17 try18 {19 //abre o arquivo com os tokens gerados pelo analisador lexico para a20 //anse sintca21 BufferedReader in = new BufferedReader(new FileReader("tokens.txt"));22 String lido;23 String Token;24 String Valor;25 String Linha;26 //percorre cada linha do arquivo de tokens27 while ((lido = in.readLine()) != null)28 {29 StringTokenizer a = new StringTokenizer(lido);30 Token=a.nextToken();31 Valor=a.nextElement().toString();32 Linha=Character.toString(lido.charAt(lido.length()-1));33
34 analisador.analisa(Token, Valor,Linha);35 }36 }37 catch (Exception e) {38 e.printStackTrace();39 }40 }41 }
Codigo 4.4.1: Primeira versao da classe Portugol
recebe como entrada o arquivo tokens.txt que contem a tabela de sımbolos gerada pela
analise lexica.
Para executar a aplicacao basta entrar no diretorio onde encontra-se as classes da
aplicacao, e executar o comando:
java Portugol <arquivo-fonte>
Um exemplo de execucao da aplicacao e mostrado na figura 4.7 para o algoritmo usado
como base para essa primeira versao.
A figura 4.8 mostra o arquivo gerado pela analise lexica que contem os tokens e suas
informacoes na exata ordem em que eles sao identificados no arquivo fonte. Cada linha
Junção dos Analisadores 40
Figura 4.7: Exemplo de execucao da primeira versao da aplicacao para o algoritmo usadocomo base para a linguagem
desse arquivo e composta pelas informacoes de cada token dispostas na seguinte ordem:
token, valor, linha do arquivo fonte onde e encontrado.
Figura 4.8: Exemplo de arquivo com a tabela de sımbolos gerado pela analise lexica
A figura 4.9 mostra o arquivo contendo as informacoes referentas aos identificadores
encontrados no arquivo fonte. Esse arquivo e util, quando se precisa das informacoes
Expansão da Linguagem 41
relativas aos lexemas de tais identificadores. Cada linha desse arquivo corresponde a um
identificador contendo seu valor e o lexema respectivamente.
Figura 4.9: Exemplo de arquivo gerad na analise lexica com as informacoes dos identifica-dores do codigo fonte
4.5 Expansao da Linguagem
Com a primeira versao do software pronta, foi feita a expansao da linguagem. A
modelagem para a versao final da aplicacao segue a mesma ordem da primeira, sendo
feita a expansao e as modificacoes necessarias em cada fase do projeto. Nessa processo de
expansao da linguagem, demonstra como a abordagem utilizada para a realizacao desse
trabalho simplifica esse processo.
4.5.1 Expansao da Gramatica em EBNF
O primeiro passo da expansao da linguagem foi a expansao da gramatica em EBNF.
Primeiramente definimos todos os comandos da linguagem, a criacao de novos tipos, pa-
lavras reservadas, sinais e sımbolos especiais, bem como a abordagem de expressoes mate-
maticas e logicas.
Nessa nova representacao em EBNF demonstrada no codigo 4.5.1, foram definidas uma
serie de novos comandos. O comandoSe que descreve o comando no qual verifica-se se
uma determinada condicao e satisfeita para a realizacao de um bloco de comandos; os
comandos de repeticao enquanto e para representados na gramatica pelos nao terminais
ComandoEnquanto e ComandoPara e os comandos de leitura e escrita representados
pelos nao terminais Leitura e Escrita, alem do comando de atribuicao ja existente na
primeira versao da gramatica.
Expansão da Linguagem 42
Nessa nova gramatica ha uma serie de chamadas recursivas de sımbolos nao terminais,
como ocorrem na maioria dos comandos e, principalmente, no bloco sintatico expressao.
Abordagem essa que nao havia na primeira versao da linguagem: o tratamento de ex-
pressoes. Ha uma modificacao nas regras para alguns tokens tambem. Os numeros agora
englobam nao apenas valores inteiros, mas tambem numeros reais. Essa situacao e repre-
sentada na gramatica pelo nao terminal Numero. A regra para esse sımbolo no codigo
4.5.1 indica que um numero e um inteiro seguido ou nao por um “.” e um inteiro. Ou
seja, valores como 5.6, 234.456773 sao reconhecidos nessa segunda versao da linguagem.
O numero de sımbolos especiais tambem aumenta consideravelmente nessa nova versao da
gramatica alem do novo tipo logico.
Algoritmo->DeclaracaoAlgoritmo {Declaracao} {Comando}"fim_algoritmo"DeclaracaoAlgoritmo->"algoritmo" IdentificadorDeclaracao-> Tipo Identificador {, Identificador} ";"Tipo->"inteiro"|"real"|"logico"Comando->{Atribuicao|Escrita|Leitura|ComandoSe|ComandoEnquanto|ComandoPara}Atribuicao->Identificador ":=" Expressao1 ";"Escrita->"escreva (" Expressao ");"Leitura->"leia (" Identificador ");"ComandoSe->"se" Expressao "entao" {Comando} [("senao" {Comando})]
"fim_seComandoEnquanto->"enquanto" Expressao "faca" {Comando} "fim_enquanto"ComandoPara->
"para" Identificador "de" Expressao "ate" Expressao "faca{comando}
"fim_para"
Expressao->Expressao1 {"ou" Expressao1|"e" Expressao1}
Expressao1->|Expressao1 ("="|"<>") Expressao1|Expressao1(">"|">="|"<"|"<=") Expressao1|Expressao1("/"|"*"|"%") Expressao1|Expressao1 ("+"|"-") Expressao1|[("+"|"-"|"nao")]Termo
Termo->Literal|"(" Expressao1 ")"|Identificador|LogicoIdentificador-> Letra {Letra|Digito|_}Letra->a|..|z|A|..|zDigito->0|..|9Literal-> Numero|LogicoInteiro->Digito{Digito}Numero->Inteiro["."Inteiro]Logico->"verdadeiro"|"falso"
Codigo 4.5.1: Gramatica da linguagem expandida
Expansão da Linguagem 43
4.5.2 Analise LexicaAutomatos
Para a analise lexica as modificacoes feitas nos automatos para os tokens refletem as
alteracoes feitas na gramatica. O automato Palavra (Figura 4.1) permanece da mesma
forma. As modificacoes foram realizadas apenas nos automatos Numero e Simbolo.
Essas modificacoes sao mostradas na figura 4.10.
Figura 4.10: Segunda versao dos automatos para os tokens
O automato Numero possui dois novos estados devido ao fato de nessa segunda versao
os analisadores reconhecerem numeros inteiros e reais. O automato Simbolo teve uma
modificacao maior, pois o numero de sımbolos especiais aumentou consideravelmente na
versao final da linguagem, possuindo agora sinais matematicos e operadores logicos. A
transicao de q0 para q2 no automato Simbolo da figura 4.10 representa os operadores
matematicos, os parenteses e o comparador “=”. As transicoes q0->q1->q2 represen-
tam o sinal de atribuicao “:=”. As demais transicoes levam ao reconhecimento de sinais
de comparacao: > ( q0->q3 ), >= ( q0->q3->q2 ), < ( q0->q4 ), <=, <> (os
Expansão da Linguagem 44
dois ultimos nas transicoes q0->q4->q2 ).
Feitas as alteracoes nos automatos isolados, bastou aplica-las para a juncao dos auto-
matos, demonstrado na figura 4.11.
Figura 4.11: Segunda versao da juncao dos automatos
A juncao dos automatos segue a mesma ideia da primeira versao, apenas utilizando as
alteracoes feitas em cada automato. Depois da juncao, faz-se novamente a eliminacao dos
estados finais e constroi-se o automato da figura 4.12 a ser implementado no analisador
lexico.
O automato da figura 4.12 leva em consideracao as mesmas situacoes do automato da
primeira versao (figura 4.3), como o fato de tokens diferentes nao separados por espacos
em branco ou quebras de linha. Porem como o numero de sımbolos especiais e maior, as
transicoes de um token para outro ocorre mais vezes nessa nova versao do automato para
o analisador lexico. Por exemplo, do estado q1, ha alem da transicao para o estado inicial
que representa o espaco em branco, quebra de linha ou outra situacao diferente da cadeia
que representa uma palavra, ha transicoes para os estados q5, q6 e q7, transicoes nas
quais o analisador devera guardar as informacoes da cadeia de caracteres que formou uma
palavra, realizar a analise e se for o caso guardar as informacoes no array de sımbolos, e
Expansão da Linguagem 45
Figura 4.12: Automato para a Segunda Versao do Analisador Lexico
a partir de um desses estados para os qual houver a transicao, continuar a analise para
um novo token. Esse tipo de situacao ocorre tambem no caso de numeros seguidos de um
sımbolo especial, como em expressoes matematicas por exemplo.
Implementacao do Analisador Lexico em Java
Na implementacao do analisador as mudancas foram feitas com base nos automatos e
a segunda versao da gramatica. O primeiro passo foi a alteracao nos arrays contendo as
palavras reservadas e os sımbolos especiais da linguagem na classe scanner1. Nessa classe
foi criada um metodo onde e feito o tratamento dos tipos logicos e no metodo numero(),
para que este aceite o novo formato para os numeros na linguagem contendo numeros reais.
Na classe lex foram acrescentadas as transicoes do automato lexico.
Feitas essas modificacoes o analisador lexico esta pronto para aceitar os novos padroes
Expansão da Linguagem 46
da linguagem definidos na nova gramatica.
Uma saıda para o analisador lexico para o codigo 4.5.2 e mostrado na figura 4.13. Nesse
arquivo pode-se observar novas palavras reservadas, como os tipos real e logico e valores
reais como o lexema 5.7 reconhecido no padrao do token numero.
1 algoritmo teste2 real b;3 logico c;4
5 b:=5.7;6 c:=falso;7 fimalgoritmo
Codigo 4.5.2: Algoritmo contendo os padroes da segunda versao da linguagem
Figura 4.13: Arquivo contendo as informacoes dos tokens do codigo 4.5.2
4.5.3 Analise SintaticaSimplificacao da gramatica em EBNF
O processo de simplificacao e o mesmo utilizado na secao 4.3. Enumerando as regras da
gramatica que correspondem aos comandos da linguagem tem-se o codigo 4.5.3. Os sımbo-
los nao terminais de correspondentes a cada comando na regra (1) devem ser substituıdas
pelas regras correspondentes descrita na gramatica.
Expansão da Linguagem 47
(1)Comando->{Atribuicao|Escrita|Leitura|ComandoSe}(2)Atribuicao->Identificador ":=" Expressao1 ";"(3)Escrita->"escreva (" Expressao ");"(4)Leitura->"leia (" Identificador " ["," Identifiador]);"(5)ComandoSe-> "se" Expressao "entao" {Comando} [("senao"{Comando})]
"fim_se"(6)ComandoEnquanto->"enquanto" Expressao"faca" {Comando} "fim_enquanto"(7)ComandoPara->"para" Identificador "de" Expressao "ate" Expressao
"faca" {comando} "fim_se"
Codigo 4.5.3: Regras da segunda versao da gramatica enumerada
Substituindo (2), (3), (4), (5), (6), (7) em (1) no codigo 4.5.3 obtem-se a regra para o
sımbolo nao terminal comando descrito no codigo 4.5.4.
Comando->{Identificador ":=" Expressao1 ";"|"escreva (" Expressao ["," Expressao]");"|"leia (" Identificador ");"|"se" Expressao"entao" {Comando}[("senao" {Comando})]"fim_se"|"enquanto" Expressao "faca"{Comando} "fim_enquanto"|"para" Identificador "de" Expressao "ate" Expressao "faca"{comando} "fim_para"}
Codigo 4.5.4: Regra que define o nao terminal Comando
O mesmo processo e feito para o nao terminal Expressao e Expressao1 que corres-
ponde as expressoes da linguagem. Os sımbolos nao terminais Expressao e Literal sao
descritos no codigo 4.5.5.
(1)Expressao->Expressao ("="|"<>") Expressao|Expressao (">"|">="|"<"|"<=") Expressao|Expressao ("+"|"-") Expressao|Expressao("/"|"*"|"%") Expressao|[("+"|"-"|"nao")]Termo
(2)Termo->Literal|"(" Expressao ")"|Identificador
Codigo 4.5.5: Regras que define os nao termnais Expressao e Literal
Substituindo (2) em (1) obtem-se a definicao para Expressao1 descrito no codigo 4.5.6.
Essa definicao sera utilizada para a construcao do automato correspondente as expressoes
logicas e aritmeticas da linguagem.
As expressoes de conjuncao sao representadas pelo nao terminal Expressao As regras
para o nao terminal Algoritmo nao seofreram alteracao na segunda versao da linguagem,
Expansão da Linguagem 48
Expressao->Expressao "ou" Expressao|Expressao "e" Expressao|Expressao ("="|"<>") Expressao|Expressao (">"|">="|"<"|"<=") Expressao|Expressao ("+"|"-") Expressao|Expressao("/"|"*"|"%") Expressao|[("+"|"-"|"nao") Literal|"(" Expressao ")"|Identificador
Codigo 4.5.6: Regra que define o simbolo nao terminal Expressao
pois a estrutura basica da linguagem, permanece a mesma. O que ocorreu foi o aumento
no numero de comandos e a existencia de expressoes. Com a definicao das regras para cada
um desses grupos sintaticas o proximo passo foi fazer as alteracoes no automato A1 que
representa os comandos da linguagem, e a criacao dos automatos que representam o grupo
sintatico Expressao e Expressao1 que define o comportamento sintatico das expressoes
da linguagem. O automato principal permanece o mesmo da primeira versao.
Criacao dos Automatos
Seguindo a definicao nas regras da gramatica, criou-se a nova versao do automato Co-
mando. A figura 4.14 mostra que as alteracoes no automato em relacao a primeira versao,
se resumem a novas transicoes a partir do estado inicial para cada comando adicionado.
Outro ponto a se observar, e o aumento no numero de transicoes de chamada de subma-
quina, tanto recursivas, onde o automato chama a si mesmo - transicoes definidas como
COMANDO-, como as chamadas para o automato Expressao.
A figura 4.15 demonstra o automato montado a partir da regra do sımbolo nao terminal
Expressao. Esse automato define o comportamento das expressoes matematicas e logicas
da linguagem.
Feitos os automatos, a proxima etapa foi a formalizacao dos mesmos. O processo
e o mesmo da primeira versao, levando agora em consideracao o automato expressao.
Bem como as novas transicoes do automato comando. Os codigos 4.5.7, 4.5.8, 4.5.10,
demonstram a formalizacao contendo todas as transicoes das submaquinas do analisador
sintatico. Cada linha desses codigos representam uma transicao do respectivo automato
seguindo as convencoes descritas na secao 3.2 do capıtulo 3.
Expansão da Linguagem 49
Figura 4.14: Versao final do automato Comando
Expansão da Linguagem 50
Figura 4.15: Automatos Expressao e Expressao1
p1{
p1,1:q1,1 algoritmo -> q1,2p1,2: q1,2 id -> q1,3p1,3:q1,3 tipo -> q1,4p1,4:q1,3 a2 -> empilha q1,6p1,5:q1,4 id ->q1,5p1,6:q1,5 ,-> q1,4p1,7:q1,5 ; -> q1,3p1,8:q1,6 fimalgoritmo ->q1,7p1,9:q1,6 a2 -> empilha q1,6p1,10:q1,7 # -> retorna
}
Codigo 4.5.7: Formalizacao do automato Algoritmo
Expansão da Linguagem 51
p2{
p2,1:q2,1 id-> q2,2p2,2:q2,1 escreva->q2,6p2,3:q2,1 leia->q2,11p2,4:q2,1 se->q2,16p2,5:q2,1 enquanto->q2,25p2,6:q2,1 para->q2,32p2,7:q2,2 := ->q2,3p2,8:q2,3 a3 ->empilha q2,4p2,9:q2,4 ;->q2,5p2,10:q2,5 # ->retornap2,11:q2,6 (->q2,7p2,12:q2,7 a3 ->empilha q2,8p2,13:q2,8 ,->q2,7p2,14:q2,8 )->q2,9p2,15:q2,9 ; -> q2,10p2,16:q2,10 # -> retornap2,17:q2,11 (->q2,12p2,18:q2,12 id->q2,13p2,19:q2,13 ,->q2,12p2,20:q2,13 )->q2,14p2,21:q2,14 ; ->q2,15p2,22:q2,15 #->retornap2,23:q2,16 a3->empilha q2,19p2,24:q2,19 entao ->q2,20p2,25:q2,20 a2 ->empilha q2,21p2,26:q2,21 a2 ->empilha q2,21p2,27:q2,21 fim_se ->q2,24p2,28:q2,21 senao ->q2,22p2,29:q2,22 a2 ->empilha q2,23p2,30:q2,23 a2 ->empilha q2,23p2,31:q2,23 fim_se ->q2,24p2,32:q2,24 # ->retornap2,33:q2,25 a3 ->empilha q2,28p2,34:q2,28 faca -> q2,29p2,35:q2,29 a2 -> empilha q2,30p2,36:q2,30 a2 -> empilha q2,30p2,37:q2,30 fim_enquanto -> q2,31p2,38:q2,31 # -> retornap2,39:q2,32 id -> q2,33p2,40:q2,33 de -> q2,34p2,41:q2,34 a3 -> empilha q2,35p2,42:q2,35 ate -> q2,36p2,43:q2,36 a3 -> empilha q2,37p2,44:q2,37 faca -> q2,38p2,45:q2,38 a2 -> empilha q2,39p2,46:q2,39 a2 -> empilha q2,39p2,47:q2,39 fim_para -> q2,40p2,48:q2,40 # -> retorna
}
Codigo 4.5.8: Formalizacao do automato Comando
p3{
p3,1:q3,1 a4 -> empilha q3,2p3,2:q3,2 e-> q3,1p3,3:q3,2 ou-> q3,1p3,4:q3,2 #-> retirna
}
Codigo 4.5.9: Formalizacao do automato Expressao
Expansão da Linguagem 52
p4{
p4,1:q4,1 nao-> q4,1p4,2:q4,1 +-> q4,1p4,3:q4,1 --> q4,1p4,4:q4,1 id-> q4,2p4,5:q4,1 numero> q4,2p4,6:q4,1 logico-> q4,2p4,7:q4,1 (-> q4,3p4,8:q4,2 + -> q4,1p4,9:q4,2 - -> q4,1p4,10:q4,2 * -> q4,1p4,11:q4,2 / -> q4,1p4,12:q4,2 % -> q4,1p4,13:q4,2 > -> q4,1p4,14:q4,2 >= -> q4,1p4,15:q4,2 < -> q4,1p4,16:q4,2 <= -> q4,1p4,17:q4,2 = -> q4,1p4,18:q4,2 <> -> q4,1p4,19:q4,2 # -> retornap4,20:q4,3 a4 ->empilha q4,4p4,21:q4,4 ) -> q4,2
}
Codigo 4.5.10: Formalizacao do automato Expressao1
Capıtulo 5
Conclusao
Nesse trabalho foram desenvolvidos analisadores lexico e sintatico em Java. Eles sao multi-
plataforma e rodam sobre a maquina virtual do Java, fazendo-se necessaria para seu uso
apenas a instalacao da maquina virtual no computador em que os analisadores forem
usados.
A necessidade de desenvolver essa aplicacao em Java deu-se devido ao fato de o software
ser multi-plataforma. Porem, com a estrutura do projeto pronto, esse trabalho poderia
ser feito utilizando outras linguagens estruturadas sem grandes transtornos, ja que a fase
fundamental para o andamento do projeto e a sua modelagem em si, utilizando EBNF e
automatos. Um ponto que descreve bem essa situacao e o fato de inicialmente o projeto ter
sido desenvolvido para uma versao mais compacta da linguagem alcancada nesse projeto.
Para fazer a expansao da linguagem bastou utilizar a modelagem feita para a primeira
versao e fazer as modificacoes adequadas, porem as caracterısticas do projeto ja estavam
definidas.
Futuramente se forem feitas otimizacoes na aplicacao e melhorias na linguagem estima-
se que a complexidade para que isso seja feito seja equivalente a da expansao da primeira
versao da linguagem para a alcancada nesse trabalho.
Pode-se concluir que o desenvolvimento do projeto utilizando como base fundamental
a utilizacao de automatos agiliza e torna menos complexo o desenvolvimento de aplicacoes
desse tipo, podendo a partir desse trabalho passar ao desenvolvimento de analisadores e
compiladores para outras linguagens.
O codigo da aplicacao e aberto, de modo que para trabalhos futuros espera-se:
• modificacoes e/ou melhorias na aplicacao;
54
• desenvolvimento um compilador ou um interpretador para a linguagem Portugol;
• com base nos metodos de desenvolvimento de compiladores utilizados nesse trabalho,
sejam feitos outros projetos para compiladores ou analisadores para outras linguagens.
Referencias Bibliograficas
[Aho et al.2007] Aho, A. V., Lam, M. S., Sethi, R., and Ullman, J. D. (2007). Compilers:
Principles, Techniques and Tools. Greg Tobin, second edition.
[da Silva2010a] da Silva, S. R. B. (2010a). Compiladores -analise lexica.
[da Silva2010b] da Silva, S. R. B. (2010b). Compiladores -analise sintatica.
[Guimaraes et al.1985] Guimaraes, de Moura, A., and de Castilho Lages, N. A. (1985).
Algoritmos e Estruturas de Dados. Livros Tecnicos e Cientıficos, first edition.
[Holb1994] Holb, A. (1994). Compiler Design in C. Prentice Hall, second edition.
[Informatica2010] Informatica, A. (2010). Visualg em:
<http://www.apoioinformatica.com> acesso em: 17 jun.
[Ramos et al.2009] Ramos, M., Neto, J., and Italo Vega (2009). Linguagens Formais.
Bookman Companhia Ed, first edition.
[Ricarte2008] Ricarte, I. (2008). Introducao a Compilacao. Elsevier, first edition.
[Silva2010a] Silva, T. (2010a). Gportugol em: http://gpt.berlios.de/site/> acesso em: 17
mar.
[Silva2010b] Silva, T. (2010b). Gramatica portugol em:
<http://gpt.berlios.de/manualnodes/node7.html > acessoem : 17mar.
[Tucker and Noonan2009] Tucker, A. B. and Noonan, R. (2009). Linguagens de Programacao
- Princıpios e Paradigmas. McGrawHill, second edition.