departamento de estatística e informática universidade ...1 análise léxica andré luis meneses...

53
1 Análise Léxica André Luis Meneses Silva [email protected] Departamento de Estatística e Informática Universidade Federal de Sergipe Compiladores

Upload: others

Post on 18-Mar-2020

7 views

Category:

Documents


1 download

TRANSCRIPT

1

Análise Léxica

André Luis Meneses Silva

[email protected]

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

14

1 32

i f

if if if

15

1 32

i f

1 2

letra

letra

digito

if

id id letra(letra|digito)*

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

26

Buffer

if 5 > 3 ...

Buffer

início último

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?

34

Gerador de Analisadores Léxicos

André Luis Meneses Silva

[email protected]

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.

41

Algumas Diretivas

• %cup

– Habilita compatilidade com o parser cup.

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);}

...

}

51

Exercício

• Como poderia especificar comentários

aninhados usando estados?

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.

53

Outras ferramenta alternativas

• Lex

– Primeira ferramenta neste estilo.

– Gera C e C++

• JAVACC

– Gerador de Analisador Léxico e Sintático (2 em

1).

– “Oficial” da Sun.

– Sintaxe das exp. regulares diferente.

– Poder de expressão equiparável.