Analisador sintático: Tipos de análises sintáticas Tanto os métodos descendentes como os ascendentes constroem a árvore de derivação da esquerda para direita. A escolha das regras basea-se na cadeia a ser reconhecida, que é lida da esquerda para a direita. Analisador sintático: Análise Sintática Ascendente Na análise sintática ascendente a construção da árvore de derivação para uma determinada cadeia (lexema) começa pelas folhas da árvore e segue em direção de sua raiz. Caso seja obtida uma árvore cuja raiz tem como rótulo o símbolo inicial da gramática, e na qual a sequência dos rótulos das folhas forma a cadeia dada, então a cadeia é uma sentença da linguagem, e a árvore obtida é a sua árvore de derivação. Analisador sintático: Análise Sintática Ascendente Embora a árvore de derivação seja usada para descrever os métodos de análise, na prática ela não precisa ser efetivamente construída. A única estrutura de dados necessária para o processo de análise sintática é uma pilha. Guarda informação sobre os nós da árvore de derivação relevantes em cada fase do processo Analisador sintático: Análise Sintática Ascendente Definição: Redução de uma cadeia → partir das folhas em direção à raiz de uma árvore de derivação. A cada passo, procura-se reduzir uma cadeia (ou subcadeia) ao seu símbolo de origem, objetivando-se atingir o símbolo inicial da gramática. Analisador sintático: Análise Sintática Ascendente Etapas da redução de uma cadeia 1) Toma-se uma determinada cadeia . 2) Procura-se por uma sub-cadeia a1,a2,...an de que possa ser substituída pelo seu símbolo de origem . Ou seja, → a1,a2,...an . Assim a1,a2,...an b = a1 a2. Analisador sintático: Análise Sintática Ascendente 3) Repetir o passo (2) até que = símbolo inicial da gramática. Caso isso não seja possível, tem-se que a cadeia analisada não pertence a linguagem especificada. Analisador sintático: Análise Sintática Ascendente Analisador sintático: Análise Sintática Ascendente Outro exemplo a cadeia x = a+a*a e produção Nesse caso, a ordem das regras corresponde a uma derivação à direita, porém de trás para frente Analisador sintático: Análise Sintática Ascendente Problemas: Os maiores problemas na análise sintática ascendente residem na dificuldade de determinação de qual parcela da cadeia (subcadeia) que deverá ser reduzida; Qual será a produção a ser utilizada na redução, para o caso de existirem mais do que uma possibilidade. Analisador sintático: Análise Sintática Descendente Na análise sintática descendente tem-se procedimento inverso ao da ascendente. Aqui a análise parte da raiz da árvore de derivação e segue em direção as folhas, ou seja, a partir do símbolo inicial da gramática vai-se procurando substituir os símbolos não-terminais de forma a obter nas folhas da árvore a cadeia desejada. Analisador sintático: Análise Sintática Descendente Etapas da redução de uma cadeia 1) Toma-se uma determinada cadeia b e o símbolo inicial da gramática. 2) Procura-se por uma regra de derivação que permita derivar em sua totalidade ou pelo menos se aproximar desta. 3) Repetir o passo (2) até que a cadeia não apresente mais símbolos não terminais, verificando se a cadeia obtida coincide com . Não havendo coincidência, a cadeia analisada não pertence a linguagem especificada. Analisador sintático: Análise Sintática Descendente Exemplo Considere a seguinte gramática e a cadeia x = a+a*a Analisador sintático: Análise Sintática Descendente Analisador sintático: Análise Sintática Descendente Problemas: Embora apresente os mesmos problemas que a analise sintática ascendente, implementa facilmente compiladores para linguagens obtidas de gramáticas LL(1) (análise da cadeia da esquerda para a direita e derivações esquerdas observando apenas o primeiro símbolo da cadeia para decidir qual regra de derivação será utilizada). Gramática: Derivações e Precedência Gramática: Ambigüidade Definição 1 Ambigüidade refere-se geralmente a confusão na GLC Sobrecarga (Overloading) pode criar ambigüidade f(17) Em algumas linguagens, f pode ser uma função ou uma macro Para eliminar ambigüidade é preciso conhecer contexto • Requer valores de declarações • É uma questão de tipo, uma gramática livre de contexto não é suficiente • Requer uma solução extra-gramatical (não em GLC) Ambigüidade: Uma gramática que produz mais de uma árvore de derivação para alguma sentença é considerada ambígua Uma GLC é ambígua se permitir mais de 1 derivação mais à esquerda ou mais de 1 derivação mais à direita para a mesma sentença Gramática: Ambigüidade Definição 2 • Se uma gramática tem mais do que uma derivação mais à esquerda ou a direita, então a gramática é ambígua • Se houver mais do que uma árvore gramatical, então a gramática é ambígua Exemplo clássico — o problema do if-then-else Esta ambigüidade é de natureza inteiramente gramatical Gramática: Ambigüidade Esta sentença tem duas derivações producão 2, depois producão 1 produção 1, depois produção 2 Gramática: Ambigüidade Removendo ambigüidade Associar cada else ao then anterior mais próximo ainda não associado if (regra de senso comum) Com esta gramática, o exemplo tem apenas uma derivação Gramática:LL(1) e LR(1):Técnicas de parsing Top-down parsers (LL(1) derivação mais a esquerda, recursivo descendente) • Inicia na raiz da árvore e cresce em direção as folhas • Obtém uma produção e tenta associar a entrada • Se escolha errada pode tentar retroceder (backtrack) • Algumas gramáticas são livre de retrocesso (parsing preditivos ) • Não podem tratar com gramáticas recursivas a esquerda Bottom-up parsers LR(1) • L é porque a entrada é da esquerda para a direita • R é porque é feito usando derivação mais a direita e o 1 porque avalia um token adiante • Inicia nas folhas e cresce até a raiz • Inicia em um estado válido para os primeiros tokens Gramática: Eliminações Eliminações: Utiliza Gramáticas Livres de Contexto (GLC) para descrever a sintaxe das construções de linguagem de programação como expressões e comandos Derivações: a partir do símbolo inicial de uma regra, substitui-se um não terminal pelo corpo de uma de suas produções -O não terminal em cada passo é escolhido: Em derivações mais à esquerda (lm), o não terminal mais à esquerda em cada forma sentencial sempre é escolhido Em derivações mais à direita (rm), o não terminal mais à direita em cada forma sentencial sempre é escolhido Gramática: Eliminações Eliminação (fatoração) de recursão à esquerda - Métodos de análise descendente não tratam recursões à esquerda - Eliminação de recursão à esquerda imediata: Gramática: Eliminações Eliminação de recursividade à esquerda Gramática: Eliminações Exemplo de Fatoração a Esquerda Cmd if Expr then Cmd else Cmd | if Expr then Cmd | Outro A a A b • Fatorando a esquerda: Cmd if Expr then Cmd else Opc | Outro A b X else Opc if Expr then Cmd else else Opc | X a X | Gramática: Eliminações Gramática: Eliminações Fatoração de uma gramática • Elimina indecisão de qual produção aplicar quando duas ou mais produções iniciam com a mesma forma sentencial. Por exemplo Gramática: Eliminações Algoritmo: - Entrada: gramática G - Saída: gramática G, fatorada à esquerda 1) para cada não terminal A, encontrar prefixo a mais longo e comum a duas ou mais de suas alternativas 2) Se a!=ℇ, então existe um prefixo comum não trivial, substitua todas as produções A, A → aB1 | aB2 | ... | aBn | y, onde y representa todas as alternativas que não começam com a, por: - A→ aA' | y - A‘→ B1 | B2 | ... | Bn Gramática: Eliminações Gramática: Eliminações Gramática: Eliminações Análise Descendente(Top-Down) Análise Descendente(Top-Down) • Como implementar um reconhecedor para uma GLC? • Constrói-se a árvore de derivação, lendo a sentença de esquerda para a direita, e substituindo sempre o não-terminal mais à esquerda. • Existe três tipos principais de parser top-down: – Recursivo com retrocesso – Recursivo preditivo – Tabular preditivo. Análise Descendente(Top-Down) Análise Descendente(Top-Down) Implementação do reconhecedor • Cria-se um procedimento por não-terminal – Ele testa a aplicação de cada produção associada ao não terminal; – Lê no texto de entrada o próximo token • Chama o analisador lexical (yylex()) ! • É necessário lembrar onde se fez uma escolha de uma alternativa, para poder retroceder neste ponto. Análise Descendente(Top-Down) ReconheceTIPO() { switch(token) { case {integer, char, num}: ReconheceTIPOSIMPLES(); break; case ^: reconhece( id) ; break; } } ReconheceTIPOSIMPLES() { switch(token){ case integer: reconhece(integer); break; case char: reconhece(char); break; case num: reconhece(num); reconhece(pp); reconhece(num); break; default: erro(); } } Análise Descendente(Top-Down) Observações sobre o método recursivo com retrocesso • É fácil de implementar. • É necessário: 1. Que a gramática não seja recursiva à esquerda • A Aa se tornará ReconheceA() { ReconheceA();... } • Recursão infinita! 2. Que a gramática seja fatorada à esquerda • Senão, deve-se fazer retrocesso. 3. Que os primeiros terminais deriváveis possibilitem a decisão de uma produção a aplicar! • Não há retrocesso sobre não-terminais... Análise Descendente(Top-Down) Definição: Conjuntos “First” • First(α): – Definição informal: • conjunto de todos os terminais que começam qualquer seqüência derivável de α. Análise Descendente(Top-Down) Condição para que se possa usar um analisador preditivo Análise Descendente(Top-Down) Análise Descendente(Top-Down) O que acontece se First(A)? Análise Descendente(Top-Down) Para tratar o caso do ε: o conjunto Follow • Follow(B): – conjunto de terminais que podem aparecer à direita de um não-terminal B em uma sentença válida. – $ passa a denotar um terminal “virtual” que marca o fim da entrada (EOF, CTRL-D,…) • Análise Descendente(Top-Down) Exemplo First/Follow SAB Ac| B cbB | ca First(A) = {c, } Follow(A) = {c} First(B) = {c} Follow(B) = {$} First(S) = {c} Follow(S) = {$} Análise Descendente(Top-Down) Análise Descendente(Top-Down) Observações First/Follow • Só terminais entram em First e Follow. • O algoritmo de cálculo de First(α): – É trivial quando α é um terminal t. – varre as produções X → tω quando α é um não-terminal X; – Inclui ε apenas quando X pode derivar em ε. • O algoritmo de cálculo de Follow(X) – É reservado aos não-terminais X – Inclui o $ em alguns casos triviais (X == o start S) – Varre as produções onde X aparece à direita (A → ωX ω’) – NUNCA inclui ε Análise Descendente(Top-Down) Exemplo First/Follow S XYZ X aXb | Y cYZcX | d Z eZYe | f First(X) = {a, } Follow(X) = {c, d, b, e, f} First(Y) = {c, d} Follow(Y) = {e, f} First(Z) = {e, f} Follow(Z) = {$, c, d} First(S) = {a, c, d} Follow(S) = {$}