900 likes | 1.29k Views
Compiladores I. Cristiano Damiani Vasconcellos cristiano.damiani@ufpel.edu.br. Bibliografia Recomendada. COMPILADORES Princípios, Técnicas e Ferramentas; Sethi, Ravi; Aho, Alfred V.; Ullman, Jeffrey D. LTC, 1995. Projeto Moderno de Compiladores;
E N D
Compiladores I Cristiano Damiani Vasconcellos cristiano.damiani@ufpel.edu.br
Bibliografia Recomendada COMPILADORES Princípios, Técnicas e Ferramentas; Sethi, Ravi; Aho, Alfred V.; Ullman, Jeffrey D. LTC, 1995. Projeto Moderno de Compiladores; Bal, Henri E.; Grune, Dick; Langendoen, Koen. CAMPUS, 2001. Modern Compiler Implementation in Java Andrew W. Appel. Cambridge University Press, 2002. Introdução A Teoria dos Autômatos, Linguagens e Computação; Hopcroft, John E.; Ullman, Jeffrey D.; Motwani, Rajeev. CAMPUS, 2002.
Introdução Pré-processador Analisador Léxico front-end Analisador Sintático Analisador Semântico Gerador de Código (intermediário) Otimizador back-end Gerador de Código
final = (nota1 + nota2) / 2; Analisador Léxico = Id1 = (Id2 + Id3) / 2 Id1 / Analisador Sintático 2 + Id2 Id3 Introdução Tabela de Símbolos
= Id1 / intToDouble(2) + Id2 Id3 Introdução Analisador Semântico Gerador de Código (intermediário) Tabela de Símbolos temp1 = Id2 + Id3 temp2 = temp1 / 2.0 Id1 = temp2
AnáliseLéxica O Analisador Léxico (scanner) examina o programa fonte caractere por caractere agrupando-os em conjuntos com um significado coletivo (tokens): • palavras chave (if, else, while, int, etc), • operadores (+, -, *, /, ^, &&, etc), • constantes (1, 1.0, ‘a’, 1.0f, etc), • literais (“Projeto Mono”), • símbolos de pontuação (; , {, }), • labels.
Análise Léxica constanteInt digito digito* constanteDouble digito digito*. digito* digito {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} X* Representa uma seqüência de zero ou mais X.
Análise Sintática Verifica se as frases obedecem as regras sintáticas da linguagem: Por exemplo, uma expressão pode ser definida como: expressão + expressão expressão – expressão (expressão) constante
Gramáticas Um conjunto de regras de produção, é um símbolo de partida. Uma regra de produção tem o formato , onde representa o nome da construção sintática e representa uma forma possível dessa construção: <expressão> <expressão> + <expressão>
Gramáticas <expr> <expr> + <expr> | <expr> – <expr> | (<expr>) | <const> <const> <const><const> | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9
Derivação A verificar se uma frase faz parte da linguagem gerada pela gramática, envolve sucessivas substituições da cadeia de símbolos que ocorre do lado esquerdo da produção pela sua construção sintática correspondente, partindo do símbolo inicial. Essa substituição é chamada derivação sendo normalmente denotada pelo símbolo .
Derivação <expressão> <expr> + <expr> (<expr>) + <expr> (<expr> - <expr>) + <expr> (<const> - <expr>) + <expr> (<const><const> - <expr>) + <expr> (1<const> - <expr>) + <expr> (10 - <expr>) + <expr> (10 - <const>) + <expr> ... (10 - 2) + 3
ÁrvoreSintática (10 – 2) + 3 <expr> <expr> + <expr> (<expr>) <const> <expr> - <expr> <const> <const> 10 2 3
<expr> <expr> + <expr> <expr> - <expr> 10 2 3 Gramáticas Ambíguas 10 – 2 + 3 <expr> <expr> - <expr> <expr> + <expr> 10 2 3
<expr> <expr> + <termo> <expr> - <termo> 10 2 3 Gramáticas <expr> <expr> + <termo> | <expr> - <termo> | <termo> <termo> (<expr>) | <const> <expr> • <expr> + <termo> • <expr> - <termo> + <termo> • <termo> - <termo> + <termo> • 10 – 2 + 3
1 + 2 * 3 <expr> Gramáticas <expr> <expr> + <termo> | <expr> - <termo> | <termo> <termo> <termo> * <fator> | <termo> / <fator> | <fator> <fator> (<expr>) | <const> <expr> + <termo> <termo> * <fator> 3 2 3
Gramáticas <expr> <expr> + <termo> | <expr> - <termo> | <termo> <termo> <termo> * <fator> | <termo> / <fator> | <fator> <fator> (<expr>) | <const> 1 + 2 * 3 <expr> <termo> <termo> * <fator>
Tradução Dirigida pela Sintaxe Programa Fonte Analisador Léxico token Solicita token Analisador Sintático Analisador Semântico Código Intermediário
Gramáticas - Exercícios • Considerando a gramática apresentada anteriormente derive as expressões e apresente a árvore sintática correspondente:(1 + 2) * 3 (1 – 2) + 3 * 4 • Altere a gramática para incluir o operador unário -, esse operador deve ter precedência maior que os outros operadores. • Altere a gramática para que os operadores de adição, subtração, multiplicação e divisão tenham associatividade da direita para a esquerda. • Defina uma gramática para expressões aritméticas (operadores +, -, *, /) pós fixadas .
Gramáticas Dados 2 conjuntos independentes de símbolos: • Vt – Símbolos terminais • Vn – Símbolos não terminais. Uma gramática é definida como a quádrupla: {Vn, Vt, S, P} Onde, S Vn é o símbolo inicial da gramática. P é um conjunto de regras de reescrita na forma: , sendo: (Vn Vt)* Vn (Vn Vt)* (Vn Vt)*
Classificação de Gramáticas • Irrestritas – nenhuma restrição é imposta • Sensíveis ao Contexto - || || • Livres de Contexto - Vn (Vn Vt)+ • Regulares - Vn tem a forma a ou aB, onde a Vt e B Vn
Gramáticas Regulares C 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 0C | 1C | 2C | 3C | 4C | 5C | 7C | 8C | 9C C CC | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 C digito digito* digito 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Especificação • Análise Léxica – expressões regulares • Análise Sintática – gramáticas livre de contexto. • Análise Semântica – sistema de tipos (regras de inferência), semântica denotacional, semântica operacional, semântica de ações. • Geração/Otimização de Código – linguagens para descrição de arquiteturas.
Linguagens Regulares • Gerada a partir de uma gramática regular. • Pode ser representada através de uma expressão regular. • Pode ser reconhecida por um Autômato Finito. • Considerando linguagens compostas por símbolos 0 e 1 podemos afirmar: a linguagem L01 ={0n1n| n 1} não é regular;a linguagem L01 ={0n1m | n 1, m 1} é regular;
Expressões Regulares Maneira compacta de representar linguagens regulares. É composta de 3 operações, sendo e1 e e2expressões geradas por duas linguagens regulares L1 e L2 respectivamente • Concatenação: e1e2 = { xy | x L1 ey L2} • Alternação: e1|e2 = { x | x L1 ou x L2} • Fechamento: e1* = zero ou mais ocorrências de e1. • É definida a precedência desses operadores como sendo: fechamento, concatenação, alternação (da maior precedência para a menor).
Expressões Regulares Exemplos: identificador (letra | _) (letra | digito | _)* letra a | b | ... | A | B | ... digito 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 constInt digito digito* constDouble digito digito*.digito* | . digito digito*
Autômato Finito A linguagem gerada por uma gramática regular pode ser reconhecida por um autômato finito. Um autômato finito consiste em: • Um conjunto finito de estados. • Um conjunto finito de símbolos de entrada (alfabeto). • Uma função de transição que tem como argumentos um estado e um símbolo de entrada e retorna a um estado. • Um estado inicial. • Um conjunto de estados finais também chamados estados de aceitação.
letra | digito | _ letra | digito | _ letra | _ letra | _ . digito digito digito digito . Autômato Finito
letra | digito | _ letra | _ f r o Autômato Finito AFN – Autômato Finito Não Determinista letra | digito | _ letra | _ AFD – Autômato Finito Determinista ld ld ld f o r Onde ld representa letra | digito | _ (com exceção da letra que faz a transição para outro estado).
Autômato Finito Implementação letra | digito | _ letra | _ 0 1 digito digito 2
Geradores de Analisadores Léxicos delim [ \t] ws {delim}+ letra [A-Za-z] digito [0-9] id {letra}({letra}|{digito})* int {digito}+ real {digito}+\.{digito}*(E[+-]?{digito}+)? char '{letra}' string '({letra}|{digito}|[ \t\\:])*' %% {char} {yylval.ptr=insereTab(&TabSimb[0], yytext);return TCCHARACTER;} {string} {yylval.ptr=insereTab(&TabSimb[0], yytext);return TCSTRING;} \n {num_linhas++;} FUNCTION {return TFUNCTION;} INTEGER {return TINTEGER;} ARRAY {return TARRAY;} IF {return TIF;} {id} {yylval.ptr=instalar(yytext); return TID;} "<" {return TMENOR;}
Análise Léxica - Exercícios • Escreva uma gramática, expressão regular e AFD que defina os números binários terminados em zero. • Mostre uma expressão regular e o AFD correspondente a gramática abaixo:S aS B bC C aC | aB | a • Escreva uma expressão regular para as constantes double da linguagem C.Dica pode-se usar o símbolo para indicar uma “cadeia” vazia.
Analisador Sintático • Obtém uma seqüência de tokens fornecidos pelo analisador léxico e verifica se a mesma pode ser gerada pela gramática. • Os métodos de análise sintática comumente usados em compiladores são classificados como: • Métodos top-down. • Métodos bottom-up. • Os métodos mais eficientes, tanto top-down quanto bottom-up, trabalham com uma subclasse de gramáticas livres de contexto.
Métodos top-down • Podem ser vistos como a tentativa de encontrar a derivação mais a esquerda para uma cadeia de entrada. Partindo do símbolo inicial da gramática são aplicadas sucessivas derivações tentado produzir a cadeia que se deseja reconhecer. • Exemplos: • Método descendente recursivo. • Método LL(1).
Método Descendente Recursivo <expr> + <expr> <expr> | - <expr> <expr> <expr> <const> <const> 0 | 1| 2 | 3| 4 | 5 | 6 | 7 | 8| 9
Método Descendente Recursivo void cons() { if (isdigit(lookahead)) nextToken(); else erro("Erro sintático"); } void expr () { if (lookahead == '+' || lookahead == '-') { nextToken(); expr(); expr(); } else cons(); }
Analisadores Sintáticos Preditivos Escrevendo a gramática de forma cuidadosa podemos obter uma gramática processável por um analisador sintático que não necessite de retrocesso. Dado um símbolo de entrada a e um não terminal A a a ser expandido, a gramática deve possuir uma única produção que leve ao reconhecimento da cadeia iniciada com a. Analisadores sintáticos não preditivos (ou não determinísticos) necessitam de retrocesso e em geral são ineficientes.
Fatoração a Esquerda As vezes é necessário fazer alterações na gramática que possibilitem a implementação de um reconhecedor preditivo: <cmd> if <expr> then <cmd> else <cmd> | if <expr> then <cmd> <cmd> if <expr> then <cmd><cmd’> <cmd’> else <cmd> |
Fatoração a Esquerda A 1 | 2 |...| n | 1 | 2 | ... | n A A’ | 1 | 2| ... | n A’ 1 | 2 | ...| n
Eliminação da Recursividade a Esquerda • E • E – T • E + T – T • T + T – T * c + c - c E E + T | E - T | T T c | (E)
Eliminação da Recursividade a Esquerda A A1 | A2 | ... |1|2| ... |m A 1A’|2A’| ... |mA’ A’ 1A’ | 2A’ | ... |nA’ | E TE’ E’ +TE’ | -TE’ | T c | (E) E E + T | E - T | T T c | (E)
Análise Sintática Preditiva não Recursiva LL(1) E TE’ E’ +TE’ | T FT’ T’ * FT’ | F c | (E) • E • TE’ • FT’E’ • cT’E’ • cE’ • c+TE’ • c+FT’E’ • c+cT’E’ • c+c*FT’E’ • c+c*cT’E’ • c+c*cE’ • c+c*c
Analisador Sintático LL(1) Considerando w a cadeia de entrada. Empilhar #, Empilhar o símbolo inicial da gramática. Faça p apontar para o primeiro símbolo de w# Repetir Seja X o símbolo no topo da pilha e a o símbolo apontado por p; Se X for um terminal ou # então Se X = a então Remover X da pilha e avançar p; Senão erro. Senão /* X não é um terminal */ Se M[X, a] = X Y1Y2...Yk então Remover X da Pilha Empilhar Yk...Y2Y1 Senão erro Até que X = #
Analisador Sintático LL(1) Uma gramática cuja tabela não possui entradas multiplamente definidas é dita LL(1). O primeiro L indica a varredura da cadeia de entrada, que e feita da esquerda para a direita (left to right) o segundo L indica que são aplicadas derivações mais a esquerda (left linear). O 1 indica que é necessário apenas um símbolo para decidir que produção aplicar (1 lookahead).
Construção da Tabela LL(1) A construção de um analisador sintático preditivo e auxiliada por duas funções associadas a gramática: PRIMEIRO e SEGUINTE (FIRST e FOLLOW) Seja uma cadeia qualquer de símbolos gramaticais, PRIMEIRO() representa o conjunto de símbolos terminais que começam as cadeias derivadas a partir de . Se * então também é um elemento de PRIMEIRO(). PRIMEIRO(E) = { (, c} E E + T E T T T * F T F F (E) F c E T F (E) E T F c
Construção da Tabela LL(1) SEGUINTES(A), para um não terminal A, é o conjunto de terminais a tais que existe uma derivação S * Aa, para algum e , onde S é o símbolo inicial da gramática. Ou seja o conjunto de símbolos que podem ocorrer após o não terminal A em alguma forma sentencial da gramática. SEGUINTES(F) = { +, #, *, ) } E E + T E T T T * F T F F (E) F c E E + T T + T F+ T E E + T E + T E + F# E T T * F F* F E T F (E) (E + T) (E + F)
Construção da Tabela LL(1) • Entrada: Gramática • Saída: Tabela M • Para cada produção A da gramática faça: • Para cada terminal a em PRIMEIRO(), adicione A em M[A, a]. • Se estiver em PRIMEIRO(), adicione A em M[A, b], para cada terminal b em SEGUINTE(A). • Cada entrada indefinida em M indica uma situação de erro.
Construção da Tabela LL(1) E TE’ E’ +TE’ | T FT’ T’ * FT’ | F c | (E) PRIMEIRO (TE’) = {c, ( } PRIMEIRO (+TE’) = {+ } SEGUINTE (E’) = { ), # } PRIMEIRO (FT’) = {c, ( } PRIMEIRO (*FT’) = { * } SEGUINTE (T’) = { +, ), # } PRIMEIRO (c) = {c} PRIMEIRO(E) = { ( }
Métodos bottom-up • Podem ser vistos como a tentativa de se reduzir a cadeia de entrada ao símbolo inicia da gramática. • Exemplos: • Precedência de Operadores; • SLR(1), LR(1), LALR(1)
Métodos LR(k) Os métodos de análise sintática LR executam uma derivação mais a direita ao contrário. O L significa que a varredura da entrada e feita a esquerda para a direita (left to right), o R que a derivação correspondente é a derivação mais a direita (rightmost derivation) e o k indica o número de símbolos de entrada que tem que ser examinados para se tomar uma decisão na análise sintática. Nós métodos SLR e LALR o que varia são as tecnicas usadas para construir a tabela sintática.