departamento de estatística e informática universidade ...1 análise léxica andré luis meneses...
TRANSCRIPT
1
Análise Léxica
André Luis Meneses Silva
Departamento de Estatística e Informática
Universidade Federal de Sergipe
Compiladores
2
Análise Léxica
• Produzir tokens a partir de
uma seqüência de
caracteres
• Ignorar comentários e
espaços em branco
• Correlacionar mensagens
de erro com o programa
fonte
• Sub-rotina ou co-rotina do
parser
analisador
lexico
parser
Tokens
Programa fonte
3
• Token: saída do an. léxico.
(símbolo terminal do
parser)
• Lexema: seqüência de
caracteres
• Padrão: descrição dos
possíveis lexemas de um
token
<ID> x, taxa, i_0
<Int> 235, 0
<Real> 235E-23
<If> if
<COMMA> ,
<ASSIGN> :=
<LPAR> (
<GE> >=
... ...
Token Lexema
Alguns Tokens possuem
atributos:
<Int,235>
<ID,”taxa”>
4
Análise Léxica
Vantagens
– Projeto de Linguagem mais simples
• Separação da análise léxica para sintáticageralmente permite simplificar ambas as fases.
– Eficiência
• Um analisador léxico dedicado permite-nosconstruir um especializado e mais eficienteanalisador para esta tarefa.
– Portabilidade
• Peculiaridades do alfabeto de entrada, bem comooutras características específicas podem ser restritasao analisador léxico.
5
Análise Léxica
Dificuldades
– Alinhamento dos lexemas (Fortran antigo)
– Espaços não são significativos (Fortran - Algol 68)
DO 5 I = 1.25
DO 5 I = 1,25
– Palavras chaves não são reservadas (PL/I)
• Distinção entre palavra chave e identificador
if then
then = else;
else
else = then;
DO: palavra chave ou parte de ID?
6
Erros Léxicos
• Poucos erros são detectados nesta etapa
• Como atuar?
– Desespero (Panic mode)
• deletar/pular até encontrar
– Apagar caractere estranho
– Substituir caractere incorreto pelo caractere correto
– Transpor dois caracteres adjacentes
7
Erros Léxicos
• Estas estratégias tentam “reparar” a entrada
através da verificação do prefixo restante da
entrada.
– Assume que erros léxicos são casos isolados.
• A solução ideal para encontrar erros no
programa é computar a quantidade mínima
de transformações requeridas para que o
programa se torne correto.
– Pode deixar a fase de análise léxica custosa.
8
Especificação dos tokens
Expressões Regulares sobre Σ
• é uma e.r. que denota {}
• ε é uma e.r. que denota {ε}
• Se a Σ, a é uma e.r. Denota {a}
• Se r e s são e.r. que denotam R e S
rs, r*, r|s são e.r. que denotam RS, R*, R S
• Precedência
* > concatenação > |
9
Definições regulares
Gramática Regular
d_1 r_1
d_2 r_2
...
d_n r_n
onde os r_i são e.r. sobre Σ {d_1,..., d_n} e
em r_k não aparece nenhum d_j com j≥k.
Obs. Não é possível usar recursão
10
Exemploletra A | ... | Z | a | ... | z
digito 0 | 1 | 2 | ... | 9
id letra( letra | digito )*
digitos digito digito*
fraçao_op . digitos | ε
expoente_op E(+|-|ε)digitos | ε
num digitos fraçao_op expoente_op
Notação: r+ ≡ rr*
r? ≡ r | ε
Ex.: digitos digito+
fraçao_op (.digitos)?
11
Exemploletra A | ... | Z | a | ... | z
digito 0 | 1 | 2 | ... | 9
id letra( letra | digito )*
digitos digito digito*
fraçao_op . digitos | ε
expoente_op E(+|-|ε)digitos | ε
num digitos fraçao_op expoente_op
Notação: r+ ≡ rr*
r? ≡ r | ε
Ex.: digitos digito+
fraçao_op (.digitos)?
12
Exemplo
letra A | ... | Z | a | ... | z
digito 0 | 1 | 2 | ... | 9
if if
id letra(letra|digito)*
relop < | <= | = | > | >= | <>
num digito+(.digito+)?(E(+|-)?digito+)?
Lexemas podem estar separados por espaços em branco.
delim branco | tab | nl
ws delim+
token
s
13
Construindo um analisador léxico
• Construir um Autômato FinitoDeterminístico (ou Autômato Finito NãoDeterminístico) para cada token (edelimitadores)
• Combinar num único AFND
• Transformar para AFD (opcional)
• Implementar o autômato combinado
– Reconhecer o maior token possível
16
1 32
i f
1 2
letra
letra
digito
1 23
4
5
67
d
d
.
d
d
E
E
+,-
d
d
dd=digito
if
id
num
num digito+(.digito+)?(E(+|-)?digito+)
17
1 32
i f
1 2
letra
letra
digito
1
2
5
<
4
=
=
3
>
6 = 7>
1 23
4
5
67
d
d
.
d
d
E
E
+,-
d
d
dd=digito
if
id
relop
num
relop < | <= | = | > | >= | <>
18
1 32
i f
1 2
letra
letra
digito
1
2
5
<
4
=
=
3
>
6 = 7>
1 23
4
5
67
d
d
.
d
d
E
E
+,-
d
d
d
1 2
delim
delim
d=digito
if
id
relop
num
ws
ws delim+
19
Construindo um analisador léxico
Construir um Autômato Finito
Determinístico (ou Autômato Finito Não
Determinístico) para cada token (e
delimitadores)
→Combinar num único AFND
• Transformar para AFD (opcional)
• Implementar o autômato combinado
– Reconhecer o maior token possível
20
1 32
i f
14 15
letra
letra
d
16
17
20
<
19
=
=
18
>
21 = 22>
23 2425
26
27
2829
d
d
.
d
d
E
E
+,-
d
d
0
d
ε
ε
ε
ε
28 29
delim
delim
ε
Autômato combinadoIF
ID
LELT
EQNE
GT GE
NUM
NUMNUM
21
Construindo um analisador léxico
Construir um Autômato Finito
Determinístico (ou Autômato Finito Não
Determinístico) para cada token (e
delimitadores)
Combinar num único AFND
→Transformar para AFD (opcional)
• Implementar o autômato combinado
– Reconhecer o maior token possível
22
21
i
f
3
letra-{i}
letra
d
4
6
<
7
=
=
5
>
8 = 9
>
1011
12
13
1415d
d
.
d
d
E
E
+,-
d
d
0
d
29
delim
delim
AFD combinado (construído
usando o aprendido em LFA)
IF
ID
LELT
EQNE
GT GE
NUM
NUMNUM
letra
dID
letra-{f}d
23
Construindo um analisador léxico
Construir um Autômato Finito
Determinístico (ou Autômato Finito Não
Determinístico) para cada token (e
delimitadores)
Combinar num único AFND
Transformar para AFD (opcional)
→Implementar o autômato combinado
– Reconhecer o maior token possível
24
Implementação
• AFD com uma matriz
int[][] delta, tal que:
delta[q][c] = p sse
• Diretamente no código
switch(estado)
case q: if (caracterCorrente==c) estado=p;
return RespectivoToken // se q for final
...
• Como reconhecer o maior token possível
– Analisar até chegar num estado sem possibilidade de sair
– Ir salvando o último Token reconhecido
q pc
25
Buffer
• Bufferização deve ser eficiente
– Única fase a ler caractere por caractere
• Leituras são realizadas em blocos e bufferizadas
• Usa-se um sentinela para eof
• Ver 3.2 – Livro do Dragão
27
Buffer
• Duas marcas no buffer para delimitar o lexema
• A Interface (assinatura) do Buffer:
class Buffer {
char proximo(); // devolve o c.c. e avança um
marcarInicio();
marcarUltimo();
retrair(int); // retrai o apontador do c.c.
retrairAoUltimo();
String lexema(); // seq. entre as marcas
}
28
classs Token {
public final int codigo;
public Object valor;
Token (int c, Obejct v) {
codigo=c; valor=v}
Token (int c) {
this(c, null) }
}
class sym {
public final static int IF = 0;
public final static int ID = 1;
...
public final static int GE = 8;
}
O tipo Token
29
class Lexer {
Buffer buffer =
new Buffer(....);
Token proximoToken( ) {
int estado = 0;
int ultimoFinal=-1;
buffer.marcarInicio( );
scan: while (true) {
c=buffer.proximo();
switch (estado) {
case 0:
if delimitador(c) estado = 29;
else if (c=='i') {
ultimoFinal = estado = 1;
buffer.marcarUltimo(); }
else if (isLetra(c)) {
ultimoFinal = estado = 3;
buffer.marcarUltimo(); }
else if ...
else throw Erro();
break;
30
case 29:
if (!delim(c)) {
buffer.retrair(1);
buffer.marcarInicio();
estado=0;
}
} //switch
}//while scan
case 1:
if (c=='f') {
ultimoFinal = estado = 2;
buffer.marcarUltimo(); }
else if (letra(c)||digito(c)) {
ultimoFinal = estado=3;
buffer.marcarUltimo(); }
else break scan;
break;
case 2: ...
...
31
switch (ultimoFinal) {
case -1: throw new Erro();
case 1: buffer.retrairAoUltimo();
return ( new Token (
sym.ID, buffer.lexema( ) );
case 2: …
}
} // proximoToken
32
Melhoras ad-hoc
• Algumas melhoras poderiam ser
introduzidas aproveitando algumas
informações inerentes à linguagem– Palavras chaves
• Tratadas como identificadores
• Pré-instaladas e distinguidas na tabela de símbolos
• Diminui muito o número de estados
– Alguns tokens não precisam de “retração”• Ex. <= já é o maior possível.
• Não é necessário se ler mais nenhum caractere e por
tanto não há retração.
33
Divisão léxico-sintático
• Léxico: Geralmente, o que pode ser expresso com
expressões regulares
– Objetivo importante: simplificar o parser.
• Sintático: Gramáticas livres de contexto
• Contra-exemplo: comentários aninhados
• Literais inteiros negativos (ex. -235) deveriam formar um
token?
35
JFlex: Um gerador de Analisadores Léxicos
• Permite especificar analisadores léxicos
• Gera um analisador léxico, escrito em Java
• Versão moderna do Lex e versão eficiente de JLex
JFLEXEspecificação
LéxicaAnalisador Léxico
(Yylex.java)
Compilador
Java
(javac)
Analisador Léxico
(Yylex.class)
36
Especificações JFlexcódigo do usuário
%%
diretivas do JFlex e definições regulares
%%
regras
• Código do Usuário
– package e imports
– Definição de classes utilitárias
• Diretivas
– Customização da classe parser gerada.
– Definições regulares auxiliares
• Regra:
– expressão regular com um código java associado
37
Exemplopackage exemplo.lex;
import exemplo.token.*;
%%
//customização
%function proximoToken
%type Token
//código incluído em Yylex (a classe do parser)
%{private TabelaSimbolos tabela;
public Yylex(tabelaSimbolos tabela) {
this.tabela = tabela;
private int instalarId(String id) {...} //inst na tabela
%}
38
//definições regulares auxiliares
delim=[\ \t\n\r]
ws={delim}+
letra=[a-zA-Z]
digito=[0-9]
id={letra}({letra}|{digito})*
numero={digito}+ (\.{digito}+)? (E[+\-]? (digito)+)?
%%{ws} {/* nenhum valor é devolvido*/}if {return new Token(sym.IF);}{id} {int i = instalarId(yytext());
return new Token(sym.ID,new Integer(i));}{numero} {return new Token(sym.NUM,
new Double(yytext()));}"<" {return new Token(sym.LT);}"<=" {return new Token(sym.LE);}... .... {system.out.println("caract. desconhecido");}
39
Algumas Diretivas
• %class "classname“– Define o nome da Classe do Analisador Léxico
• %implements "interface 1"[, "interface 2", ..]– Define as interfaces que o analisador léxico implementa
• %extends "classname“– Define a classe que o analisador léxico estende.
• %public– Visibilidade pública para a classe
• %apiprivate– Faz com que todos os campos e métodos da classe sejam declarados como
privado (com exceção do método que devolve o próximo token).
40
Algumas Diretivas
• %char (yychar)
– Habilita a contagem de caracteres
• %line (yyline)
– Habilita a contagem de linhas
• %column (yycolumn)
– Habilita a contagem de colunas
• %unicode
– Habilita seu scanner avaliar entradas que
seguem o padrão Unicode 16.
42
Como é feito o casamento
1. É escolhido o token de maior tamanho
possível
– Se há mais de dois, o primeiro na listagem é
escolhido
• Ex.: palavras reservadas deverão ser listadas
primeiro para não serem “ocultas” por {id}
2. É executada a ação associada
43
Expressões Regulares no JFlex
• Metacaracteres
? * + | ( ) ^ $ / ; . = < >
[ ] { } " \ ! ~
– precisam ser escapados
com aspas ou \
• Expresões regulares
– e f, e | f, e*, e+, e? e
(e)
significado normal
– \b, \n, \t, \f, \r,
\udddd, \., \\, \$, ....
– $ fim de linha
– . tudo menos \n
– "...." string sem escape,
exceto para " e \
– {nome} macro expansion
– [bde\n], [a-zA-Z],
[a-z0-9]
[^a-z]
– classes de caracteres
44
Exemplo: Comentários
"/*" [^]* "*/" { /*não faça nada */}
Funciona?
Outro tentativa:
"/*" [^*]* "*" ([^/] [^*]* "*")* "/“
Outra:
"/*" ( [^*]* | "*"+ [^/*] )* "*"+ "/"
45
Exemplo: Comentários
"/*" [^]* "*/" { /*não faça nada */}
Funciona?
Outro tentativa:
"/*" [^*]* "*" ([^/] [^*]* "*")* "/“
Outra:
"/*" ( [^*]* | "*"+ [^/*] )* "*"+ "/"
/**/*/
46
Exemplo: Comentários
"/*" [^]* "*/" { /*não faça nada */}
Funciona?
Outro tentativa:
"/*" [^*]* "*" ([^/] [^*]* "*")* "/“
Outra:
"/*" ( [^*]* | "*"+ [^/*] )* "*"+ "/"
47
Exemplo: Comentários
"/*" [^]* "*/" { /*não faça nada */}
Funciona?
Outro tentativa:
"/*" [^*]* "*" ([^/] [^*]* "*")* "/“
Outra:
"/*" ( [^*]* | "*"+ [^/*] )* "*"+ "/"/***/*/
48
Exemplo: Comentários
"/*" [^]* "*/" { /*não faça nada */}
Funciona?
Outro tentativa:
"/*" [^*]* "*" ([^/] [^*]* "*")* "/“
Outra:
"/*" ( [^*]* | "*"+ [^/*] )* "*"+ "/"
49
Outros operadores do JFlex
• A negação de uma exp. reg.
!e
Casa com todas as cadeias que não casam e
• O operador upto
~e
Casa tudo até a primeira ocorrência de e (incluindo)
~e equivale com !([^]* e [^]* | "") e
• Exemplo dos comentários
"/*" ~"*/"
• Cuidado: negação e upto são muito ineficientes (na geração)
50
Estados• Mistura de Exp. Reg. com estados (autômatos)
– Especificações mais operacionais
– No entanto, as vezes mais adequadas.
<COMMENT> {
"*/" {yybegin(
YYINITIAL) ; }
[^] { /*nada*/ }
}
<DOC-COMMENT> {
“@param" {.... }
...
}
<YYINITIAL> {
if {return new Token...}
{id} {return new Token ... }
"/*" {yybegin(COMMENT);}
"/**" {yybegin(
DOC-COMMENT);}
...
}
52
O operador de lookahead
e / f
Casa um e seguido de um f. O texto relativo ao fnão é consumido
e $
Casa com e de eol
• Operador ineficiente (na execução)
• Exemplo de utilidade:
– Reconhecer identificador ou palavra reservada no problema do DO do fortran antigo.