Compiladores - Unicamp• Há compiladores que geram código em linguagem assembly. –...

Post on 21-Jul-2020

2 views 0 download

Transcript of Compiladores - Unicamp• Há compiladores que geram código em linguagem assembly. –...

Compiladores

Marco A. Amaral HenriquesDCA/FEEC/UNICAMP

versão 2006

Compilador• Programa que transforma texto escrito em

linguagem de alto nível (programa-fonte) em programa objeto.

ProgramaFonte Compilador Programa

Objeto

Exemplo Simples de Compilação• Compilação de trecho de programa

int a, b, valor;a = 100;b = a*(valor + 20);

• Compilador vê trecho como a sequênciaint a, b, valor;a = 100;b = a*(valor + 20);

• Sequência deve ser analisada lexicamente:– Agrupar caracteres em unidades elementares

• Itens Léxicos: palavras válidas da linguagem. |int|a|,|b|,|valor|; |a|=|100|;|b|=|a|*|(|valor| +|20|)|;|

Exemplo: Análise Sintática• Itens léxicos são analisados sintaticamente

– Agrupar itens léxicos em setenças válidas

• Código de máquina é gerado– Usando tabelas: uma entrada de código para cada operação

possível na linguagem (ineficiência).– Usando sistema formais:

• regras formais para construção e análise de sentenças,• algoritmos para analisar sentenças tornam-se viáveis.

int a , b , valor ; a = 100 ; b = a * (valor + 20 ) ;

Sistema Formal• Conjunto de palavras e de relações chamadas

regras de inferência.• Gramática Formal

– Permite especificar formalmente uma linguagem.– É uma quádrupla ordenada G=(N,T, Σ, P) onde

• T : conjunto de símbolos terminais : correspondem aos símbolos nos programas-fonte.

• P : conjuto de produções : regras de transformação de uma cadeia de caracteres em outra.

• N : conjunto de símbolos não-terminais : aparecem em estágios intermediários da geração de um sentença.

• Σ : símbolo não-terminal inicial .

Exemplo de Gramática Formal

• Gramática G = (N, T, Σ, P):• N = {Σ, A, B}• T = { a , b }• P = {Σ --> AB;

A --> aA;A --> a;B --> Bb;B --> b; }

• Pode gerar sequências de caracteres do tipo anbm, com n, m > 0.

Tipos de Gramática

• De acordo com o número e o tipo de símbolos nos lados direito e esquerdo das produções, as gramáticas podem ser classificadas em quatro tipos distintos, segundo a hierarquia de Chomsky:– Tipo 0: Gramáticas redutíveis e dependentes do contexto– Tipo 1: Gramáticas irredutíveis e dependentes de contexto– Tipo 2: Gramáticas livres de contexto– Tipo 3: Gramáticas regulares

Gramáticas Tipo 0• Gramáticas redutíveis e dependentes do contexto

– Têm produções que expandem/reduzem o comprimento da sequência de caracteres, dependendo do contexto onde esta se insere.

– Não há restrições quanto ao tipo de produções:• qualquer tipo e quantidade de símbolos à esquerda ou à

direita.– Ex: aA → c

BBB → zCt BbC → aAAAAAA AAAaaaAAAa → x

Gramáticas Tipo 1• Gramáticas irredutíveis e dependentes de contexto

– Nenhuma produção diminui o número de caracteres da sequência, mas todas são dependentes do contexto onde está a mesma.

– Para produções na forma α → β, obrigatoriamente tem-se que |α| ≤ |β|, onde |x| indica o número de símbolos em x. Ou seja, a aplicação das produções aumenta ou mantem o tamanho da seqüência de símbolos, mas nunca o diminui.

– Ex: AaA → BBB AaaaA → AAAAAA Ccc → zCC

Gramáticas Tipo 2• Gramáticas livres de contexto

– Lado esquerdo das produções tem um único símbolo e assim não depende do contexto.

• Para todas as produções na forma α → β, obrigatoriamente tem-se que |α| = 1.

– Símbolo do lado esquerdo é símbolo não-terminal.– Ex: P = { B → aBbc ;

B → B ;C → xpto }

Gramáticas Tipo 3• Gramáticas regulares

– Livres de contexto e com restrições ao número máximo de símbolos acrescidos a cada produção.

– Podem ser modeladas como autômatos finitos.• Logo, existem algoritmos para implementá-las.

– Duas variantes:• Gramática Linear Direita:

– as produções são do tipo A → a ou A → aA• Gramática Linear Esquerda:

– as produções são do tipo A → a ou A → Aa

Gramáticas Tipo 3: características• São adequadas para definir formatos de

constantes, identificadores e palavras reservadas de uma linguagem → úteis na Análise Léxica– Ex: G = ({letra, digito}, {IDENT, RESTO},

P, IDENT ) P = { IDENT → letra | letra RESTO;

RESTO → letra |digito |letra RESTO |digito RESTO }

• Exercício: supondo que os símbolos terminais “letra” e “digito” sejam equivalentes a letras e dígitos respectivamente, forneça dois exemplos bem distintos de seqüências válidas e dois de seqüências inválidas na gramática acima.

Expressões Regulares (regexp)• Forma alternativa de se definir sentenças geradas

por gramáticas tipo 3.• As regexp clássicas de um alfabeto A podem ser:

– um elemento de A ou string vazia;– uma seqüência das regexps P e Q: PQ ;– uma escolha entre as regexps P e Q : P | Q (P ou Q) ;– zero ou mais ocorrências de P: P* .

• Ex: regexp que descreve um identificador: letra ( letra | digito )*

Gramáticas Tipo 3: limitações• Tanto as Gramáticas Tipo 3 como as regexps não

são adequadas para expressar sentenças que possuam limitadores balanceados, tais como ( ... (...) ... (...) ... ( ... (... ) ... ) ... )

• Entretanto, estas sentenças podem ser expressas facilmente por gramáticas livres de contexto como, por exemplo: S → ( S ) S → SS S → ε

Gramáticas Livres de Contexto• Mais utilizadas na etapa de análise sintática de

uma linguagem.• Possuem a propriedade da “autoincorporação”,

isto é possui pelo menos uma produção da forma A → α1 A α2 onde A é um símbolo não-terminal e α1 , α2 são seqüências não vazias de símbolos terminais.

• Obs: uma gramática livre de contexto que não possua a propriedade acima descrita pode ser convertida a uma gramática regular.

Representação de Gramáticas Livres de Contexto

• Toda gramática livre de contexto equivale a uma gramática na Forma Normal de Chomsky, onde todas as produções podem ser escritas na forma

A → BC A → a

– Lado esquerdo da produção: símbolo não-terminal– Lado direito da produção: expansão do símbolo da

esquerda, contendo símbolos terminais e/ou não-terminais

Notação BNF (Backus-Naur Form)• Representação textual de gramáticas livres de contexto

– operador binário ::= para descrever produções– colchetes angulares < e > para símbolos não-terminais

• Ex: <exp> ::= <exp> <termo>– operador OU | : produções alternativas (Ex: <S> ::= a | b )– operador para símbolos opcionais [ ]

• Ex: <S> ::= [ a ] equivale a <S> ::= a ou <S> ::= ε– operador de fatoração ( | )

• Ex: <S> ::= a (b | c) d equivale a <S> ::= abd ou <S> ::= acd– operador de repetição *

• Ex: <S> ::= a* equivale a <S> ::=ε ou <S> ::= a <S>– operador de concatenação { || }* para repetição múltipla

• Ex: <S> ::= {a||b}* equivale a <S>::=a <T> e <T> ::=ε ou <T> ::=ba <T>

Tarefas Iniciais do Compilador• Análise Léxica

– Reconhecer os itens léxicos (tokens) do programa.– Atribuir identificador a cada item léxico.

• Análise Sintática– Validação da estrutura formada pelos itens léxicos.– Procura construir uma árvore sintática com os itens.

• Análise Semântica– Normalmente é implementada junto com a análise sintática.– Extrai informações da árvore sintática para sintetizar código

de máquina.• Consistência entre declaração e uso de variáveis.• Reserva de memória para variáveis.

Tarefas Finais do Compilador• Geração de Código Intermediário

– Tradução de cada nó da árvore sintática em instruções de máquina.

• Há compiladores que geram código em linguagem assembly.– Formalização da gramática de linguagem de máquina é difícil.

• Rotinas de tradução ainda são especificadas manualmente.

• Otimização– Modificação do código e estruturas de dados gerados para

minimizar tempo de execução e espaço requerido.– Desempenho de otimizador depende da criatividade de seu

projetista.

Análise Léxica• Reduz uma sentença a conjunto de itens léxicos.• Classifica itens léxicos.

– Distingue os tipos aceitos por uma gramática.– Classes mais comuns

• identificadores de variáveis• símbolos ou palavras reservadas (for, switch, etc em C)• literais ou constantes• símbolos especiais (símbolos de controle)• marcador de fim de arquivo

– Cada novo item léxico recebe um identificador e é guardado na Tabela de Terminais.

Autômatos Finitos na representação de itens léxicos

• Itens léxicos normalmente representáveis por:– gramáticas regulares (Tipo 3) ou– expressões regulares (regexp)• Existe correspondência unívoca entre gramáticas

(expressões) regulares e autômatos finitos.• Autômatos finitos: quádrupla M = (K, Σ, δ, S, F)– K: conjunto finito de estados– Σ: alfabeto de entrada finito– δ: conjunto de transições– S: estado inicial (S ∈ K)– F: conjunto de estados finais (F ⊆ K)

Representação de Autômatos Finitos• AFs são representados por grafos dirigidos,

denotando as transições entre os estados.• Círculos representam os estados.

– Círculos duplos representam estados finais.• Transições: representadas por triplas (si , ΣT , sf ):

– si : estado inicial da transição– ΣT : conjunto de símbolos do alfabeto que disparam a

transição quando o estado é si – sf : estado do autômato após a transição

asisf

Representação de autômatos por tabelas de transição

• Forma mais prática de implementação.• Colunas representam os estados.• Linhas representam os símbolos do alfabeto de

entrada.• Cada célula da tabela indica o estado final de

uma transição a partir do estado indicado na coluna após a entrada do símbolo indicado na linha.

. . . si . . .

. . . . . . . . .

. . . sf . . .

. . . . . . . . .

. . .

. . .

a

Tipos de Autômatos• Determinísticos

– para cada par (estado, entrada) existe apenas uma transição aplicável.

• Não-determinísticos– para cada par (estado, entrada) existe mais de uma

transição aplicável.• A todo AF não-determinístico corresponde um

AF determinístico que representa (aceita) a mesma linguagem.– Logo, é possível transformar um AF não-

determinístico por um AF determinístico sem perda de funcionalidade.

Construção de autômato finito não-determinístico

• Algoritmo de Thompson– constrói AFs não determinísticos para reconhecer

sentenças de uma gramática regular.• Primeiro passo: decompor a expressão regular

em termos de suas relações elementares:– um símbolo do alfabeto da linguagem,– uma concatenação tipo RS,– uma alternativa tipo R | S ,– uma repetição tipo R .

• Segundo passo: construir um autômato finito que reconheça cada uma destas relações elementares.

Autômatos Finitos Elementares

i

f

f

f

f

ii

i

M1

M1

M1

M2

M2

a

ε

εεε

εε

ε

ε

Reconhecimento do símbolo a Concatenação

RepetiçãoAlternativas

(estado final de M1 e inicial de M

2)

(string vazia)

Autômato M

Exemplo de construção deAF não-determinístico

• Construção do autômato finito que reconheça a expressão regular R = (a | b)* abb.

• Relações elementares:R

1 = a

R2 = b

R3 = R

1 | R

2

R4 = R

3 *

R5 = R

4 R

1

R6 = R

5 R

2

R = R

6 R

2

Exemplo de construção deAF não-determinístico: resultado

5 6ε

εε

ε410 117

1

3

2

8 9ε ε

ε

ε

a

bb ba

R = (a|b)*abb

Conversão de AF não-determinístico a AF determinístico

• AFs não-determinísticos são ambíguos.• Conversão baseada na construção de subconjuntos.

– Subconjunto ε*(T): inclui os estados em T e todos os demais alcançáveis a partir deles por entradas vazias.

– Determina-se ε*(T0) para T

0 contendo os estados iniciais do

AF não-determinístico (estados originais).– Subconjunto ε*(T

0) é o estado inicial s

0 do AF

determinístico.– Analisa-se ε*(T

0) para cada entrada possível, obtendo-se T

1,

T2 etc. e repete-se o procedimento com

s1 = ε*(T

1), s

2 = ε*(T

2) etc.

– Subconjunto si = ε*(T

i) será um estado final do AF

determinístico se Ti contiver um estado final original.

Resultado da conversãopara AF determinístico

s0

s1

s3

s4s

2

a

b a a

bb

b

b

a

a

Grafo Tabela

entr.

a

b

s0

s1

s2

s3

s4

s1

s1

s1

s1

s1

s2

s3

s2

s4

s2

R = (a|b)*abb

Minimização de estados• Se existirem vários AFs reconhecendo uma mesma

sentença, alguns estados de alguns AFs podem ser desnecessários.

• Para minimizar o número de estados de um AF:– divide-se o grupo de estados S em um subgrupo G

1

contendo todos os estados finais (F) e outro G2

contendo os estados não-finais (S-F);– faz-se nova divisão em cada subgrupo, considerando

que um novo subgrupo Gi só conterá os estados s e t, se

todas as transições partindo de s e de t, levam sempre a um mesmo subgrupo;

– repete-se a divisão para os novos subgrupos até que não seja mais possível dividir.

Resultado da minimizaçãodo AF determinístico

s1

s3

s4s

0

a a

bb

b

b

a

a

Grafo Tabela

entr.

a

b

s0

s1

s3

s4

s1

s1

s1

s1

s2

s3

s4

s2

R = (a|b)*abb

Analisador Léxico• É um programa que implementa um autômato

finito para reconhecer ou rejeitar seqüência de símbolos σ de uma linguagem.

• Sejam os seguintes procedimentos auxiliares:

S0 = ESTADO_INICIAL(M): retorna o estado inicial do

autômato M;

Resp = ESTADO_FINAL(M,Si): indica se estado S

i é

elemento do conjunto de estados finais do autômato M;

Si+1

= PRÓXIMO_ESTADO(M, Si , símbolo): retorna o

próximo estado do autômato M, quando se encontra no estado S

i e recebe a entrada símbolo.

Algoritmo de Analisador Léxico

SCANNER(M,σ)s = ESTADO_INICIAL(M)while true do

if É_VAZIO(σ)then if ESTADO_FINAL(M,s)

then return trueelse return false

else c = REMOVA_O_PRIMEIRO(σ)s = PROXIMO_ESTADO(M, s, c)if s = NULL

then return false

Geradores de Analisadores Léxicos• Conjunto de programas para construção

automática de analisadores léxicos.• Podem não gerar analisadores eficientes.• Geradores mais comuns: lex e flex

– Formato geral de gramática em lex definida em arquivo com extensão .l :

definições%% <== divisão de seção no arquivoregras%% <== divisão de seçãorotinas do usuário

Seção de Regras do LEX• Define a funcionalidade do analisador léxico

através de regras (produções da gramática).• Cada regra é um par padrão-ação.• Regras:

– expressões regulares (regexps) indicam uma sequência válida de caracteres;

– expressões regulares são descritas por uma linguagem definida por lex;

– a ação associada a cada regra é um bloco de código C que será executado sempre que a seqüência de símbolos de entrada for reconhecida pela regexp;

Linguagem de regexps em lexO p e r a d o r S i g n i f i c a d o E x e m p l o

? e x p r e s s ã o a n t e r i o r é o p c i o n a l 1 0 ? 9 < = > 1 0 9 o u 1 9* q u a l q u e r r e p e t i ç ã o , i n c l u s i v e 0 a * < = > { 0 , a , a a , a a a , . . . }+ q u a l q u e r r e p e t i ç ã o , e x c l u i n d o 0 a + < = > { a , a a , a a a , . . . }

| a l t e r n a t i v a a | b < = > a o u b( ) a g r u p a m e n t o d e e x p r e s s õ e s

• q u a l q u e r c a r a c t e r , e x c e t o l i n e f e e d^ i n í c i o d e l i n h a , m a s s e e s t i v e r e n t r e [ ]

s i g n i f i c a c o m p l e m e n t o[ ] q u a l q u e r c a r a c t e r d e n t r e o s e s p e c i f i c a d o s [ a b c ] < = > { a , b , c }

- f a i x a d e v a l o r e s [ 0 - 9 ] < = > { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }{ } n ú m e r o d e r e p e t i ç õ e s p o s s í v e i s a { 1 , 2 } < = > { a , a a }

\ E s c a p e s c a r a c t e r s e g u i n t e o ur e p r e s e n t a c a r a c t e r e s n ã o i m p r i m í v e i s .

\ • < = > •\ t < = > t a b u l a ç ã o

Seção de Definições do LEX• Pode conter:

– Macrosdigito [0-9]frac .[0-9]+

– Linhas de comando em C, sempre delimitadas por %{ e %} .

%{#include <classes.h>#define NUMBER 400%}

Exemplo de Geração de Analisador Léxico

Gramática G=(N,T,Σ,P)

N = {Σ, <IDN>, <INT>, <REAL>, <SIMB_ATR>,<SIMB_MUL>,<SIMB_DIV>, <SIMB_SOM>,<SIMB_SUB>, <SIMB_POT>,<SIMB_PA1>,<SIMB_PA2>, <BL>, <seq>, <digito>, <letra>,<exp> }

T = {0,1,2,3,4,5,6,7,8,9, . ,a,b,c,...,z,A,B,C,...,Z,+,-,*,/,(,),=,”\n”,”\t”,” ”}

Produções da Gramática Exemplo• P = { Σ ::= <BL><exp>;

<exp> ::= <IDN>|<INT>|<REAL>|<SIMB_ATR>|<SIMB_MUL>|<SIMB_DIV>|<SIMB_SOM>|<SIMB_SUB>|<SIMB_POT> ;

<IDN> ::= <letra> <seq>; <BL> ::= “\t”<BL> | “ ”<BL> | “\t” | “ ” ; <seq> ::= <letra> <seq> | <digito> <seq> | <letra> | <digito>; <INT> ::= <digito> <INT> | <digito> ; <REAL> ::= <digito> <REAL> | .<INT> ; <SIMB_MUL> ::= * ;

<SIMB_DIV> ::= / ; <SIMB_SOM> ::= + ; <SIMB_SUB> ::= - ;

<SIMB_POT> ::= *<SIMB_MUL> ; <SIMB_ ATR> ::= = ; <SIMB_PA1> ::= ( ; <SIMB_PA2> ::= ) ; <digito> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ; <letra> ::= a | b | c | d | . . . | A | B | C | D | . . . | Z | “\n” }

Criação de Arquivo .l %{

#defineIDN 257#defineINT 258#defineREAL259. . .%}%%[a-zA-Z] | [a-zA-Z][a-zA-Z0-9]+ {/* Ação Semântica 1 */

printf(“Token da classe <Identificador> \n”); return IDN; }

[0-9]+ {/* Ação Semântica 2 */ printf(“Token da classe <Inteiro> \n”); return INT; }

[0-9]+\ . [0-9]+ |\ . [0-9]+ {/* Ação Semântica 3 */

printf(“Token da classe <Racional> \n”); return REAL; } . . .

Rotinas do Usuário em Arquivo .lSeção de definições%%Seção de regras%%Seção de rotinas do usuário#include <stdio.h>main(int argc, char *argv) {

int val;while (val = yylex( ))

printf(“valor = %d \n”, val);}

Utilização do Analisador Léxico• Assumindo que o arquivo .l se chama teste.l :

> lex teste.l• Tal comando gera um programa lex.yy.c que aceita

sequência de caracteres e retorna itens léxicos.• Programa lex.yy.c deve ser compilado

> cc -o teste lex.yy.c -ll • Exemplo de uso: > teste a=7.02 resultará em

Token da classe <Identificador>valor = 257Token da classe <Operador de atribuição>valor = 261Token da classe <Racional>valor = 259

Análise Sintática• Gramáticas do Tipo 3 (gramáticas regulares) não

são adequadas para identificar a forma como os itens léxicos estão combinados (análise sintática).

• Gramáticas do Tipo 2 (gramáticas livres de contexto) são mais adequadas para representar as regras de uma linguagem de programação e, portanto, podem ser usadas em análisadores sintáticos (parsers).– Nem todas as construções de uma linguagem de

programação podem ser representadas diretamente, mas por meio de reconhecedores de sentenças livres de contexto e algumas estratégias heurísticas é possível automatizar a etapa da análise sintática.

Reconhecimento de sentenças

• Procura-se “reduzir” a sentença de símbolos terminais até se alcançar o símbolo não-terminal inicial (processo inverso da derivação ou expansão).

• Caso o símbolo não-terminal inicial não seja alcançável por nenhuma combinação das regras da gramática, a sentença é considerada inválida.– Neste caso é preciso tentar todas as combinações de

regras possíveis até que não haja mais nenhuma combinação não tentada.

Exemplo de reconhecimento• Seja a gramática G=(N,T, <E>, P), onde:

N = {<E>, <T>, <F>} T = {+, ×, id , ( , ) }

P = { <E> ::= <E> + <T> | <T> ;

<T> ::= <T> × <F> | <F> ;

<F> ::= ( <E> ) | id }

• A sentença (id + id) id não pode ser reduzida

nesta gramática, mas (id + id) × id pode.

Derivações Canônicas• Há diversas opções para a seqüência de aplicação

das regras, algumas reconhecendo a sentença e outras não.

• Derivações Canônicas são formas sistemáticas de aplicação das regras gramaticais.– Derivação mais à esquerda (leftmost derivation):

aplicar uma regra de derivação ao símbolo não-terminal mais à esquerda.

• Seqüência de reconhecimento mais à esquerda– Derivação mais à direita (rightmost derivation):

seleciona-se o símbolo não-terminal mais à direita para aplicação da regra de derivação.

• Seqüência de reconhecimento mais à direita

Exemplos de Derivações Canônicas Derivação mais à esquerda

<E>::= <T>::= <T> × <F>::= <F> × <F>::= (<E>) × <F>::= (<E> + <T>) × <F>::=(<T> + <T>) × <F>::=(<F> + <T>) × <F>::=(id + <T>) × <F>::=(id + <F>) × <F>::=(id + id) × <F>::=(id + id) × id

Derivação mais à direita

<E>::= <T>::= <T> × <F>::= <T> × id::= <F> × id::= (<E>) × id::= (<E> + <T>) × id::= (<E> + <F>) × id::= (<E> + id) × id::= (<T> + id) × id::= (<F> + id) × id::= (id + id) × id

Árvores Sintáticas ou Gramaticais• Representação para derivações de uma sentença a partir

do símbolo inicial Σ.• Símbolo Σ ocupa raiz da árvore.• Símbolos terminais ocupam folhas da árvore.• Símbolo não-terminal <S> corresponde à raiz de uma

sub-árvore.– Ex: se <S> ::= X1X2X3...Xn , então <S> é uma sub-árvore da

gramática.<S>

X1 X2 X3 Xn. . .

Exemplos de árvores sintáticas

Formas de reconhecimento• Reconhecimento pode ser visto como:

– leftmost parse: encontrar uma seqüência de reconhecimento mais à esquerda

ou

– rightmost parse: encontrar uma seqüência mais à direita

ou

– construção de uma árvore sintática.

Gramática Ambígua• Gramática livre de contexto na qual existe mais de

uma árvore sintática para uma mesma sentença.• Ex: gramática G=(N,T,Σ,P)

• N = {Σ , <inteiro>}• T = { 1 , 0 }• P = {Σ ::= <inteiro>; <inteiro> ::= <inteiro> <inteiro>; <inteiro> ::= 1; <inteiro> ::= 0 }tem duas árvores para sequência 101.

Gramática Não-ambígua• Só há uma árvore sintática por sentença.• Permite geração de mais de uma derivação para uma

mesma sentença, dependendo da ordem de substituição dos símbolos não-terminais (problema de precedência).

• Formas naturais de escolha do símbolo– substituir o mais à esquerda

• Gramáticas LL : input from Left, Leftmost derivation– substituir o mais à direita

• Gramáticas LR : input from Left, Rightmost derivation

• Às vezes pode ser obtida de gramática ambígua, mas não existe algoritmo que determine se uma dada gramática é ou não é ambígua.

Exemplo de Gramática Não-ambígua• Gramática G=(N,T,Σ,P)

• N = {Σ, <inteiro> , <digito>}• T = { 1 , 0 }• P = { Σ ::= <inteiro> ;

<inteiro> ::= <inteiro> <digito> ;<inteiro> ::= <digito> ;<digito > ::= 1 ;<digito > ::= 0 }

tem somente uma árvore para sequência 101.

Analisadores Sintáticos• Constroem a árvore sintática correspondente à

sentença sob análise.• Duas formas de contrução:

– ascendente (bottom-up): parte das folhas para a raizou

– descendente (top-down): parte da raiz e tenta chegar até as folhas.

• Ex: seja a função REDUZIR(G,α) que retorna o símbolo não-terminal à esquerda de uma produção da gramática G cujo lado direito contenha α.

Exemplo de construção ascendentePARSER_ASCENDENTE(G,α)

declare s : Símbolowhile true do s ← REMOVER_PRIMEIRO(α)

if s = OBTER_NÃO_TERM_INICIAL(G) e ESTA_VAZIA(α)

then return trueelse INSERIR(α, s)

α ← REDUZIR(G,α) if ESTA_VAZIA(α)

then return false

Analisador de deslocamento e redução• Baseia-se na técnica de construção ascendente para

reconhecer sentenças válidas.• Símbolos terminais da sentença são lidos um a um.• A cada símbolo lido, analisador decide se:

– lê um novo símbolo (desloca o ponteiro para o próximo da sentença) ou

– aplica uma produção da gramática aos símbolos já lidos, reduzindo-os a um símbolo não-terminal.

• Sua peça fundamental é a Tabela de Deslocamento e Redução (Shift-Reduce Table)

Tabela SR• A Tabela SR (Shift-Reduce) é baseada nas

relações de precedência simples entre os símbolos da gramática.

• As relações de precedência são:

X Y : X confere precedência a Y

e

X > Y : X tem precedência sobre Y.

Regras para relações “confere precedência”

• Regra 1: $ ∑ , isto é, delimitador de sentença confere precedência ao símbolo não-terminal inicial.

• Regra 2: se existe alguma produção de G na forma α → βXYμ, então diz-se que XY.

• Regra 3: se X α , onde α é um símbolo não-terminal, e existe uma produção para α em G onde Y é o primeiro símbolo do lado direito (α→Yμ), então diz-se que XY.

Regras para relações “tem precedência sobre”

• Regra 4: ∑ > $ , isto é, símbolo não-terminal inicial tem precedência sobre delimitador de sentença.

• Regra 5: se, para algum símbolo não-terminal α, existe a relação α Y e existe uma produção para α cujo último símbolo é X (α → βX), então diz-se que X > Y.

• Regra 6:se, para algum símbolo não-terminal α, existe a relação α > Y e existe uma produção para α cujo último símbolo é X (α → βX), então diz-se que X > Y.

• Regra 7: se, para algum símbolo não-terminal α, existe a relação X > α e existe uma produção para α cujo primeiro símbolo é Y (α → Yμ), então diz-se que X > Y.

Observações sobre regras de precedência

• Não se deve confundir as relações de precedência com as relações “maior que” ou “menor que”, pois podem surgir situações que aparentemente não fariam sentido. Por exemplo:– XY não implica que Y>X ;– é possível encontrar gramáticas em que XY ocorre

ao mesmo tempo que YX ;– é possível encontrar gramáticas em que XY ocorre

ao mesmo tempo que X>Y .

Formação da Tabela SR• Gramática é estendida com a inclusão do símbolo $

que serve para indicar início e fim de sentença.• Regras de 1 a 7 são aplicadas à gramática.• Para toda relação Xa, a tabela deverá indicar que

deve ser buscado o próximo símbolo da sentença.– X é um símbolo qualquer e a é um símbolo terminal.

• Para toda relação X>a, a tabela deverá indicar que é possível fazer uma redução nos últimos símbolos lidos da sentença.

• Para os pares (X,a) de símbolos sem relação de precedência definida, a tabela deverá estar em branco.

Exemplo de construção de Tabela SR• Seja a gramática G=(N,T, <E>, P), onde:

N = {<E>, <T>, <F>} T = {+, ×, id , ( , ) } P = { <E> ::= <E> + <T> | <T> ;

<T> ::= <T> × <F> | <F> ;<F> ::= ( <E> ) | id }

• Pela Regra 1: $E• Pela Regra 2: E+ +T T×

×F (E E)• Pela Regra 3: $E $T +T +F ×(

×id (E (T $F +( +id (F $( $id (( (id

Exemplo de construção de Tabela SR (cont.)

• Seja a gramática G=(N,T, <E>, P), onde:N = {<E>, <T>, <F>} T = {+, ×, id , ( , ) } P = { <E> ::= <E> + <T> | <T> ;

<T> ::= <T> × <F> | <F> ;<F> ::= ( <E> ) | id }

• Pela Regra 4: E>$

• Pela Regra 5: T>+ F>× T>)• Pela Regra 6: T>$ F>+ )>× id>×

F>) F>$ )>+ id>+ )>) id>) )>$ id>$

Exemplo de construção de Tabela SR (cont.)

id + × ( ) $$ S SE S S R*T R S R RF R R R Rid R R R R+ S S× S S( S S) R R R R

indica reconhecimentoda sentença

indica combinaçãoerrônea de símbolos

Geradores de Analisadores Sintáticos

• Ferramentas similares e complementares aos geradores de analisadores léxicos.

• Ferramentas mais comuns: yacc (yet another c compiler) e bison.– lex gera a função yylex( ) que retorna o identificador

de um item léxico reconhecido.– yacc gera a função yyparse( ), que analisa os itens

léxicos e decide se eles formam ou não uma sentença válida.

Passos para Gerar Analisador Sintático• Escrever programa analisador léxico ou usar

ferramenta lex para tal.• Escrever gramática da linguagem em arquivo

com extensão .y .• Escrever programa analisador sintático ou usar a

ferramenta yacc para tal.• Compilar e ligar os programas-fonte.

– Para yylex( ) e yyparse( ) trabalharem em conjunto é preciso garantir que os tokens e seus valores sejam comuns às duas funções.

Formato Geral da Gramática de yacc

• Similar ao da gramática de lex– Três seções no arquivo .y :

definições (declaração de nomes e tipos de tokens, de variáveis etc)

%%regras (contém as produções da gramática em BNF:

símbolo ::= derivações {ações semânticas} )%%rotinas do usuário (contém a função principal

main( ) e outras rotinas do usuário)

Exemplo de Geração de Analisador Sintático

• Gramática G = (N,T,<stat>,P)N = {<stat> , <expr> , <termo> , <fator> }

T = { INT , REAL , IDN , ( , ) , + , - , * , / }

P = { <stat> ::= <expr>; <expr> ::= <expr> (+ | - ) <termo> | <termo> ; <termo> ::= <termo> ( * | / ) <fator> | <fator> ; <fator> ::= INT | REAL | IDN | ( <expr> ) }

Criação de Arquivo .y%token IDN%token INT%token REAL%token SIMB_ATR%token SIMB_PA1%token SIMB_PA2%token SIMB_MUL%token SIMB_DIV%token SIMB_SOM%token SIMB_SUB%token SIMB_POT%token SIMB_END%start stat

%%stat: SIMB_END | expressao SIMB_END { /* Ação Semântica 0 */ printf("<stat> ::= <expressao> SIMB_END\n"); exit(1); } ;

expressao: termo { /* Ação Semântica 1 */

printf("<expr> ::= <termo> \n"); } | expressao SIMB_SOM termo { /* Ação Semântica 2 */

printf("<expr> ::= <expr> + <termo>\n"); } | expressao SIMB_SUB termo { /* Ação Semântica 3 */

printf("<expr> ::= <expr> - <termo>\n"); } ;/* demais regras devem ser incluídas . . . */%%main( ) {

yyparse( );}

Utilização do Analisador Sintático• Função main( ) deve ser retirada do analisador

léxico, para que somente uma exista no conjunto .• Analisador sintático y.tab.c é gerado por

> yacc teste.y (usando o nome teste para o arquivo .y )• Os programas-fonte devem ser compilados e

ligados, formando o executável analisador .> gcc -o analisador y.tab.c lex.yy.c -ll -ly

Usando o Analisador Sintático> analisador93 + (122)Token da classe <Inteiro><fator> ::= INTToken da classe <Op. Soma>Token da classe <Abre parênteses>Token da classe <Inteiro><fator> ::= INTToken da classe <Fecha parênteses><fator> ::= ( <expr> )<expr> ::= <expr> + <termo><stat> ::= <expr> SIMB_END

Análise Semântica noAnalisador Sintático

• Ações semânticas podem ser atribuídas às produções da gramática– Gramática se torna gramática com atributos.– Exemplo

Produção Ação Semântica<expr> ::= <termo> Atribuir valor de <termo> a <expr> .<expr> ::= <termo> (+|-) <termo>

Valor de <expr> é a soma/subtração dos valores dos filhos.<termo> ::= <fator> Atribuir valor de <fator> a <tarmo> .<termo> ::= <fator> (*|/) <fator>

Valor de <termo> é o produto/divisão dos valores do filhos.<fator> ::= <expr> Atribuir valor de <expr> a <fator> .<fator> ::= <INT> Atribuir valor do token léxico <INT> a <fator> .

• Não existe gerador “universal” para analisadores semânticos.– É possível aproveitar gerador de analisadores sintáticos para definir ação

atribuída a cada produção.

Criação de Arquivo .y%token IDN%token INT%token REAL%token SIMB_ATR%token SIMB_PA1%token SIMB_PA2%token SIMB_MUL%token SIMB_DIV%token SIMB_SOM%token SIMB_SUB%token SIMB_POT%token SIMB_END%start stat

%%stat: SIMB_END | expressao SIMB_END { /* Ação Semântica 0 */ printf("Resultado = %d \n", $1); exit(1); } ;

expressao: termo { /* Ação Semântica 1 */ $$ = $1; } | expressao SIMB_SOM termo { /* Ação Semântica 2 */ $$ = $1 + $3; printf("Soma de %d e %d = %d \n", $1, $3, $$); } | expressao SIMB_SUB termo { /* Ação Semântica 3 */ $$ = $1 - $3;

printf("Diferença de %d e %d = \n", $1, $2, $$); } ;/* demais regras devem ser incluídas . . . */%%main( ) {

yyparse( );}

Utilização do Analisador Sintático-Semântico

• É a mesma adotada para o analisador sintático:• Cria-se o analisador y.tab.c através de

> yacc teste.y (usando o nome teste para o arquivo .y )• Compila-se e liga-se os programas-fonte y.tab.c e

lex.yy.c , para gerar arquivo executável analisador .> gcc -o analisador y.tab.c lex.yy.c -ll -ly

Exemplo de uso do Analisador Sintático-Semântico

> analisador103 - (54 + 9)Token da classe <Inteiro>Token da classe <Op. Subtração>Token da classe <Abre parênteses>Token da classe <Inteiro>Token da classe <Op. Soma>Token da classe <Inteiro>Token da classe <Fecha parênteses>Soma de 54 e 9 = 63Diferença de 103 e 63 = 40Resultado = 40>

Análise Semântica• É difícil verificar por meio de gramáticas:

– se todas as variáveis estão declaradas;– se variáveis estão sendo usadas com o tipo no qual

foram declaradas.• Análise semântica cuida do inter-relacionamento

entre partes distintas do programa.• Tarefas básicas:

– verificação de tipos de variáveis;– verificação de fluxo de controle;– verificação de unicidade de declaração de variáveis;– outras tarefas, dependendo da linguagem.

Exemplos de Análise Semântica- verificação de tipos -

• Na compilação com gcc do código em C:int teste(int a, float b){

return a%b;}

teríamos o seguinte erro:In function 'teste':...: invalid operands to binary %

acusado pelo analisador semântico, pois o operador “ % ” (módulo) não admite operando real.

Exemplos de Análise Semântica- verificação de fluxo de controle -

• Na compilação com gcc do código em C:void teste(int i, int j){

if (j == k)break;

elsecontinue; }

teríamos os seguintes erros:In function 'teste':...: break statement not within loop or switch...: continue statement not within a loop

acusados pelo analisador semântico, pois o comando break só pode ser usado dentro de um laço ou no final de um switch-case, e o continue só pode ser usado em laços.

Exemplos de Análise Semântica- verificação de unicidade -

• Na compilação com gcc do código em C:void teste(int k){

struct { int a;float a;} x;

float x;switch (k){

case 0x31: x.a = k;case '1': x = x.a; }

teríamos os seguintes erros:In function 'teste':...: duplicate member 'a'...: previous declaration of 'x'...: duplicate case value

Conversão automática de tipos• Vários compiladores fazem conversão

automática de tipos quando encontram alguma incompatibilidade.– Conversão conhecida também como coerção ou cast.

• Exemplos: codigo = codigo – '0'

– a constante '0' é convertida do tipo char para o tipo int antes de se resolver a expressão aritmética.

medida = 5.9 + 2

– a constante inteira 2 é convertida do tipo int para o tipo float antes de se resolver a expressão aritmética.

Conversão manual de tipos• O programador pode controlar as conversões de

tipos manualmente por meio dos operadores de molde.– Coloca-se o nome do tipo desejado entre parênteses

antes da expressão cujo resultado deve ser convertido.• Ex. 1: int a, *ponteiro;

a = ponteiro;

– compilando as linhas acima obteríamos:warning: assignment makes integer from pointer without a cast

• Ex. 2: int a, *ponteiro; a = (int) ponteiro;

– neste caso não obteríamos nenhum alerta, pois o tipo de ponteiro seria convertido para int antes da atribuição.

Geração de Código

• Geração ótima de código não é factível.• Geração de código orientada a sintaxe:

– adicionar à gramática as ações que escrevem num arquivo a sequência de instruções adequadas a cada redução.

• Normalmente feita em duas etapas:– geração de código intermediário com base em processador fictício;– conversão do código intermediário em código definitivo para o

processador alvo.• Tipos de código intermediário:

– notação posfixa– código de três endereços

Código de Três Endereços• Composto por instruções com operações unárias,

operações binárias e uma atribuição.– Instruções envolvem no máximo três variáveis: duas

para os operandos e uma para receber resultados.• Esta é a razão para o nome “Três Endereços”.

– Quatro tipos básicos de instruções:• atribuição• desvios• chamada a rotinas• acesso (indireto ou indexado)

Código de Três Endereços- instruções de atribuição -

• Três formas básicas:– variável recebe resultado de operação binária

a = b operador c ;– variável recebe resultado de operação unária

a = operador b ;– variável recebe valor de outra variável

a = b ;• Ex.:

– instruções em C: a = b + c * d ;– instruções intermediárias:

t = c * d ;a = b + t ;

Código de Três Endereços- instruções de desvio -

• Possuem duas formas básicas:– desvio incondicional: GOTO rótulo– desvio condicional: IF x operador y GOTO rótulo

• Exemplo:

while (i++ <= k)x[i] == 0;

x[0] = 0 ;

_L1: if i>k goto _L2; i := i + 1; x[i] := 0; goto _L1;_L2: x[0] := 0;

Código de Três Endereços- instruções de chamada de rotinas -

• Ocorre em duas etapas:– argumentos são devidamente armazenados pela

instrução param ;– rotina é efetivamente chamada pela instrução call;

• pode ou não haver valor de retorno.• Exemplo: para chamar uma função func(a,b,c) o

código intermediário seriaparam aparam bparam c_t1 := call func,3

– o valor “3” após a chamada mostra quantos argumentos foram armazenados para serem usados na chamada à rotina.

Código de Três Endereços- instruções de acesso -

• Acessos indexados:– Exemplos:

a := b[i]d[j] := c

• Acessos indiretos: permitem manipulação de endereços.– Exemplos:

a := &b c := *d

*e := f

Código de Três Endereços- representação interna -

• Baseada em tabelas de três ou quatro colunas.• Ex. para expressão a = b + c * d ;

1

2

operador arg_1 arg_2 resultado

* c d _t

+ b _t aquádrupla

tripla: associaresultados aos números de linha na tabela

1

2

operador arg_1 arg_2

* c d

+ b (1)

Notação Posfixa• Também conhecida por notação polonesa ou

notação polonesa reversa (RPN).• Características:

– operador vem após operandos;– não exige parênteses;– pode ser eficientemente implementada em máquinas

baseadas em pilhas (máquinas de zero endereços):• operandos são inseridos e retirados do topo da pilha com

instruções tipo push e pop;• operadores retiram da pilha os operandos e devolvem o

resultado da operação para o topo da pilha.• Exemplos:

a * b + c ; ==> a b * c +(a + b) * (c + d) ==> a b + c d + *

Exemplo de processadorcom notação posfixa

• Repertório parcial de instruções de um processador fictício:

Código Mnemônico Função

11 LDC <valor> Empilhe valor. 12 LD Desempilhe endereço e empilhe

conteúdo deste endereço. 13 ST Desempilhe valor; desempilhe endereço;

armazene valor neste endereço. 16 ADD Desempilhe a; desempilhe b; empilhe b+a . 17 SUB Desempilhe a; desempilhe b; empilhe b-a .

Exemplo de uso de processador fictício• Resolução de a = (2+3) - 1;

– Em notação posfixa: 2 3 + 1 - a =– Sequência de instruções:

Mnemônico Conteúdo da pilha

LDC <end. de a> <end. de a>LDC 2 <end. de a> , 2LDC 3 <end. de a> , 2 , 3ADD <end. de a> , 5LDC 1 <end. de a> , 5 , 1SUB <end. de a> , 4ST

Adaptação do Arquivo .y%token IDN%token INT%token REAL%token SIMB_ATR%token SIMB_PA1%token SIMB_PA2%token SIMB_MUL%token SIMB_DIV%token SIMB_SOM%token SIMB_SUB%token SIMB_POT%token SIMB_END%start stat

%%stat: SIMB_END | expressao SIMB_END { /* Ação Semântica 0 */ exit(1); } ;

expressao: termo { /* Ação Semântica 1 */ $$ = $1; } | expressao SIMB_SOM termo { /* Ação Semântica 2 */ $$ = $1 + $3; fprintf("ADD \n",); } | expressao SIMB_SUB termo { /* Ação Semântica 3 */ $$ = $1 - $3;

fprintf("SUB \n"); } ;/* demais regras devem ser incluídas . . . */%%main( ) {

yyparse( );}

Otimização• Conjunto de transformações no código gerado

visando minimização de tempo de execução e/ou memória ocupada.– Otimização independente de máquina:

• Não considera a arquitetura da máquina, como p. ex.x + 0 --> x

– Otimização dependente da máquina:• Usa recursos da arquitetura para melhorar o código

ADD --> ADDQ (no 68000)– Otimização local: considera apenas um bloco de

instruções.– Otimização global: considera todo o procedimento.

Otimização por Trecho de Instruções• Técnica de otimização local que inclui:

– Eliminação de códigos redundantes– Eliminação de códigos não alcançáveis– Eliminação de desvios desnecessários– Redução de complexidade– Uso de modos de endereçamento

Otimizações Independentes de Máquina• Eliminação de expressões comuns• Propagação de cópias• Eliminação de códigos mortos• Otimização em laços

– Movimento de códigos– Eliminação de variáveis de indução

• Substituições algébricas