Escolar Documentos
Profissional Documentos
Cultura Documentos
Sumrio
Introduo
1.1
Linguagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
O que um Compilador? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3
1.4
Organizao de um Compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.1
Anlise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2
Sntese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5
1.6
1.7
Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.8
Concluso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Anlise Lxica
10
2.1
10
2.1.1
12
17
2.2.1
Expresses Regulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
2.2.1.1
Expresses bsicas . . . . . . . . . . . . . . . . . . . . . . . . . .
18
2.2.1.2
Caracteres especiais + e ? . . . . . . . . . . . . . . . . . . . . . .
19
2.2.1.3
19
2.2.1.4
20
2.2.1.5
Outras caractersticas . . . . . . . . . . . . . . . . . . . . . . . .
20
2.2.1.6
Alguns exemplos . . . . . . . . . . . . . . . . . . . . . . . . . .
20
2.3
21
2.4
Uso do flex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2.4.1
Formato da entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2.4.2
22
2.4.3
25
2.2
ii
2.6
3
30
30
2.5.1
A Linguagem Mini C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
2.5.2
31
Concluso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
Anlise Sinttica
38
3.1
Estrutura sinttica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
3.1.1
rvores de expresso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
3.2
41
3.3
42
3.3.1
Exemplo: Palndromos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
3.3.2
Derivao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
3.3.3
45
3.3.4
rvores de Derivao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
3.3.5
Ambiguidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
3.3.6
49
49
3.4
A Instalao de Softwares
51
52
B.1 Captulo 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
52
B.1.1
exp_lexer.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
B.1.2
simples.ll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
B.1.3
exp_flex/Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
B.1.4
exp_flex/exp.ll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
B.1.5
exp_flex/exp_tokens.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
B.1.6
exp_flex/exp_flex.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
B.1.7
minic/minic_tokens.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
B.1.8
minic/lex.ll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
B.1.9
minic/lex_teste.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
ndice Remissivo
67
iii
Prefcio
BAIXANDO A VERSO MAIS NOVA DESTE LIVRO
Acesse https://github.com/edusantana/compiladores-livro/releases para verificar se h
uma verso mais o Histrico de revises, na incio do livro, para verificar o que mudou
entre uma verso e outra.
Caixas de dilogo
Nesta seo apresentamos as caixas de dilogo que podero ser utilizadas durante o texto. Confira os
significados delas.
Nota
Esta caixa utilizada para realizar alguma reflexo.
Dica
Esta caixa utilizada quando desejamos remeter a materiais complementares.
iv
Importante
Esta caixa utilizada para chamar ateno sobre algo importante.
Cuidado
Esta caixa utilizada para alertar sobre algo que exige cautela.
Ateno
Esta caixa utilizada para alertar sobre algo potencialmente perigoso.
Os significados das caixas so apenas uma referncia, podendo ser adaptados conforme as intenes
dos autores.
Vdeos
Os vdeos so apresentados da seguinte forma:
Compreendendo as referncias
As referncias so apresentadas conforme o elemento que est sendo referenciado:
Referncias a captulos
Prefcio [iv]
Referncias a sees
Como voc deve estudar cada captulo [iv], Caixas de dilogo [iv].
Referncias a imagens
Figura 2 [vii]
Nota
Na verso impressa, o nmero que aparece entre chaves [ ] corresponde ao nmero da
pgina onde est o contedo referenciado. Na verso digital do livro voc poder clicar no
link da referncia.
Feedback
Voc pode contribuir com a atualizao e correo deste livro. Ao final de cada captulo voc ser
convidado a faz-lo, enviando um feedback como a seguir:
Feedback sobre o captulo
Voc pode contribuir para melhoria dos nossos livros. Encontrou algum erro? Gostaria de
submeter uma sugesto ou crtica?
Para compreender melhor como feedbacks funcionam consulte o guia do curso.
Nota
A seo sobre o feedback, no guia do curso, pode ser acessado em: https://github.com/edusantana/guia-geral-ead-computacao-ufpb/blob/master/livro/capitulos/livroscontribuicao.adoc.
vi
vii
Captulo 1
Introduo
O BJETIVOS DO CAPTULO
Ao final deste captulo voc dever ser capaz de:
Entender a funo e a estrutura geral de um compilador
Diferenciar interpretadores de compiladores
Compreender os motivos por que se estuda os compiladores
Este captulo uma introduo aos compiladores: o que so, para que servem, e como so organizados. Tambm discutimos para qu se aprende sobre compiladores e onde esse conhecimento pode ser
til. Compiladores so, essencialmente, tradutores de linguagens de programao. Por isso, vamos
comear a discusso falando sobre linguagens de programao em geral.
1.1
Linguagens
linguagem
O que uma linguagem? Deixamos para os linguistas e filsofos a definio geral do que vem a
ser uma linguagem, nos seus vrios sentidos. Aqui nos preocupamos apenas com as linguagens de
programao, e daqui em diante quando se falar em linguagem ser entendido que uma linguagem
de programao; quando for preciso tratar de outro tipo de linguagem, isso estar explcito no texto.
Um programa uma seqncia de instrues que devem ser executadas por um computador. Em outras palavras, um programa especifica um algoritmo de maneira executvel. Uma linguagem de programao uma notao para escrever programas. Enquanto as linguagens naturais, como portugus,
so notaes para comunicao entre pessoas, as linguagens de programao existem, a princpio,
para que o programador comunique ao computador as tarefas que devem ser realizadas. A linguagem
deve ser, portanto, precisa; o computador no pode fazer julgamentos e resolver ambiguidades.
importante notar tambm que um programa freqentemente um instrumento de comunicao entre
programadores: comum que um deles tenha que ler e entender programas escritos por outro. Alguns
importantes cientistas da computao, alis, defendem que a comunicao entre programadores o
objetivo primrio de um programa, a sua execuo sendo praticamente um efeito colateral. Donald
Knuth sugeriu que programao a arte de dizer a outra pessoa o que se quer que o computador faa.
impossvel estudar compiladores sem estudar as linguagens de programao. J o contrrio possvel: podemos estudar linguagens sem conhecer nada sobre compiladores. Desta forma, as linguagens
1 / 68
1.2
O que um Compilador?
Como vimos, uma linguagem de programao uma notao para escrever programas. Em geral,
programas so escritos por pessoas para serem executados por computadores. Mas pessoas e computadores funcionam de forma diferente, o que leva existncia de linguagens de programao com
diferentes nveis. Os processadores que executam os programas de computador normalmente executam instrues simples e elementares. As linguagens de baixo nvel so aquelas mais prximas das
linguagens dos processadores. Essas linguagens, entretanto, so consideradas difceis de programar,
devido grande quantidade de detalhes que precisam ser especificados. Assim, algumas linguagens
foram criadas para tornar mais fcil a tarefa de programao de computadores. Essas linguagens so
chamadas de linguagens de alto nvel.
Para executar programas escritos em uma linguagem de alto nvel, entretanto, preciso traduzir esses programas para uma linguagem de baixo nvel que possa ser executada diretamente por alguma
mquina. O programa que faz essa traduo chamado de compilador.
Portanto, um compilador um programa que traduz programas escritos em uma linguagem, chamada
de linguagem-fonte, para outra linguagem, a linguagem-destino. Normalmente, a linguagem-fonte
uma de alto nvel, e a linguagem de destino uma linguagem de mquina de algum processador, ou
algum outro tipo de linguagem de baixo nvel que seja executada diretamente por uma plataforma
existente. O diagrama na Figura 1.1 [2] resume essa estrutura bsica.
programa
fonte
compilador
programa
destino
1.3
Mais uma vez, um compilador um tradutor cujo objetivo principal transformar um programa para
uma forma diretamente executvel. Esta no a nica maneira de executar programas em linguagens
de alto-nvel: uma alternativa traduzir e executar ao mesmo tempo. o que fazem os interpretadores. Um interpretador puro tem que analisar e traduzir o programa-fonte toda vez que ele precisa ser
executado.
2 / 68
programa
programa
compilador
entrada
interpretador
sada
entrada
executvel
sada
3 / 68
1.4
Organizao de um Compilador
Na Figura 1.1 [2] a estrutura bsica de um compilador apresentada de uma forma muito simplificada. Agora consideramos essa estrutura em maiores detalhes. Em dcadas de desenvolvimento dos
compiladores, estabeleceram-se algumas tradies na forma de estrutur-los. Uma dessas tradies
separar o compilador em duas partes principais: a primeira analisa o programa-fonte para verificar
sua corretude e extrair as informaes necessrias para a traduo; a segunda utiliza as informaes
coletadas para gerar, ou sintetizar, o programa na linguagem de destino. o modelo de anlise e
sntese; a fase de anlise tambm chamada de vanguarda do compilador (front-end) e a de sntese
conhecida como retaguarda (back-end). Isso mostrado na Figura 1.3 [4].
fonte
sntese
anlise
destino
4 / 68
1.4.1
Anlise
caracteres
anlise lxica
tokens
anlise sinttica
rvore sinttica
anlise semntica
rvore com anotaes
gerao de cd. int.
cdigo intermedirio
5 / 68
1.4.2
Sntese
cdigo intermedirio
otimizao de cdigo
cdigo otimizado
gerao de cdigo
cdigo de destino
otimizao
cdigo final
1.5
O estudo das linguagens de programao uma das reas principais da cincia da computao. Como
os compiladores so, a rigor, implementaes de linguagens de programao, sua importncia fica automaticamente estabelecida. As situaes encontradas por cientistas da computao requerem algum
entendimento sobre a implementao das linguagens que ele usa, mesmo que ele nunca implemente
um compilador em sua carreira.
6 / 68
1.6
As tcnicas usadas na implementao dos compiladores encontram aplicao em muitos outros problemas que envolvem a anlise de uma linguagem de entrada ou a traduo de informaes de um
formato para outro.
7 / 68
1.7
Exemplos
Existem vrios compiladores que so utilizados no dia-a-dia pelos programadores. Para as linguagens
C e C++ a coleo de compiladores GCC (GNU Compiler Collection) muito utilizada, sendo o
compilador padro em muitas IDEs de programao como o Dev-C++.
Na plataforma Windows, a ferramenta de programao nativa mais utilizada a IDE Visual Studio,
que inclui compiladores para vrias linguagens: para C e C++ o compilador usado o cl.exe,
enquanto que para C# o compilador o csc.exe.
A linguagem Java possui um sistema de compilao mais complexo, mas o compilador principal
do JDK (Java Development Kit) o javac, que compila cdigo Java para bytecodes da Mquina
Virtual Java. A Mquina Virtual Java traduz o cdigo em bytecodes para cdigo nativo no momento
da interpretao, usando um compilador JIT (Just In Time). Outro compilador comumente usado por
programadores Java o compilador incremental includo como parte da IDE Eclipse.
1.8
Concluso
Este captulo serviu como um primeiro contato com as ideias e tcnicas envolvidas na implementao
de compiladores. Vimos o que so linguagens de programao e o que um compilador, alm da
8 / 68
9 / 68
Captulo 2
Anlise Lxica
O BJETIVOS DO CAPTULO
Ao final deste captulo voc dever ser capaz de:
Entender a funo do analisador lxico dentro de um compilador
Descrever a estrutura lxica de uma linguagem usando expresses regulares
Criar o analisador lxico para uma linguagem usando um gerador de analisadores
Organizao prvia
Para acompanhar as explicaes do captulo recomendado que voc instale o software
flex no seu computador, as intrues de instalao se encontram no Apndice A [51], na
Seo A.1 [51].
A anlise lxica a primeira etapa do compilador, e recebe o arquivo de entrada criado pelo usurio.
O arquivo de entrada geralmente armazenado como uma sequncia de caracteres individuais que
podem ser lidos. A anlise lxica tem como funo agrupar os caracteres individuais em tokens, que
so as menores unidades com significado no programa-fonte. Um token pode ser pensado como sendo
similar a uma palavra.
Podemos fazer uma analogia do processo de anlise do compilador com o ato de ler um texto. Na
leitura, ns no lemos e decodificamos individualmente cada letra; nosso crebro l e processa um
texto uma palavra por vez. Isso comprovado pelo fato que conseguimos entender um texto mesmo
que as palavras tenham erros de ortografia ou mesmo sejam escritas de maneira diferente.
A anlise lxica faz com que as etapas seguintes do compilador possam trabalhar no nvel das palavras, ao invs do nvel dos caracteres individuais. Isso facilita bastante o trabalho das etapas posteriores. Na anlise lxica tambm so realizadas algumas tarefas como remover comentrios dos arquivos
do programa-fonte e registrar em uma tabela os nomes de identificadores usados no programa. Os
detalhes de como isso feito so o tpico deste captulo.
2.1
Como vimos, a anlise lxica agrupa os caracteres do arquivo de entrada (que contm o programafonte) em tokens. Um token similar a uma palavra do texto de entrada e composto por duas partes
principais:
10 / 68
Neste exemplo, todos os tokens de tipo nmero so formados por mais de um caractere, o maior tendo
cinco caracteres (20925). O analisador lxico gera para esse exemplo a seguinte sequncia de tokens:
Tabela 2.1: Sequncia de tokens para o exemplo
Lexema
42
+
(
675
*
31
)
20925
Tipo
Nmero
Operador
Pontuao
Nmero
Operador
Nmero
Pontuao
Operador
Nmero
11 / 68
Valor
42
SOMA
PARESQ
675
MULT
31
PARDIR
SUB
20925
2.1.1
Para uma linguagem simples como a linguagem de expresses aritmtica do exemplo, escrever um
programa que faz a anlise lxica no apresenta grande dificuldade. Nesta seo vamos examinar
as partes mais importantes do analisador lxico para essa linguagem, pois vrios elementos sero
similares para linguagens mais complexas.
O cdigo fonte completo do analisador lxico para a linguagem de expresses pode ser encontrado
no seguinte arquivo:
Cdigo fonte /exp_lexer.c[code/cap2/exp_lexer.c]
Aqui vamos analisar as principais partes deste programa. Comeamos com a definio da estrutura
que vai guardar os tokens:
Definio de estrutura para tokens
typedef struct
{
int tipo;
int valor;
} Token;
Como vimos, um token tem dois campos: o tipo do token e um valor associado. Ambos os campos
so inteiros, ento definimos algumas constantes para representar os valores possveis desses campos.
As primeiras constantes especificam o tipo de token:
Constantes que representam o tipo do token
#define TOK_NUM
#define TOK_OP
#define TOK_PONT
0
1
2
Com relao ao valor, para nmeros o valor do token apenas o valor do nmero encontrado. Para
operadores e pontuao, por outro lado, precisamos apenas de alguns valores para representar os
quatro operadores e dois caracteres de pontuao:
Constantes para operadores e pontuao
#define
#define
#define
#define
SOMA
SUB
MULT
DIV
#define PARESQ
#define PARDIR
0
1
2
3
0
1
12 / 68
A anlise iniciada ao chamar a funo inicia_analise, que estabelece o valor inicial das
variveis globais:
Funo para inicializar a anlise lxica
void inicializa_analise(char *prog)
{
codigo = prog;
tamanho = strlen(codigo);
pos = 0;
}
A funo inicia_analise recebe uma string contendo o cdigo do programa de entrada como
parmetro (prog), e armazena um ponteiro para essa string na varivel global codigo; a funo
tambm estabelece o valor da varivel global tamanho e inicializa a posio atual na anlise com
valor zero.
A anlise lxica em si funciona de maneira incremental: ao invs de analisar todo o cdigo de entrada
de uma vez e retornar todo o fluxo de tokens, a funo de anlise retorna um token de cada vez. Por
isso, o nome da funo que realiza a anlise proximo_token, e ela retorna o prximo token na
sequncia a cada vez que chamada.
Vamos analisar a funo proximo_token por partes. Comeando pelas variveis locais usadas
pela funo:
Funo que realiza a anlise lxica
Token *proximo_token(Token *tok)
{
char c;
char valor[200];
// string para obter valor de um numero
int vpos = 0;
// posicao na string de valor
Como indicado nos comentrios, a string valor usada para determinar o valor de um token de
tipo nmero. Isso necessrio porque a funo de anlise l um caractere do nmero de cada vez; a
varivel vpos usada para guardar a posio atual na string valor. A varivel c, de tipo caractere,
guarda o caractere atualmente sendo lido do cdigo de entrada.
13 / 68
A funo le_caractere uma funo auxiliar que obtm o prximo caractere do programa de
entrada, atualizando a posio atual dentro da string que contm o programa (para detalhes, veja
o cdigo-fonte completo do analisador). O cdigo acima l caracteres da entrada enquanto eles os
caracteres lidos forem de espao (espao em branco, tabulao e caracteres de nova linha), usando a
funo isspace da biblioteca padro da linguagem C. Ao final desse loop, a varivel c vai conter o
primeiro caractere do prximo token.
A funo de anlise deve ento usar esse caractere para determinar que tipo de token est sendo lido,
e continuar de acordo com o tipo. Nessa linguagem possvel determinar o tipo do token olhando
apenas para o seu primeiro caractere, mas em linguagens mais complexas isso geralmente no
possvel.
Se o primeiro caractere do token for um dgito, a anlise determina que o prximo token um nmero.
O processo a seguir ler os prximos caracteres enquanto forem dgitos, armazenando cada dgito
lido na string auxiliar valor. Ao final, o valor do token obtido atravs da converso da string
valor para um nmero inteiro, usando a funo atoi():
Leitura de um token de tipo nmero
if (isdigit(c)) {
tok->tipo = TOK_NUM;
valor[vpos++] = c;
c = le_caractere();
while (isdigit(c)) {
valor[vpos++] = c;
c = le_caractere();
}
// retorna o primeiro caractere que nao eh um digito
// para ser lido como parte do proximo token
pos--;
// termina a string de valor com um caractere 0
valor[vpos] = \0;
// converte string de valor para numero
tok->valor = atoi(valor);
}
A condio no if acima uma forma mais curta de verificar se o caractere um dos operadores, ao
invs de usar quatro comparaes. A constante global ops definida da seguinte forma:
Conjunto de operadores da linguagem
const char *ops = "+-*/";
A funo proximo_token completa, reunindo os trechos vistos de forma separada, pode ser vista
a seguir:
Funo completa que faz a anlise lxica
Token *proximo_token(Token *tok)
{
char c;
char valor[200];
// string para obter valor de um numero
int vpos = 0;
// posicao na string de valor
c = le_caractere();
// pula todos os espacos em branco
while (isspace(c)) {
c = le_caractere();
}
if (isdigit(c)) {
tok->tipo = TOK_NUM;
15 / 68
O cdigo completo do analisador inclui algumas funes de impresso e uma funo principal que
l o programa de entrada a partir do teclado e mostra a sequncia de tokens obtida desta entrada. A
funo principal :
Funo principal do programa de anlise lxica
int main(void)
{
char entrada[200];
Token tok;
printf("Analise Lexica para Expressoes\n");
printf("Expressao: ");
fgets(entrada, 200, stdin);
inicializa_analise(entrada);
printf("\n===== Analise =====\n");
while (proximo_token(&tok) != NULL) {
imprime_token(&tok);
}
printf("\n");
16 / 68
Executando esse programa para a expresso de exemplo que vimos anteriormente, obtemos a seguinte
saida:
Sada para a expresso 42 + (675 * 31) - 20925
Analise Lexica para Expressoes
Expressao: 42 + (675 * 31) - 20925
=====
Tipo:
Tipo:
Tipo:
Tipo:
Tipo:
Tipo:
Tipo:
Tipo:
Tipo:
Analise =====
Numero
-Operador
-Pontuacao -Numero
-Operador
-Numero
-Pontuacao -Operador
-Numero
--
Valor:
Valor:
Valor:
Valor:
Valor:
Valor:
Valor:
Valor:
Valor:
42
SOMA
PARESQ
675
MULT
31
PARDIR
SUB
20925
A sada est de acordo com o que esperamos da anlise lxica dessa linguagem, como pode ser visto
na Tabela Tabela 2.1 [11].
Vimos que para uma linguagem simples como a de expresses, fcil criar diretamente o analisador
lxico necessrio. Entretanto, medida que a estrutura da linguagem se torna mais complexa (como
ocorre nas linguagens de programao real), a complexidade do analisador lxico vai crescendo e se
torna difcil criar o analisador lxico sem ter alguma tcnica sistemtica para lidar com a complexidade.
As tcnicas que usaremos para isso so relacionadas a uma classe de linguagens formais conhecida
como linguagens regulares. Essas tcnicas so fundamentadas em uma teoria bem desenvolvida, e
contam com ferramentas que automatizam a maior parte do processo de anlise lxica.
2.2
2.2.1
Expresses Regulares
As expresses regulares descrevem padres simples de texto de forma compacta e sem ambiguidade.
Por exemplo, o padro que descreve todas as strings formadas com caracteres a e b que comeam
com a e terminam com b pode ser escrito como a expresso regular a(a|b)*b (a construo dessa
expresso ser explicada em breve).
Existem vrias sintaxes e representaes diferentes para expresses regulares, dependendo da linguagem ou biblioteca utilizada. Como vamos utilizar o gerador de analisadores flex, usaremos aqui a
sintaxe usada nessa ferramenta.
2.2.1.1
Expresses bsicas
Cada expresso regular (ER) uma string que representa um conjunto de strings; tambm podemos
dizer que uma ER representa um padro que satisfeito por um conjunto de strings.
A maioria dos caracteres representam eles mesmos em uma expresso regular. Por exemplo, o caractere a em uma ER representa o prprio caractere a. A ER a representa um padro que poderia
ser descrito em portugus como o conjunto de strings que possuem um caractere a. Obviamente
s existe uma string dessa forma: a string "a". Colocando um padro aps o outro realiza a concatenao dos padres. Comeando com caracteres simples, se juntarmos um a e um b formamos a
expresso ab, que representa a string que contm um a seguido por um b, ou seja, a string "ab".
Mas o poder das Expresses Regulares vem de alguns caracteres que no representam eles mesmos;
esses so caracteres especiais. Um caractere especial bastante usado o *, que representa zero ou
mais repeties de um padro. Por exemplo, a expresso a* representa strings com zero ou mais
caracteres a. A string vazia satisfaz esse padro e corresponde a zero repeties; outras strings
satisfeitas pelo padro so "a", "aa", "aaa", etc. O asterisco representa zero ou mais repeties
do padro que vem antes, no s de um caractere: a expresso (ab)* representa , "ab", "abab",
"ababab", etc. Mas pelas regras de precedncia das expresses, ab* o mesmo que a(b*), que
representa um a seguido por zero ou mais caracteres b, e no igual a (ab)*.
Outro caractere especial importante a barra vertical |, que representa opes nas partes de um
padro. Por exemplo a|b representa a ou b, ou seja, as strings "a" e "b".
Isso nos leva ao exemplo apresentado antes: a(a|b)*b uma expresso regular formada por trs
partes concatenadas em sequncia: a, depois (a|b)* e por fim b. Isso significa que uma string que
satisfaz essa expresso deve comear com um caractere a, seguido por caracteres que satisfazem o
padro (a|b)* e terminando com um caractere b. O padro (a|b)* satisfeito por zero ou mais
repeties do padro (a|b), que por sua vez um padro que satisfeito por caracteres a ou b.
18 / 68
Caracteres especiais + e ?
Ja vimos que o caractere especial * representa zero ou mais repeties de um padro. O caractere
especial + similar, mas representa uma ou mais repeties; a nica diferena que o caractere +
causa a obrigatoriedade de pelo menos uma repetio do padro. A expresso a+ representa as strings
"a", "aa", "aaa", etc., sem incluir a string vazia.
O caractere especial ? representa partes opcionais em um padro, ou seja, zero ou uma repetio de
um determinado padro. A expresso b?a+ representa strings com uma ou mais repeties de a,
podendo comear opcionalmente com um b.
2.2.1.3
Importante
Ateno ao uso dos colchetes ([]) nas classes especiais, eles fazem parte da definio
da classe. Quando utilizamos as classes especiais dentro de um intervalo ns teremos
dois colchetes. Por exemplo, a expresso para nmeros de 0 a 7 ou letras maisculas :
[0-7[:upper]].
Um outro tipo de caracteres especiais so os metacaracteres. Um metacaractere um caractere especial que pode representar outros caracteres. O exemplo mais simples o metacaractere ., que pode
representar qualquer caractere. A expresso a.*b representa strings que comeam com a, terminam
com b e podem ter qualquer nmero de outros caracteres no meio, por exemplo "a0x13b".
As sequncias de escape so iguais as que existem na linguagem C: \n representa um caractere de
nova linha, \t um caractere de tabulao, etc. A barra invertida (\) tambm pode ser usada para
desativar a interpretao especial de um caractere especial. Por exemplo, se quisermos um caractere
+ em uma expresso regular que representa o smbolo de soma, e no a repetio de uma ou mais
vezes, devemos usar \+.
2.2.1.5
Outras caractersticas
Alm das possibilidades de repetio que vimos at agora (zero ou mais vezes, uma ou mais vezes,
zero ou uma vez), possvel na notao do flex ser mais especfico no nmero de repeties. Se
p um padro, p{2} representa exatamente duas repeties do padro, p{2,} representa duas ou
mais repeties, e p{2, 5} representa um nmero de repeties entre duas e cinco, inclusive. A
expresso (la){3} representa trs repeties de la, ou seja, a string "lalala"; e a expresso
(la){1,3} representa as strings "la", "lala" e "lalala".
Dica
No vamos tratar aqui de todos os detalhes das expresses regulares no flex, mas eles
podem ser consultados no manual da ferramenta em http://flex.sourceforge.net/manual/Patterns.html ou atravs do comando info flex nos sistemas Unix.
2.2.1.6
Alguns exemplos
Agora que introduzimos a maior parte das caractersticas das expresses regulares no flex, veremos
alguns exemplos de padres descritos usando essa notao. Depois veremos outros exemplos diretamente ligados anlise lxica de linguagens de programao.
[0-9]{3}\.[0-9]{3}\.[0-9]{3}\-[0-9]{2} um padro que descreve os nmeros de
CPF: trs dgitos ([0-9]{3}) seguidos por um ponto (\.), depois mais trs dgitos e um ponto,
depois mais trs dgitos, um hfen (\-) e finalmente dois dgitos.
20 / 68
2.3
Um gerador de analisadores lxicos um programa que recebe como entrada a especificao lxica
para uma linguagem, e que produz como sada um programa que faz a anlise lxica para essa linguagem. Como vimos anteriormente, possvel escrever o cdigo de um analisador lxico sem o uso
de uma ferramenta, mas usar um gerador de analisadores menos trabalhoso e tem maior garantia de
gerar um analisador lxico correto. A Figura 2.1 [21] mostra um diagrama de blocos que descreve
o uso de um gerador de analisadores (no caso o flex). O gerador recebe uma especificao da estrutura lxica na entrada, e gera um programa analisador (no caso do flex, um programa na linguagem
C). Este programa, quando executado, recebe como entrada os caracteres do programa de entrada, e
produz como sada a sequncia de tokens correspondentes.
especificao
flex
caracteres
analisador
tokens
2.4
Uso do flex
No sistema Unix original foi criado um gerador de analisadores lxicos chamado lex, um dos primeiros geradores desse tipo. O projeto GNU criou o flex como uma verso do lex com licena de software
livre. O flex, assim como o lex original, um gerador de analisadores lxicos que gera analisadores
na linguagem C, e possui verses compatveis nos principais sistemas operacionais atuais.
Como mostrado na Figura 2.1 [21], o flex recebe como entrada um arquivo de especificao e produz,
na sada, um programa na linguagem C que implementa o analisador lxico que segue a especificao dada. Para usar o flex primeiramente precisamos saber como escrever a especificao lxica da
linguagem no formato esperado pela ferramenta.
2.4.1
Formato da entrada
A parte principal da especificao lxica no flex um conjunto de regras. Cada regra composta por
duas partes: um padro e uma ao; o padro uma expresso regular que descreve um determinado
tipo de tokens da linguagem, e a ao determina o que fazer quando encontrar o padro correspondente. Para um compilador, a maioria das aes vai simplesmente criar um token para o lexema
encontrado, como veremos adiante.
O formato do arquivo de especificao do flex dividido em trs partes separadas por uma linha
contendo os caracteres %%, da seguinte forma:
Formato de um arquivo de especificao do flex
definies
%%
regras
%%
cdigo
A nica parte obrigatria do arquivo so as regras. As definies permitem dar nomes a expresses regulares, o que til quando uma determinada expresso regular aparece como parte de vrios
padres, ou como forma de documentao, para deixar mais claro o que significam as partes de um
padro complexo. Veremos exemplos de uso das definies mais adiante. No comeo do arquivo
tamm podem ser especificadas algumas opes que alteram o comportamento do analisador gerado.
A terceira parte do arquivo pode conter cdigo em linguagem C que ser adicionado, sem alteraes,
ao programa C gerado pelo flex. Como a sada do flex um programa em linguagem C, isso permite que o criador do arquivo de especificao adicione funes ou variveis ao analisador gerado.
Geralmente a parte de cdigo til para definir funes auxiliares que podem ser usadas pelas aes.
J entendemos a maior parte do que necessrio para usar o flex, mas alguns detalhes s ficam claros
com alguns exemplos. Vamos comear com um exemplo de especificao bastante simples.
2.4.2
Para exemplificar o uso do flex, vamos ver um exemplo de especificao simples e auto-contida que
tambm serve como uma forma de testar os padres do flex.
Cdigo fonte /simples.ll[code/cap2/simples.ll]
Especificao simples para o flex
22 / 68
%option noyywrap
2x
CPF [0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}
EMAIL [[:alnum:]\._]+@[[:alnum:]]+\.[[:alnum:]]+
%%
3x
{CPF}
{ printf("CPF\n"); }
{EMAIL} { printf("EMAIL\n"); }
.
{ printf("Caractere nao reconhecido\n"); }
%%
// funcao principal que chama o analisador
int main()
{
yylex();
}
Definies
A especificao contm as trs partes: definies, regras e cdigo. Antes das definies est especificada a opo noyywrap, que simplifica a especificao (sem essa opo seria necessrio escrever
uma funo chamada yywrap). So definidas duas expresses, CPF e EMAIL.
Na parte de regras so descritas trs, as duas primeiras determinam o que o programa deve fazer
quando encontra um token que satisfaz os padres CPF e EMAIL, e a terceira regra determina o que
o programa deve fazer com tokens que no satisfazem nenhum dos dois.
A primeira regra :
{CPF}
{ printf("CPF\n"); }
Uma ao sempre tem duas partes, o padro e a ao, separados por espaos em branco:
padro ao
CPF entre chaves especifica que deve ser usada a definio com esse nome, ao invs de tratar os
caracteres como representando eles mesmos (o padro CPF, sem chaves, seria satisfeito apenas pela
string "CPF"). A regra um trecho de cdigo C que ser executado casa o padro seja satisfeito pelo
token. Nesse caso apenas impressa a string "CPF", para que o usurio veja o tipo de token que foi
determinado.
O uso de definies opcional. A seguinte regra tem exatamente o mesmo efeito que a regra anterior
para nmeros de CPF:
[0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}
23 / 68
{ printf("CPF\n"); }
Como j vimos, o ponto um metacaractere no flex que representa qualquer caracter. O padro .
(carcter .) significa qualquer caractere, ou seja, esse padro reconhece qualquer caractere no
reconhecido pelos padres anteriores. importante entender como as regras do flex so processadas:
o analisador testa a string atual com todos os padres da especificao, procurando ver que padres
so satisfeitos pela string. Se mais de um padro satisfeito pel string ou parte dela, o analisador vai
escolher o padro que satisfeito pelo maior nmero de caracteres.
Usando as regras do exemplo atual, digamos que a string atual seja um CPF. Essa string satisfaz o
padro para nmeros de CPF na primeira regra do arquivo de especificao, mas o primeiro caractere
da string, que um nmero, tambm satisfaz a ltima regra (o padro .), pois esse padro satisfaz
qualquer caractere. Entre as duas regras ativadas, a regra do CPF casada com todos os caracteres
da string atual, enquanto que a regra do ponto s casada com o primeiro caractere da string atual.
Portanto, a regra do CPF casada com o maior nmero de caracteres, e essa regra escolhida. Mas se
a string atual for uma sequncia de trs dgitos como 123, o nico padro que satisfeito o ltimo,
do ponto, que aceita qualquer caractere, e nesse caso o analisador imprime mensagens de erro (o
padro do ponto satisfeito trs vezes por essa string, j que o ponto representa apenas um caractere).
Como o flex casa a entrada com os padres
O funcionamento geral do flex determinado pelos padres que esto presentes nas regras
especificadas para o analisador. Para a sequncia de caracteres da entrada, o analisador
gerado pelo flex tenta casar os caracteres de entrada, ou uma parte inicial deles, com algum
padro nas regras.
Se apenas um padro casado com os caracteres iniciais da sequncia, a regra de onde
vem o padro disparada, ou seja, a ao da regra executada pelo analisador. Se nenhum padro for casado com os caracteres atuais, o analisador gerado executa uma regra
padro inserida pelo flex. A regra padro simplesmente imprime na sada os caracteres no
reconhecidos por nenhum padro.
Se mais de um padro for satisfeito por uma sequncia inicial dos caracteres atuais, o analisador escolhe o padro que casado com o maior nmero de caracteres e dispara a regra
desse padro. Se vrios padres casam com o mesmo nmero de caracteres da sequncia
atual, o analisador escolhe aquele que aparece primeiro no arquivo de especificao. Isso
significa que a ordem das regras no arquivo de especificao importante.
Se uma regra disparada ao casar os caracteres atuais com algum padro, os caracteres
restantes que no foram casados com o padro permanecem guardados para uma prxima
vez que o analisador for chamado. Por exemplo, se a entrada a string 123456 e o nico
padro do analisador representa cadeias de trs dgitos, a primeira chamada ao analisador
vai casar os caracteres 123 e disparar a regra associada, deixando os caracteres 456 no
analisador, para uma prxima chamada. Se o analisador for chamado novamente, a regra vai
casar com os caracteres 456 e disparar novamente. Dessa forma o analisador pode atuar
em um token de cada vez.
O cdigo nesse caso define uma funo main, para que o cdigo C gerado pelo flex possa ser executado diretamente. A funo main apenas chama a funo yylex() que a funo principal do
24 / 68
2.4.3
Vimos anteriormente um analisador lxico para uma linguagem de expresses criado diretamente na
linguagem C, sem uso de gerador. Nosso prximo exemplo um analisador para a mesma linguagem,
mas agora usando flex. Isso serve a dois propsitos: o primeiro mostrar mais algumas caractersticas
do uso do flex; o segundo comparar o esforo necessrio para criar um analisador com e sem usar
um gerador como o flex.
O analisador composto pelo arquivo de especificao, exp.ll, um arquivo C que chama o analisador lxico (exp_flex.c), e um arquivo de cabealho com definies (exp_tokens.h).
25 / 68
Nota
Voc pode consultar os cdigos completos destes arquivos no Apndice B [52].
O arquivo de cabealho contm as definies de tipos e constantes, iguais ao analisador que foi mostrado anteriormente:
Cdigo fonte /exp_flex/exp_tokens.h[code/cap2/exp_flex/exp_tokens.h]
O contedo principal desse arquivo :
Arquivo de cabealho para analisador lxico de expresses
// constantes booleanas
#define TRUE
1
#define FALSE
0
// constantes para tipo de token
#define TOK_NUM
0
#define TOK_OP
1
#define TOK_PONT
2
#define TOK_ERRO
3
// constantes para valores de operadores
#define SOMA
0
#define SUB
1
#define MULT
2
#define DIV
3
// constantes para valores de pontuacao (parenteses)
#define PARESQ
0
#define PARDIR
1
// estrutura que representa um token
typedef struct
{
int tipo;
int valor;
} Token;
// funcao para criar um token
extern Token *token();
// funcao principal do analisador lexico
extern Token *yylex();
Alm das constantes j vistas para tipo e valor do token, temos um novo tipo de token declarada e
prottipos para duas funes. O novo tipo o TOK_ERRO, que sinaliza um erro na anlise lxica.
Esse tipo usado quando o analisador recebe uma sequncia de caracteres que no reconhece como
token da linguagem. As duas funes declaradas so a funo principal do analisador, e uma funo
para criar tokens que usada pelo analisador. A funo principal desse analisador retorna um ponteiro
para uma estrutura Token, e por isso ela deve ser declarada de outra forma (a funo yylex padro
retorna um inteiro, como vimos antes).
26 / 68
%option noyywrap
%option nodefault
%option outfile="lexer.c" header-file="lexer.h"
%top {
#include "exp_tokens.h"
}
NUM [0-9]+
%%
[[:space:]] { }
/* ignora espacos */
{NUM}
\+
\*
\/
\(
\)
{
{
{
{
{
{
{
token(TOK_NUM,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_PONT,
token(TOK_PONT,
return
return
return
return
return
return
return
atoi(yytext)); } 3x
SOMA);
}
SUB);
}
MULT);
}
DIV);
}
PARESQ); }
PARDIR); }
}
// erro para
// token desconhecido
%%
x
O arquivo de especificao usa todas as trs partes: definies (e opes), regras e cdigo. Na parte
de definio so includas as opes para no precisar da funo yywrap e opes para selecionar
27 / 68
{ return token(TOK_NUM,
atoi(yytext)); }
De resto, a ltima regra usa o padro com um ponto para capturar erros lxicos na entrada.
A seo de cdigo inclui uma varivel e uma funo que sero includas no analisador gerado. A
varivel tok serve para guardar o token atual, e a funo token serve para guardar os dados de um
token e retorn-lo para a parte do programa que chama o analisador.
O arquivo C exp_flex.c contm o programa principal que recebe entrada do teclado e chama o
analisador lxico. Esse arquivo contm as funes operador_str() e imprime_token() que
so idnticas s funes no arquivo exp_lexer.c do analisador lxico anterior. A funo principal
do programa em exp_flex.c mostrada abaixo:
Cdigo fonte /exp_flex/exp_flex.c[code/cap2/exp_flex/exp_flex.c]
Funo principal do progra em exp_flex.c
int main(int argc, char **argv)
{
char entrada[200];
Token *tok;
printf("Analise Lexica para Expressoes\n");
printf("Expressao: ");
fgets(entrada, 200, stdin);
inicializa_analise(entrada);
printf("\n===== Analise =====\n");
28 / 68
A funo principal quase igual ao analisador criado diretamente, mas as funes inicializa_analise() e proximo_token() so diferentes.
A funo
proximo_token() apenas chama a funo principal do analisador gerado pelo flex, yylex():
Funo para obter o prximo token
Token *proximo_token()
{
return yylex();
}
2.4.4
A funo yylex gerada pelo flex normalmente l sua entrada de um arquivo, a no ser que seja usada
a funo yy_scan_string como no exemplo anterior. O analisador gerado pelo flex contm
uma varivel chamada yyin que um ponteiro para o arquivo de entrada usado pelo analisador.
Normalmente essa varivel igual varivel global stdin da linguagem C, ou seja, a entrada padro.
Para fazer que o analisador leia de um arquivo ao invs da entrada padro, necessrio mudar o valor
dessa varivel para o arquivo desejado.
O cdigo necessrio basicamente o seguinte:
Exemplo de como fazer o analisador ler de um arquivo
int inicializa_analise(char *nome)
{
FILE *f = fopen(nome, "r");
if (f == NULL)
return FALSE;
// arquivo aberto com sucesso, direcionar analisador
yyin = f;
}
Ou seja, deve-se abrir o arquivo desejado usando fopen, e depois atribuir a varivel yyin do analisador para apontar para o mesmo arquivo aberto. A partir da, chamadas funo yylex vo ler os
caracteres usados no analisador do arquivo, ao invs da entrada padro. uma boa prtica fechar o
arquivo aberto durante a inicializao ao final da anlise lxica. Vamos ver um exemplo de analisador
que l a entrada a partir de um arquivo a seguir.
2.5
Os exemplos vistos at agora foram preparao para sabermos como criar um analisador lxico para
uma linguagem de programao. Vamos usar o flex como maneira mais fcil de criar um analisador do
que escrever o cdigo diretamente. Para especificar os padres que definem os vrios tipos de tokens
no flex, precisamos usar expresses regulares. J vimos como escrever um arquivo de especificao
do flex para criar um analisador lxico, e como trabalhar com o cdigo C gerado pelo flex. Nesta
seo vamos juntar todas as peas e criar um analisador lxico para uma pequena linguagem de
programao, uma verso simplificada da linguagem C.
30 / 68
2.5.1
A Linguagem Mini C
A linguagem que vamos usar como exemplo uma simplificao da linguagem de programao C,
chamada aqui de Mini C. A ideia que todo programa Mini C seja um programa C vlido, mas muitas
caractersticas da linguagem C no esto disponveis em Mini C.
Um programa Mini C um conjunto de declaraes de funes. Cada funo uma sequncia de
comandos. Alguns comandos podem conter expresses. Variveis podem ser declaradas, apenas do
tipo int. As estruturas de controle so apenas o condicional if e o lao while. As expresses que
podem ser formadas na linguagem tambm so simplificadas.
Discutiremos mais sobre a sintaxe da linguagem Mini C no prximo captulo. Aqui o que interessa
a estrutura lxica da linguagem, ou seja, que tipos de token ocorrem na linguagem e quais devem
ser os valores associados a eles. J que o analisador lxico encarregado de retirar os comentrios do
cdigo-fonte, tambm nesta fase precisamos definir a sintaxe para comentrios. A linguagem C tem
atualmente dois tipos de comentrios: os comentrios que podem se extender por mltiplas linhas,
delimitados por /* e */, e os comentrios de linha nica, que comeam com // e vo at o final da
linha. Vamos adotar esse ltimo tipo de comentrio na linguagem Mini C, pois ele um pouco mais
simples de tratar no analisador lxico.
Os tipos de tokens da linguagem Mini C so:
identificadores (nomes de variveis e funes)
palavras-chave (if, else, while, return e printf)
constantes numricas
strings (delimitadas por aspas duplas ")
pontuao (parnteses, chaves, etc.)
Os identificadores devem ser iniciados por uma letra, seguida de zero ou mais letras ou dgitos. As
constantes numricas so formadas por um ou mais dgitos, e como pontuao inclumos as chaves
{ e }, os parnteses ( e ), a vrgula e o ponto-e-vrgula. Nos operadores esto includos operadores
aritmticos (as quatro operaes bsicas), comparaes (a operao de menor e de igualdade), e dois
operadores lgicos (E-lgico e negao).
Como a ideia que os programas Mini C sejam compatveis com compiladores C, todo programa
Mini C deve poder ser compilado como se fosse um programa C. Os programas Mini C podem usar
printf para imprimir na tela (em Mini C, printf uma palavra-chave, no uma funo como em
C), e portanto para que isso no crie problemas com compiladores C, preciso que os programas Mini
C tenham a linha #include <stdio.h> no comeo. Por isso, um token especial na linguagem
Mini C o chamado de prlogo, que a string #include <stdio.h>.
Nos captulos seguintes veremos mais detalhes sobre a linguagem Mini C, mas neste captulo vamos
apenas trabalhar a estrutura lxica da linguagem.
2.5.2
Aqui veremos um analisador lxico para a linguagem Mini C criado com o flex. Quase todas as
caractersticas do flex usadas aqui j foram apresentadas antes, mas veremos algumas novidades. A
maior novidade no analisador para a linguagem Mini C a necessidade de usar tabelas para armazenar
as strings e os nomes de identificadores que ocorrem no programa.
31 / 68
1
4
5
6
7
8
9
100
0
1
2
3
4
1
2
3
4
5
6
1
2
3
4
5
6
7
8
9
// tipos
typedef struct
{
int tipo;
int valor;
} Token;
Agora vamos analisar o arquivo de especificao do flex para a linguagem Mini C, uma seo de cada
vez. O arquivo completo pode ser encontrado no endereo abaixo.
Cdigo fonte /minic/lex.ll[code/cap2/minic/lex.ll]
32 / 68
As opes so as mesmas que j vimos no exemplo anterior. Temos um trecho de cdigo includo
no comeo do analisador (a parte comeando com %top) que realiza a incluso de dois arquivos
de cabealho e define o prottipo da funo token (definida na seo de cdigo). Os cabealhos
includos so minic_tokens.h, visto acima, e tabelas.h, que declara as funes para tabelas
de strings e de smbolos: as funes so chamadas de adiciona_string para adicionar uma
nova string na tabela, e adiciona_simbolo para um novo identificador na tabela de smbolos.
Essas funes so definidas no arquivo tabelas.c e retornam o ndice da string ou smbolo na
tabela respectiva; esse ndice pode ser usado como valor do token. O uso de tabelas de strings e
de smbolos importante por vrios motivos, entre eles a eficincia do cdigo do compilador. Em
captulos seguintes veremos como a tabela de smbolos uma estrutura de importncia central em um
compilador.
A especificao tem trs definies: NUM o padro para constantes numricas inteiras, idntica que
vimos no exemplo anterior; ID o padro que especifica os identificadores da linguagem e STRING
o padro que determina o que uma literal string em um programa Mini C: deve comear e terminar
com aspas duplas, contendo no meio qualquer sequncia de zero ou mais caracteres que no sejam
aspas duplas nem caracteres de nova linha (\n).
As regras da especificao so mostradas abaixo:
Regras na especificao para o analisador da linguagem Mini C
[[:space:]]
\/\/[^\n]*
"#include <stdio.h>"
{
{
{
{
{
return
return
return
return
return
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
PC_IF); }
PC_ELSE); }
PC_WHILE); }
PC_RETURN); }
PC_PRINTF); }
33 / 68
{NUM}
{ID}
\+
\*
\/
\<
==
&&
!
=
{
{
{
{
{
{
{
{
{
return
return
return
return
return
return
return
return
return
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
\(
\)
\{
\}
,
;
{
{
{
{
{
{
return
return
return
return
return
return
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
SOMA);
SUB);
MULT);
DIV);
MENOR);
IGUAL);
AND);
NOT);
ATRIB);
}
}
}
}
}
}
}
}
}
PARESQ); }
PARDIR); }
CHVESQ); }
CHVDIR); }
VIRG);
}
PNTVIRG); }
}
A primeira regra ignora os espaos, como vimos no exemplo anterior. A segunda para ignorar os comentrios. Um comentrio qualquer sequncia que comea com duas barras e vai at o fim da linha.
Como a barra um caractere especial no flex, ele precisa ser especificado com a contrabarra antes, e
portanto // vira \/\/ no padro. As outras regras no tm novidade, exceto que a regra para strings
e a regra para identificadores obtm o valor do token chamando as funes adiciona_string e
adiciona_simbolo, como discutimos antes.
O analisador lxico normalmente no vai ser usado de forma isolada: ele chamado pelo analisador sinttico para produzir tokens quando necessrio, como veremos no prximo captulo. Mas
aqui vamos testar o funcionamento do analisador lxico isoladamente, criando um programa principal que abre um arquivo de entrada e chama o analisador lxico. Esse programa est no arquivo
lex_teste.c:
Cdigo fonte /minic/lex_teste.c[code/cap2/minic/lex_teste.c]
O programa principal seguinte:
Funo do principal do programa de teste do analisador lxico
int main(int argc, char **argv)
{
Token *tok;
if (argc < 2) {
printf("Uso: mclex <arquivo>\n");
return 0;
}
inicializa_analise(argv[1]);
tok = yylex();
while (tok != NULL) {
34 / 68
A funo principal obtm o nome do arquivo de entrada dos argumentos de linha de comando, inicializa a anlise passando esse nome de arquivo, e para cada token encontrado na entrada imprime uma
representao desse token na tela. A funo inicializa_analise apenas tenta abrir o arquivo
com o nome passado e configura o analisador lxico para usar esse arquivo, como discutimos antes:
Funo que inicializa a anlise lxica
void inicializa_analise(char *nome_arq)
{
FILE *f = fopen(nome_arq, "r");
if (f == NULL) {
fprintf(stderr,"Nao foi possivel abrir o arquivo de entrada:%s\n",
nome_arq);
exit(1);
}
yyin = f;
}
A funo de impresso de tokens apenas imprime o tipo e o valor do token, mas usando uma string
que representa o nome do tipo ao invs de simplesmente imprimir a constante numrica. A funo
completa pode ser vista no cdigo fonte.
Para testar o analisador, vamos criar um pequeno programa na linguagem Mini C. Esse programa
pode ser encontrado no arquivo teste.c.
Cdigo fonte /minic/teste.c[code/cap2/minic/teste.c]
Arquivo de teste na linguagem Mini C
// teste para o compilador Mini C
#include <stdio.h>
35 / 68
int main()
{
printf("Ola, mundo!\n");
return 0;
}
Compilando o programa principal lex_teste.c para gerar um executvel mclex, obtemos a seguinte sada para o arquivo teste.c:
Execuo do analisador mclex sobre o arquivo teste.c
andrei$ ./mclex teste.c
Tipo: prologo - Valor: 0
Tipo: identificador - Valor:
Tipo: identificador - Valor:
Tipo: pontuacao - Valor: 1
Tipo: pontuacao - Valor: 2
Tipo: pontuacao - Valor: 3
Tipo: palavra chave - Valor:
Tipo: pontuacao - Valor: 1
Tipo: string - Valor: 0
Tipo: pontuacao - Valor: 2
Tipo: pontuacao - Valor: 6
Tipo: palavra chave - Valor:
Tipo: numero - Valor: 0
Tipo: pontuacao - Valor: 6
Tipo: pontuacao - Valor: 4
0
1
Nota
interessante ver que o analisador ignorou a primeira linha com um comentrio, e o primeiro
token mostrado o prlogo. A sequncia de tokens segue corretamente do contedo do arquivo fonte. Tambm deve ser observado que o valor dos tokens de tipo string e identificador
so os ndices deles nas tabelas correspondentes. Para obter a string ou identificador encontradas pelo analisador, preciso acessar as tabelas. Veremos exemplos disso no captulo
seguinte.
2.6
Concluso
Neste captulo vimos o que a etapa de anlise lxica do compilador, e como criar um analisador
lxico para qualquer linguagem de entrada. Vimos o que so os tokens na anlise lxica e como especificar os padres que determinam os tipos de tokens usando expresses regulares. possvel criar
um analisador lxico escrevendo o cdigo diretamente a partir dos padres dos tokens, mas mais
prtico usar um gerador de analisadores lxicos como o flex. Vimos o uso do flex para linguagens
bastante simples e tambm para linguagens de programao mais prxima da realidade. Neste captulo tambm vimos uma introduo linguagem Mini C, que ser usada como exemplo no resto
do livro. Um analisador lxico completo para a linguagem Mini C, usando o flex, foi mostrado e
36 / 68
37 / 68
Captulo 3
Anlise Sinttica
O BJETIVOS DO CAPTULO
Ao final deste captulo voc dever ser capaz de:
Entender a funo do analisador sinttico e como ele se integra ao resto do compilador
Compreender o conceito de estrutura sinttica
Criar Gramticas Livres de Contexto que capturam a estrutura sinttica de linguagens
Criar o analisador sinttico para uma linguagem usando uma ferramenta geradora
A anlise sinttica a etapa do compilador que ocorre aps a anlise lxica. O objetivo da anlise
sinttica determinar a estrutura sinttica do cdigo-fonte que est sendo compilado. Para isso, a
anlise sinttica utiliza o fluxo de tokens produzido pela anlise lxica.
No captulo anterior mencionamos uma analogia entre compilao de um programa e leitura de um
texto. Vimos que a anlise lxica pode ser entendida como o agrupamento de letras individuais em
palavras. J a anlise sinttica similar ao agrupamento de palavras para formar frases. Quando
lemos, nosso crebro determina automaticamente a estrutura sinttica das frases, pois entender essa
estrutura (mesmo que apenas intuitivamente) necessrio para compreender o significado da frase:
quem o sujeito (quem realiza a ao), quem o objeto (quem sofre a ao), etc. A anlise sinttica
em um compilador faz o mesmo tipo de tarefa, mas determinando a estrutura do cdigo-fonte que est
sendo compilado.
Neste captulo vamos entender em mais detalhes a funo do analisador sinttico e como ele funciona. Assim como usamos o formalismo das expresses regulares para guiar a criao do analisador
lxico, veremos o uso das Gramticas Livres de Contexto para guiar a criao de analisadores sintticos. Tambm veremos como usar o gerador bison como uma ferramenta para ajudar na criao do
analisador sinttico (assim como usamos o flex para a anlise lxica).
3.1
Estrutura sinttica
A estrutura sinttica de um programa relaciona cada parte do programa com suas sub-partes componentes. Por exemplo, um comando if completo tem trs partes que o compem: uma condio de
teste, um comando para executar caso a condio seja verdadeira, e um comando para excutar caso a
condio seja falsa. Quando o compilador identifica um if em um programa, importante que ele
38 / 68
Topo da rvore
Arco ou Aresta
B
C
Folhas
B pai de C
C lho de B
(ns que no
possuem lhos)
Figura 3.1: Representao usual de uma rvore na computao: com a raiz no topo e folhas em baixo.
Uma rvore que representa a estrutura sinttica de um programa normalmente chamada de rvore
sinttica. Para o exemplo do comando condicional mencionado antes, a rvore sinttica seria similar
mostrada na Figura 3.2 [39]. Como filhos do n if (a raiz) existem trs ns, o primeiro representando a condio, o segundo o comando para o caso da condio ser verdadeira (C1) e o terceiro
representando o comando para o caso da condio ser falsa (C2).
if
cond
C1
C2
3.1.1
rvores de expresso
*
+
+
2
3
2
(a)
3
(b)
Nesse comando, a condio uma expresso relacional (de comparao), x > 2, o comando 1 a
atribuio do valor da expresso (x - 2) * 7 varivel y, e o comando 2 a atribuio do valor
da expresso x + 2 * 5 varivel y. A rvore para esse comando comea da forma que j vimos,
mas inclui a estrutura das partes do comando. Essa rvore pode ser vista na Figura 3.4 [41].
40 / 68
if
=
>
x
*
x
7
2
+
x
*
2
3.2
O analisador sinttico a etapa que vem logo aps o analisador lxico no compilador, e isso acontece
porque as etapas esto fortemente relacionadas. A tarefa do analisador sinttico muito mais simples
de realizar partindo dos tokens da entrada, ao invs dos caracteres isolados.
Em teoria, como vimos no Captulo 1, a comunicao entre o analisador lxico e o analisador sinttico
sequencial: o analisador lxico produz toda a sequncia de tokens (criada a partir do arquivo de
entrada) e passa essa sequncia inteira para o analisador sinttico. Essa ideia mostrada na Figura 3.5
[41].
caracteres
lxico
tokens
sinttico
rvore
proxtoken()
caracteres
lxico
sinttico
rvore
token
Figura 3.6: Relao entre analisadores lxico e sinttico, na prtica.
Um dos motivos que levaram a essa organizao das duas primeiras etapas de anlise foi que em
computadores antigos era pouco provvel ter memria suficiente para armazenar toda a sequncia
de tokens da entrada (a no ser que o programa de entrada fosse pequeno), ento fazia mais sentido
processar um token de cada vez. Da mesma forma, no havia memria suficiente para guardar toda a
rvore sinttica do programa. Por isso, os compiladores eram organizados de maneira que o analisador
sinttico comandava todo o processo de traduo: obtia o prximo token do analisador lxico e, se
fosse possvel, passava uma sub-estrutura completa do programa (uma sub-rvore) para ser processada
pelas etapas seguintes, j gerando o cdigo-destino para essa parte. Em seguida, essa parte da rvore
era descartada e o analisador sinttico passava para a prxima parte da rvore.
Esse tipo de organizao de um compilador era conhecida como traduo dirigida pela sintaxe. Hoje
em dia, com os computadores tendo quantidades de memria disponvel muito maiores, menos comum ver compiladores reais seguindo esse esquema, e muitos constroem a rvore sinttica inteira do
programa, que passada para as etapas seguintes. Isso porque vrios processos das etapas seguintes podem funcionar melhor se puderem ter acesso rvore sinttica inteira, ao invs de apenas um
pedao de cada vez.
Mas a relao entre o analisador lxico e o analisador sinttico continua a mesma mostrada na Figura 3.6 [42] at hoje, mesmo tendo mais memria, pois para a maioria das linguagens de programao no mesmo necessrio acessar toda a sequncia de tokens da entrada. Alguns tipos de analisador
sinttico armazenam e analisam os ltimos n tokens, para n pequeno (ao invs de analisar apenas um
token por vez). Mesmo assim, isso no muda a relao entre os analisadores lxico e sinttico, o
analisador sinttico apenas chama a funo de obter o prximo token quantas vezes precisar.
3.3
As gramticas formais so ferramentas para descrio de linguagens. Usamos aqui o adjetivo gramticas formais para distinguir de outros sentidos da palavra gramtica, por exemplo na frase a
gramtica da lngua portuguesa, mas daqui para frente, sempre que usarmos a palavra gramtica,
estaremos nos referindo s gramticas formais, a no ser que haja indicao do contrrio.
As gramticas livres de contexto esto associadas s linguagens livres de contexto. Assim como a
classe das linguagens regulares usada na anlise lxica, a classe das linguagens livres de contexto
essencial para a anlise sinttica. Aqui no vamos nos preocupar com linguagens livres do contexto
em geral, apenas usando as gramticas como ferramentas para fazer a anlise sinttica.
42 / 68
3.3.1
Exemplo: Palndromos
O primeiro exemplo uma linguagem bastante simples que gera cadeias que so palndromos. Um
palndromo uma palavra ou frase que lida da mesma forma de frente para trs e de trs para
frente, como roma e amor ou socorram-me, subi no nibus em marrocos. Vamos trabalhar com
palndromos construdos com um alfabeto bastante limitado, de apenas dois smbolos: a e b. Alguns
palndromos nesse alfabeto so abba, aaa e ababa.
Existem dois tipos de palndromos, que podemos chamar de palndromos pares e palndromos mpares. Os palndromos pares, como abba, contm um nmero par de smbolos, com a segunda metade
igual ao reverso da primeira metade. No caso de abba, as metades so ab e ba, sendo que a segunda metade, ba, o reverso da primeira, ab. Cada smbolo em uma metade deve ocorrer na outra
tambm.
Os palndromos mpares, como ababa, possuem um nmero mpar de smbolos, com uma primeira
parte, um smbolo do meio, e uma ltima parte; a ltima parte o reverso da primeira, mas o smbolo
do meio pode ser qualquer um. No caso do alfabeto com smbolos a e b, tanto ababa quanto abbba
so palndromos mpares com primeira e ltima partes idnticas, mas smbolos do meio diferentes.
A gramtica para essa linguagem de palndromos tem dois smbolos terminais (a e b), um smbolo
varivel (S) que tambm o smbolo inicial, e quatro produes:
S aSa
S bSb
S
a
S
b
S
Cada uma dessas produes representam uma forma em que o smbolo S pode ser transformado para
gerar cadeias da linguagem. O smbolo representa uma cadeia vazia, ou seja, uma cadeia sem
nenhum smbolo. Quando temos vrias produes para o mesmo smbolo varivel, como no caso da
gramtica para palndromos, podemos economizar espao usando a seguinte notao:
S aSa | bSb | a | b |
Todas as produes para o smbolo S aparecem na mesma linha, separadas por barras. Podemos ler
essa gramtica como S pode produzir aSa ou bSb ou . . . .
O processo de gerao de uma cadeia seguindo as regras de produo de uma gramtica chamado
de derivao, e ser explicado a seguir.
43 / 68
3.3.2
Derivao
3.3.3
Um exemplo mais similar s linguagens de programao uma linguagem simples para expresses
aritmticas, como vimos no Captulo 2. Aqui veremos uma gramtica para uma linguagem de expresses aritmticas formadas por nmeros inteiros e as quatro operaes bsicas.
Diferente do exemplo anterior dos palndromos, para a linguagem de expresses no interessante
trabalhar com caracteres isolados. Afinal, vimos como criar um analisador lxico justamente para
agrupar os caracteres em tokens, o que facilita muito a anlise sinttica. Por isso, nesse exemplo
e em praticamente todos daqui para a frente, os smbolos terminais no sero caracteres, mas sim
tokens. Alguns tokens so formados por apenas um caractere, mas para a gramtica no faz diferena;
a anlise sinttica vai ser realizada com base nos tokens.
Para a linguagem de expresses, temos tokens de trs tipos: nmeros, operadores e pontuao. Os
operadores so os smbolos para as quatro operaes, e o tipo pontuao para os parnteses. Lembrando do captulo anterior, cada token tem um tipo e um valor; um token do tipo operador vai ter
um valor associado que determina qual dos quatro operadores o token representa. O mesmo acontece
com o valor dos tokens de tipo pontuao: o valor especifica se um parntese abrindo ou fechando.
Para os tokens de tipo nmero, o valor o valor numrico do token.
Uma gramtica para a linguagem de expresses a seguinte:
E E + E | E E | E E | E/E | (E) | num
Essas produes representam o fato que uma expresso pode ser:
Uma soma (ou multiplicao, subtrao, diviso) de duas expresses
Uma expresso entre parnteses
Uma constante numrica (representada aqui por um token de tipo num)
Todos os smbolos nas produes dessa gramtica so variveis ou so tokens; para deixar a notao
mais leve, usamos o caractere + para representar um token de tipo operador e valor que representa um
operador de soma. Isso no deve causar problema; deve-se apenas lembrar que todos os terminais so
tokens. No caso do token de tipo num, o valor dele no aparece na gramtica porque no relevante
para a estrutura sinttica da linguagem. Qualquer token de tipo nmero, independente do valor, faz
parte dessa mesma produo (diferente dos tokens de operadores).
45 / 68
3.3.4
rvores de Derivao
Uma alternativa para representar derivaes em uma gramtica usar as rvores de derivao ao invs
de sequncias lineares de formas sentenciais que vimos at agora. Uma rvore de derivao semelhante s rvores sintticas que vimos antes, mas incluem mais detalhes relacionados s produes da
gramtica utilizada. Uma rvore sinttica no inclui nenhuma informao sobre smbolos variveis
da gramtica, por exemplo. Mais frente, um dos nossos objetivos ser obter a rvore sinttica de um
programa, mas para fazer a anlise sinttica importante entender as rvores de derivao.
Em uma rvore de derivao, cada n um smbolo terminal ou varivel. As folhas da rvore so
smbolos terminais, e os ns internos so smbolos variveis. Um smbolo varivel V vai ter como
filhos na rvore os smbolos para os quais V substitudo na derivao. Por exemplo, sejam as
seguintes derivaes na gramtica de expresses:
E num
E E + E num + E num + num
As rvores de derivao correspondentes so:
E
E
E
num
num
num
3.3.5
Ambiguidade
O exemplo anterior demonstra um problema importante que pode ocorrer com gramticas livres de
contexto: ambiguidade. Uma gramtica ambgua quando existe pelo menos uma sentena gerada
pela gramtica que pode ser gerada de duas ou mais formas diferentes; ou seja, essa sentena ter
duas ou mais rvores de derivao diferentes.
A ambiguidade um problema pois significa que uma mesma sentena pode ter duas estruturas sintticas diferentes, na mesma gramtica. A estrutura sinttica de uma sentena vai influenciar no seu
significado e como ela interpretada pelo compilador, por exemplo. Desta forma, uma gramtica
ambgua para uma linguagem de programao significa que certos programas poderiam funcionar de
duas (ou mais) maneiras diferentes, dependendo de como o compilador interprete as partes ambgua. Obviamente importante que uma linguagem tenha programas que funcionem sempre de uma
mesma maneira, caso contrrio o programador teria dificuldade para aprender como trabalhar com a
linguagem.
No exemplo da gramtica de expresses, uma ambiguidade ocorre quando misturamos operadores
como soma e multiplicao. Na expresso 6 * 5 + 12, deve ser efetuada primeiro a soma ou a
multiplicao? Em termos de estrutura sinttica, a pergunta se a expresso
1. uma soma, com operando esquerdo 6 * 5 e operando direito 12
2. ou uma multiplicao com operando esquerdo 6 e operando direito 5 + 12
Ns somos acostumados com a conveno de sempre fazer multiplicaes e divises antes de somas
e subtraes, ento para ns o mais natural seguir a primeira interpretao. Mas a gramtica que
vimos no estabelece nenhuma interpretao, possibilitando as duas. Para essa mesma sentena, nesta
gramtica, duas rvores de derivao podem ser construdas:
47 / 68
num
num
num
num
num
num
48 / 68
num
num
num
3.3.6
Agora que j vimos as caractersticas das gramticas livres de contexto e alguns exemplos, vamos ver
uma gramtica para uma linguagem de programao simples, que demonstra o tipo de situaes com
as quais teremos que lidar para criar o analisador sinttico de um compilador.
C print string
C if R then C else C
C num := E
RR=E |R<E |E
E E +T | E T | T
T T F | T /F | F
F (E) | num | id
3.4
Os geradores de analisadores sintticos funcionam de maneira bastante similar aos geradores de analisadores lxicos vistos no Captulo 2. Para gerar um analisador sinttico, usamos a ferramenta geradora
passando como entrada uma especificao da estrutura sinttica da linguagem que queremos analisar;
49 / 68
especificao
bison
tokens
analisador
rvore
50 / 68
Apndice A
Instalao de Softwares
A.1
Instalao do flex
Como comum para a maioria das ferramentas, para usar o flex preciso instalar o programa antes.
Essa instalao geralmente simples, ou mesmo desnecessria por j vir instalado, dependendo do
sistema operacional utilizado:
Mac OS X
Em sistemas Mac OS X j vem uma verso do flex instalada. Embora no seja uma das verses
mais recentes da ferramenta, isso no um problema para o nosso uso.
Linux
Em sistemas Linux o flex deve estar disponvel como um pacote no sistema gerenciador de pacotes da distribuio. Por exemplo, no Ubuntu a instalao pode ser feita digitando no terminal:
sudo apt-get install flex
Windows
No Windows o mais adequado instalar usando um instalador criado especificamente para esse
sistema operacional, que pode ser encontrado no endereo http://gnuwin32.sourceforge.net/packages/flex.htm
Voc pode testar a instalao do flex passando o parmetro --version, que ir retornar a verso do
flex instalado:
Testando a instalao do flex no terminal
$ flex --version
flex 2.5.35
51 / 68
Apndice B
Cdigos completos
Neste captulo apresentamos os cdigos completos dos arquivos, pois durante os captulos optamos
por apresentar, didaticamente, apenas os trechos mais relevantes ao que estava sendo explicado.
B.1
Captulo 2
B.1.1
exp_lexer.c
<stdio.h>
<stdlib.h>
<ctype.h>
<string.h>
SOMA
SUB
MULT
DIV
0
1
2
3
// --- funcoes ----------------------------------------------------// funcao utilitaria para obter proximo caractere do codigo
// retorna -1 quando chega ao final da string
char le_caractere(void)
{
char c;
if (pos < tamanho) {
c = codigo[pos];
pos++;
}
else
c = -1;
return c;
}
// determina se um caractere eh um operador, e retorna o tipo se for
int operador(char c)
{
53 / 68
55 / 68
B.1.2
simples.ll
%option noyywrap
CPF [0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}
EMAIL [[:alnum:]\._]+@[[:alnum:]]+\.[[:alnum:]]+
%%
{CPF}
{ printf("CPF\n"); }
{EMAIL} { printf("EMAIL\n"); }
.
{ printf("Caractere nao reconhecido\n"); }
%%
// funcao principal que chama o analisador
int main()
{
yylex();
}
B.1.3
exp_flex/Makefile
B.1.4
exp_flex/exp.ll
/* ignora espacos */
{NUM}
\+
\*
\/
\(
\)
{
{
{
{
{
{
{
token(TOK_NUM,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_PONT,
token(TOK_PONT,
return
return
return
return
return
return
return
atoi(yytext)); }
SOMA);
}
SUB);
}
MULT);
}
DIV);
}
PARESQ); }
PARDIR); }
}
// erro para
// token desconhecido
%%
// variavel global para um token
Token tok;
Token * token(int tipo, int valor)
{
tok.tipo = tipo;
tok.valor = valor;
return &tok;
}
B.1.5
exp_flex/exp_tokens.h
58 / 68
#endif
B.1.6
// __EXP_TOKENS_H
exp_flex/exp_flex.c
59 / 68
B.1.7
minic/minic_tokens.h
1
4
5
6
7
8
9
100
0
1
2
3
4
1
2
3
4
5
6
1
2
3
4
5
6
7
8
9
// tipos
typedef struct
{
62 / 68
#endif
B.1.8
// __MINIC_TOKENS_H
minic/lex.ll
"#include <stdio.h>"
{
{
{
{
{
return
return
return
return
return
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
token(TOK_PCHAVE,
PC_IF); }
PC_ELSE); }
PC_WHILE); }
PC_RETURN); }
PC_PRINTF); }
{NUM}
{ID}
63 / 68
{
{
{
{
{
{
{
{
{
return
return
return
return
return
return
return
return
return
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
token(TOK_OP,
SOMA);
SUB);
MULT);
DIV);
MENOR);
IGUAL);
AND);
NOT);
ATRIB);
\(
\)
\{
\}
,
;
{
{
{
{
{
{
return
return
return
return
return
return
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
token(TOK_PONT,
}
}
}
}
}
}
}
}
}
PARESQ); }
PARDIR); }
CHVESQ); }
CHVDIR); }
VIRG);
}
PNTVIRG); }
}
%%
Token tok;
Token *token(int tipo, int valor)
{
tok.tipo = tipo;
tok.valor = valor;
return &tok;
}
B.1.9
minic/lex_teste.c
case TOK_PROLOGO:
tipo = "prologo";
break;
case TOK_ERRO:
tipo = "erro";
break;
default:
tipo = "desconhecido";
}
printf("Tipo: %s - Valor: %d\n", tipo, tok->valor);
}
int main(int argc, char **argv)
{
Token *tok;
if (argc < 2) {
printf("Uso: mclex <arquivo>\n");
return 0;
}
inicializa_analise(argv[1]);
tok = yylex();
while (tok != NULL) {
imprime_token(tok);
tok = yylex();
}
finaliza_analise();
return 0;
}
66 / 68
Captulo 4
ndice Remissivo
_
*, 18
+, 19
., 20, 24
:alpha:, 19
?, 19
[:digit:], 19
[:lower:], 19
[:space:], 19
[:upper:], 19
[], 19
rvores de expresso, 40
rvore sinttica, 39
, 20
|, 18
\, 20
\n, 20
\t, 20
{}, 20
definies, 22
E
email, 21
ER, 18
especificao simples, 22
expresso, 40
Expresses Regulares, 18
F
fim de arquivo, 25
flex, 20, 22
cdigo, 22
definies, 22
especificao simples, 22
regras, 22
terminar, 25
I
interpretadores, 2
intervalos, 19
A
Anlise
Lxica, 10
anlise sinttica, 38
Analisador Lxico, 12
autmatos finitos, 17
L
Lxica, 10
lex, 22
lexema, 12
linguagem, 1
linguagem de programao, 1
Linguagens Regulares, 17
B
bytecodes, 3
M
metacaracteres, 20
C
cdigo, 22
chamadas de sistema, 3
Classes de caracteres, 19
compilador, 2
CPF, 20, 23
N
nmero de repeties, 20
negao, 20
nodefault, 28
noyywrap, 23
D
datas, 21
P
67 / 68
68 / 68