410 likes | 577 Views
Análise Léxica e Sintática. Teoria e Implementação de Linguagens Computacionais - IF688 – 2007.1. Fases da compilação Analise Lexica Tokens, lexemas, expressões regulares e autômatos finitos Analise Sintática Gramáticas e parsers Parser trees Derivações Gramáticas ambíguas.
E N D
Análise Léxica e Sintática Teoria e Implementação de Linguagens Computacionais - IF688 – 2007.1
Fases da compilação Analise Lexica Tokens, lexemas, expressões regulares e autômatos finitos Analise Sintática Gramáticas e parsers Parser trees Derivações Gramáticas ambíguas Ambigüidade aritméticas Parser recursive descendent Recursão à esquerda Gramáticas LL(k) Gramáticas LR(k) Outras gramáticas Dangling else Parsing LR de gramáticas ambíguas AST Referências Roteiro
Atenção! • Este material não substitui a leitura da bibliografia • Sugerimos pesquisar a leitura referenciada no final deste trabalho e no site da disciplina
beginifx = 5then... Processo de Compilação output 1100111 0011100011 + params Programa Código Fonte Compilador
Fases da compilação Código fonte Análise Léxica tokens e lexemas implementação abstração Árvoresintáticaabstrata Análise Sintática Análise Semântica AST decorada CódigoMáquina Geração de Código
Background Acadêmico - CIn IP Lógica Teórica
"n" id intLit intLit "1" "0" Análise Léxica O analisador léxico é responsável por traduzir o arquivo fonte em lexemas e tokens if (n == 0) { return 1; } else { ... } if LPAR assign RPAR LCUR return comm RCUR else ...
Reconhecendo tokens Expressões regulares (implementadas como Autômatos Finitos) são comumente utilizadas Exemplos: if IF [a-z][a-z0-9]* ID [0-9]+ NUM
IF i f 1 2 ID a-z a-z 2 3 1 0-9 Reconhecendo tokens
Análise Sintática “syn-tax: the way in wich words are put together to form phrases, clauses or setences.” Webster´s Dictionary A seguinte construção é válida? int y = 0,k = 0; int x = y+++k;
Análise Sintática O Analisador Sintático é responsável por verificar quando uma sentença faz parte da gramática da linguagem. Entrada: lexemas e tokens gerados pelo analisador léxico
Gramáticas – descrevendo linguagens Gramáticas livres de contexto são utilizadas para descrever linguagens de programação • Produções • Símbolos terminais • Símbolos não-terminais • Símbolo inicial
S → S ;S S → id:=E S → print(L) E → id E → num E → E+E E → (S ,E) L → E L → L ,E Terminais: id print , + ; := ( ) Não terminas: S E L Símbolo inicial: S → é utilizado na notação de produções A cadeia seguinte pertence à gramática? a := 7; b := c + (d := 5 + 6, d) Exemplo
Derivações Para determinar se uma cadeia pertence à gramática pode ser utilizado o processo de Derivação: S S ; S S ; id := E id := E ; id := E id := num ; id := E id := num ; id := E + E id := num ; id := E + (S, E) id := num ; id := id + (S, E) id := num ; id := id + (id := E, E) id := num ; id := id + (id := E + E, E) id := num ; id := id + (id := E + E, id) id := num ; id := id + (id := num + E, id) id := num ; id := id + (id := num + num, id)
Parse tree S S S ; E id := E id := num + E E ) S E ( , id A Parse Tree é construída conectando cada derivação a sua origem. Na prática não é implementada pelos compiladores. id E id := E E + num num
Gramáticas ambíguas • Uma gramática é ambígua se a partir dela uma sentença pode dar origem a duas arvores de parsing diferentes • Indeterminismo é problemático para a compilação • Eliminação de ambigüidade é quase sempre possível • Refatoração da gramática
Gramáticas ambíguas x := 1 + 2 + 3; S S E E id := id := E E + E + E num E E + E num + E num num num num
S → S ;S S → id:=E S → print(L) E → id E → num E → E + E E → (S ,E) L → E L → L ,E S → S ;S S → id:=E S → print(L) E → id E → num E → E + T E → T E → (S ,E) L → E L → L ,E Gramática refatorada
Parsers • Utilizados para avaliar uma entrada quanto à sintaxe • Podem ser • Top-down • Recursive-descent / LL(k) • Bottom-up • SRL, LR(k)
Parser Recursive descent • Algoritmo baseado em previsões • Também conhecido como Predictive Parsing • Funções mutuamente recursivas • Simples implementação • Uma função para cada não-terminal • Uma cláusula para cada produção • Verifica o primeiro símbolo terminal para decidir qual função usar
Parser Recursive descent • Desenvolvendo um recursive descentparser • Cada não terminal 'X' dará origem a um método/função parseX(); • Produções do tipo 'A | B' darão origem a cláusulas cases
Parser Recursive descent parseA() { accept(‘a’); parseB(); accept(‘c’); parseC(); } parseB() { case (d): parseC(); parseB(); case (c): accept(‘c’); parseC(); } A ::= aBcC B ::= CB | cC C ::= da parseC() { accept(‘d’); accept(‘a’); }
Recursive descent • Na prática constrói uma tabela de produções indexadas por não-terminais e terminais A ::= aBcC B ::= CB | CA C ::= da
Recursive descent • Vantagens • Fácil de implementar • Fácil de entender • Desvantagens • Performance deficiente • Gramática reconhecida possui restrições • Sem recursão à esquerda • Deve estar fatorada
Recursive descent A ::= aBcC B ::= CB | CA C ::= da A ::= aBcC B ::= CX X ::= B | A C ::= da Gramática LL(1)
Gramáticas e Parsers LL(1) • Gramáticas SEM entradas duplicadas na tabela são conhecidas como LL(1) • LL(1) - Left-to-right, leftmost-derivation, 1-symbol lookahead • Left-to-right– direção na qual os símbolos serão examinados • Leftmost-derivation – ordem pela qual os símbolos não-terminais serão expandidos • 1-symbol lookahead– não mais que um símbolo será avaliado por vez • Existem LL(2), LL(3),... • Toda LL(1) é LL(2), toda LL(2) é LL(3),... LL(k)
LL(1) na prática - Applet http://ag-kastens.uni-paderborn.de/lehre/material/uebi/parsdemo/LL1Parser.html
Recursão à esquerda Gramáticas LL(1) são vulneráveis às entradas duplicadas. Por exemplo, o fragmento a seguir: E → E + T E → T O fato de E aparecer no início do lado direito da produção é a causa do problema. Isso é conhecido como Recursão à Esquerda. Para corrigir isso, vamos refatorar a gramática, com Recursão à Direita: E → T E´ E´ → +T E´ E´ →
Gramáticas e Parsers LR(1) • As fraquezas de LL(k) são superadas pela técnica LR(k) • LR(1) - Left-to-right, rightmost-derivation, 1-symbol lookahead • Uso de uma pilha para armazenar símbolos de forma temporária • Possui duas operações, shift e reduce • shift: Move o primeiro símbolo para o topo da pilha • reduce: escolhe uma regra da gramática do tipo X→A B C. pushX da pilha e popC B A.
Outros Parsers LR • LR(0) • Olham apenas para a pilha • SLR • Melhoramento sobre o LR(0) • LR(1) • Lookahead de 1 símbolo • Consegue descrever a maioria das linguagens de programação • LALR(1) • Melhoramento sobre o LR(1) • Diminuí o tamanho da tabela de parsing
shift-reduce na prática - Applet http://ag-kastens.uni-paderborn.de/lehre/material/uebi/parsdemo/SRParser.html
Parsing LR de Gramáticas Ambíguas • Gramáticas ambíguas ocasionam conflitos em parsers LR • Shift-reduce conflict • O parser não consegue decidir se empilha o próximo símbolo da entrada, ou se reduz para uma regra já disponível • Reduce-reduce conflict • O parser pode realizar uma redução para duas regras distintas
Parsing LR de Gramáticas Ambíguas • Caso clássico: dangling-else S ::= 'if' E 'then' S 'else' S S ::= 'if' E 'then' S S ::= ...
Parsing LR de Gramáticas Ambíguas if a then { if b then s1 } else s2 ? if a then if b then s1 else s2 if a then { if b then s1 else s2 }
Parsing LR de Gramáticas Ambíguas • Solução: • Transformar a gramática • Introdução dos conceitos de matched e unmatched S ::= 'if' E 'then' S 'else' S S ::= 'if' E 'then' S S ::= ... S ::= M | U M ::= 'if' E 'then' M 'else' M | ... U ::= 'if' E 'then' S | 'if' E 'then' M 'else' U
Gramáticas não-ambíguas Gramáticas ambíguas LR(k) LL(k) LL(1) LR(1) LALR(1) SLR LR(0) LL(0)
Sintaxe abstrata • Apenas reconhecer se uma sentença pertence ou não a linguagem especificada por uma gramática não é o suficiente • É necessário produzir uma estrutura que sirva de base para a próxima fase do processo de compilação • Parse trees nunca são montadas na prática
AST – Abstract Syntax Tree • Capturam a essência da estrutura de uma gramática abstraindo não-terminais • Representação possível • Java: Classes que possam se relacionar a fim de montar uma árvore • Pode ser produzida através da inserção de ações semânticas no parser
AST – Abstract Syntax Tree IfThenElse ::= 'if' expr 'then' comm1 'else' comm2 return new IfThenElse(expr, comm1, comm2);
Referências • Análises léxica e sintática, Mauro La-Salette C. L. de Araújo • Modern Compiler implementation in Java, Andrew W. Appel