520 likes | 709 Views
Compiladores Análise Sintática. Guilherme Amaral Avelino gavelino@gmail•com. Analisador sintático ( parser ) é o responsável por verificar se as construções utilizados no programa estão gramaticalmente corretas. Envia token. Árvore de derivação. Programa fonte. Analisador sintático.
E N D
Compiladores Análise Sintática Guilherme Amaral Avelino gavelino@gmail•com
Analisador sintático (parser) é o responsável por verificar se as construções utilizados no programa estão gramaticalmente corretas Envia token Árvore de derivação Programa fonte Analisador sintático Analisador léxico Solicita novo token Tabela de símbolos
Reconhecimento de uma Linguagem • Toda linguagem tem de ter regras que descrevem sua estrutura sintática (ou sintaxe) • A sintaxe pode ser descrita através de uma gramática ou pela notação BNF • Vantagens de se utilizar uma gramática: • Fornece uma especificação sintática precisa e fácil de entender • Para certas classes de gramáticas, podemos construir automaticamente um analisador sintático e o gerador automático pode certas ambigüidades sintáticas da LP, difíceis de serem identificadas diretamente pelo projeto do compilador • Novas construções que surgem com a evolução da linguagem podem facilmente ser incorporadas a um compilador se este tem sua implementação baseada em descrições gramaticais
Descrição de Uma Linguagem Através de uma Gramática • Linguagens regulares não são capazes de identificar recursões centrais • E = x | “(“ E “)” • Solução: Uso de gramáticas livres de contextos • Uma Gramática Livre de Contexto é construída utilizando símbolos terminais e não-terminais, um símbolo de partida e regras de produções, onde: • Os terminais são os símbolos básicos a partir dos quais as cadeias são formadas• Na fase de análise gramatical os tokens da linguagem representam os símbolos terminais• Ex: if, then, else, num, id, etc•
Gramática Livre de Contexto • Os não-terminais as variáveis sintáticas que denotam cadeias de caracteres• Impõem uma estrutura hierárquica que auxilia na análise sintática e influencia a tradução• Ex: cmd, expr• • Numa gramática um não terminal é distinguido como símbolo de partida, e o conjunto que o mesmo denota é a linguagem definida pela linguagem• Ex: program • As produções de uma gramática especificam como os terminais e não-terminais podem se combinar para formas as cadeias da linguagem• Cada produção consiste em um não terminal seguido por uma seta (ou ::=), serguido por uma cadeia de não terminais e terminais
Gramática Livre de Contexto • Ex: expr ::= expr op expr expr ::= (expr) expr ::= - expr expr ::= id op ::= + op ::= - op ::= * op ::= / • Simbolos terminais • id+ - * / ( ) • Símbolos não-terminais • expr e op , sendo expr o símbolo de partida
Convenções Notacionais • Símbolos Terminais • Letras minúsculas do inicio do alfabeto, tais como a, b c • Símbolos de operadores, tais como +, -, etc • Símbolos de pontuação, tais como parênteses e vírgulas • Dígitos 0, 1, •••, 9 • Cadeias em negritos como id ou if • Símbolos não-terminais • Letras maiúsculas do início do alfabeto, tais como A, B, C • A letra S, quando aparecer é usualmente símbolo de partida • Nomes em itálico formados por letras minúsculas, como expr ou cmd • A menos que seja explicitamente estabelecido, o lado esquerdo da primeira produção é o símbolo de partida
Gramáticas • Produções para o mesmo símbolo não terminal a esquerda podem ser agrupadas utilizando “|”• Ex: A::= +|-|••• • Exemplo: expr ::= expropexpr expr ::= (expr) expr ::= - expr expr ::= id op ::= + op ::= - op ::= * op ::= / E ::= E A E|(E)|-E| id A ::= +|-|*|/
Grafos de Sintaxe • Grafo direcionado contendo dois tipos de vértices • Vértices em elipse para representar os símbolos terminais • Vértices retangulares para não terminais
Árvores Gramaticais A ••• X1 Xn X2 • Representação gráfica de uma derivação • Dá forma explícita a estrutura hierárquica que originou a sentença • Dada uma GLC, a árvore de derivação é obtida: • A raiz da árvore é o símbolo inicial da gramática • Os vértices interiores são obrigatoriamente não-terminais• Ex: Se A ::= X1X2•••Xn é uma produção da gramática, então A será um vétice interior e X1, X2, •••, Xn serão os filhos (da esquerda para a direita) • Símbolos terminais e a palavra vazia são as folhas
Árvores de Derivação • Exemplo: -(id + id) E E::=-E - E E::=(E) ( E ) E::=E+E + E E id Id E::=id E::=id
Derivações • Processo através do qual as regras de produções da gramática são aplicadas para formar uma palavra ou verificar se esta pertence a linguagem • Símbolo não terminal é substituído pelo lado direito da produção correspondete • Ex: -( id + id ) E => -E => -(E) => -(E+E) => -(id + E) => -(id + id) • Dois passos: • Qual terminal será escolhido para derivar • Derivação mais a esquerda • Derivação mais a direita • Qual regra utilizar
Ambigüidade • Se uma gramática possui mais de uma árvore gramatical para uma mesma sentença é dita ambígua • Parte do significado dos comandos de uma linguagem podem estar especificado em sua estrutura sintática • Ex: id + id * id possui duas derivações mais a esquerda E E + + id E id E * E E * E E id id id id
Ambigüidade • Regras de precedência • Reescrita da gramática expr ::= term | term op1 term term ::= fator | fator op2 fator fator ::= id | (expr) op1 ::= + op1 ::= - op2 ::= * op2 ::= / expr ::= expropexpr expr ::= id op ::= + op ::= - op ::= * op ::= /
cmd ::= if expr then cmd |if expr then cmd else cmd |outro if E1 then S1 else if E2 then S2 else S3 cmd if expr then else cmd cmd S1 E1 if expr then else cmd cmd S3 S2 E2
cmd ::= if expr then cmd |if expr then cmd else cmd |outro cmd ifE1thenifE2 thenS1elseS2 if expr then cmd E1 if expr then else cmd cmd E2 S1 S2 cmd Regra geral: associar cada else ao then anterior mais próximo if expr then else cmd cmd S2 E1 if expr then cmd E2 S1
Reescrevendo a Gramática • Todo enunciado entre um thene um elseprecisa ser “associado”, isto é não pode terminar com um then ainda não “associado” • Um enunciado associado ou é um enunciado if-then-else contendo somente enunciados associados ou é qualquer outro tipo de enunciado incondicional cmd ::= cmd_associado |cmd_não_associado cmd_associado ::= ifexprthencmd_associadoelsecmd_associado |outro cmd_não_associado::= ifexprthencmd | ifexprthencmd_associadoelsecmd_não_associado
Eliminação de Recursão a Esquerda • Uma gramática é recursiva a esquerda se possui um não-terminal A, tal que, exista uma derivação A => Aα para alguma cadeia α • É importante para permitir o processamento top-down • Método: • Agrupamos os produções recursivas • A ::= Aα1|Aα2|••• |Aαn |β1|β2|•••|βn • Onde nenhum β começa com um A • Substituímos as produções-A por • A ::= β1A’| β2A’|•••|βnA’ • A’ ::= α1A’| α2A’|•••| αnA’|ε • Ex: E ::= E + T|T T ::= T * F|F F ::= (E)|id E ::= TE’ E’ ::= +TE’ | ε T ::= FT’ T’ ::= *FT’ | ε F ::= (E) | id
Eliminação de Recursão a Esquerda • Recursão não-imediata S ::= Aa | b A ::= Ac | Sd | ε S ::= bS’ S’ ::= daS’| ε S ::= Sda | b ‘ A ::= SdA‘ A’ ::= cA’ | ε A ::= Ac | Sd | ε A ::= Ac | Aad | bd | ε S ::= Aa | b A ::= bdA’ | A’ A’ ::= cA’ | adA’ | ε S ::= bS’ S’ ::= daS’| ε A ::= SdA‘ A’ ::= cA’ | ε
Fatoração À Esquerda • Transformação que facilita a análise sintática • Deve ser realizada quando a escolha entre duas opções começa com uma cadeia comum • Neste caso deve se adiar a escolha • Regra geral: • Se A ::= αβ1 | αβ2 forem duas produções e a entrada começar com uma cadeia não vazia derivada de α, não sabemos se A deve ser expandida para αβ1 ou αβ2 • Devemos, então, adiar a decisão expandido A para αA’ e após ler a entrada derivada de α, expandir A’ para β1 ou β2• • A ::= αA’ A’ ::= β1 | β2
cmd ::= if expr then cmd else cmd |if expr then cmd |outro cmd ::= if expr then cmd cmd’| outro cmd' ::= else cmd | ε α
Análise Gramatical S ::= aS|c w = aac • Processo através do qual é verificado se uma cadeia pode ser gerado pela gramática • Análise Top-Down ou Descendente • Inicia-se na raiz da árvore gramatical e segue em direção as folhas • Em cada passo um lado esquerdo de uma regra de produção é substituído pelo direito até produzir todos os símbolos folha da palavra • Análise Botton-Up • A análise é feita a partir das folhas em direção a raiz • Em cada passo um lado direito de uma regra de produção é substituído por um símbolo não-terminal (redução) até obter o símbolo inicial S (raiz) S S S S a S a S a S a S a S S ::= aS|c w = aac c S S S S S S a a c a a c a a c a a c
Analisador Sintático Top-Down (Descendente) • Produz uma derivação mais a esquerda para uma cadeia de entrada • Tem como principal problema determinar, a cada passo, qual produção deve ser aplicada para substituir um o símbolo não-terminal • Quando uma produção é escolhida, o restante do processo de análise consiste em casar os símbolos terminais da produção com o a cadeia de entrada
Análise Sintática de Descida Recursiva • Consiste em um conjunto de procedimentos, um para cada não terminal da gramática void A(){ escolheProdução-A(); // A:: X1X2•••Xk for (i=1 até k){ if (Xi é um não terminal) executa Xi(); else if (Xi igual a símbolo de entrada a) avança na entrada para o próximo símbolo; else /*ocorre um erro*/ } }
Análise Sintática de Descida Recursiva • Pode exigir retrocesso, resultando em repetidas leituras sobre a entrada (Tentativa e erro) • Deve-se permitir a escolha de mais de uma produção • Um erro em uma tentativa de reconhecimento não deve gerar um erro, mas sim a tentativa de uma nova produção • Um erro só deve ocorrer quando não houver mais nenhuma produção a ser testada e ainda houver símbolos na cadeia de entrada • Para tentar uma nova produção é necessário colocar o apontador de entrada na posição que estava no inicio do processo S ::= cAd A ::= ab | a *Obs: Uma gramática recursiva à esquerda pode fazer com que um analisador recursivo à esquerda entre em loop infinito S c A d a a b
Funções First e Follow • Funções que auxiliam a construção de analisadores sintáticos • Permitem escolher qual produção deve ser aplicada baseada no próximo símbolo de entrada • First • Define o conjunto de símbolos que iniciam derivações a partir de uma seqüência de símbolos terminais e não-terminais • c está em First(A) • Follow • Define o conjunto de símbolos terminais que podem aparecer imediatamente à direita de um dado símbolo não terminal • a está em Follow(A) ••• S α A a β c γ
Função FIRST - Regras • Para calcular FIRST(X) de todos os símbolos X da gramática, as seguintes regras devem ser aplicadas até que não haja mais terminais ou ε: • Se X é um símbolo terminal, então FIRST(X)={X} • Se X é um símbolo não-terminal e X::= Y1Y2•••Yk é uma produção p/ algum k≥1, então: • acrescente a a First(X) se, para algum i, a estiver em FIRST(Yi), e εestiver em todos os FIRST(Y1),••• FIRST(Yi-1)• • adicione εse εestá em FIRST(Yj) para todo j = 1,2,•••k • Se Y1 não derivar ε, nada mais deve ser acrescentado a FIRST(X) • Se X::=ε é uma produção, então acrescente εa FIRST(X)
Função FIRST - Exemplo • Dada a Gramática G=({+,*,(,),id}, {E,T,F,T’,E’}, E, {E::=TE’; E’::=+TE’|ε; T::=FT’; T’=*FT’|ε; F::=(E)|id}), determine: • FIRST(T) = • FIRST(E’) = • FIRST(T’) = FIRST(F) FIRST(() U FIRST(id) {(,id} FIRST(+) U FIRST(ε) {+, ε} FIRST(*) U FIRST(ε) {*, ε}
Função FIRST - Exemplo • Dada a Gramática G=({a,b,c}, {I,A,B}, I, {I::=aBa|BAc|ABc; A::=aA|ε; B::=ba|c}), determine: • FIRST(aBa) = • FIRST(BAc) = • FIRST(ABc) = {a} FIRST(ba) U FIRST(c) {b} U {c} {b,c} FIRST(aA) U FIRST(ε) U FIRST(Bc) {a,ε} U FIRST(ba) U FISRT(c) {a,ε} U {b} U {c} {a,b,c}
Função FOLLOW - Regra • Para calcular FOLLOW(X) de todos os não-terminais A, as seguintes regras devem ser aplicadas até que nada mais possa ser acrescentado a nenhum dos conjuntos FOLLOW: • Se X é o símbolo inicial da gramática coloque $ em FOLLOW(X), onde $ é o marcador de fim da entrada • Se houver uma produção A::αXβ, então tudo em FIRST(β) exceto εestá em FOLLOW(X) • Se houver uma produção A::αX, ou uma produção A::= αXβ, onde o FIRST(β) contém ε, então inclua o FOLLOW(A) em FOLLOW(X)
Função FOLLOW - Exemplo • G=({+,*,(,),id}, {E,T,F,T’,E’}, E, {E::=TE’; E’::=+TE’|ε; T::=FT’; T’=*FT’|ε; F::=(E)|id}) • FOLLOW(E) = • FOLLOW(T) = • FOLLOW(F) = FIRST()) U {$} {),$} FIRST(E’) U FOLLOW(E) {+} U {),$} {+,),$} FIRST(T’) U FOLLOW(T) {*} U {+,),$} {*,+,),$}
Função FOLLOW - Exemplo • G=({a,b,c}, {I,A,B}, I, {I::=aBa|BAc|ABc; A::=aA|ε; B::=ba|c} • FOLLOW(I) = • FOLLOW(A) = • FOLLOW(B) = {$} FIRST(c) U FIRST(Bc) U FOLLOW(A) {c} U {b,c} U ({c} U {b,c} U •••) {b,c} FIRST(a) U FIRST(Ac) U FIRST(c) {a} U {a,c} U {c} {a,c}
Analisadores Sintáticos preditivos • Não necessitam de retrocesso • O símbolo da cadeia de entrada, em análise, é suficiente para determinar qual regra de produção deve ser escolhida • São construídos utilizando gramáticas LL(1) • Cadeia de entrada analisada da esquerda para a direita (left-to-right) • A derivação das produções é feita mais a esquerda (leftmost) • A cada passo é observado um símbolo a frente para determinação de que ação deve ser tomada
Gramáticas LL(1) • Uma gramática G é LL(1) se, e somente se: • A gramática não tiver recursividade a esquerda • For fatorada a esquerda • Para os terminais com mais de uma regra de produção, os primeiros terminais devem ser capazes de identificar, univocamente, a produção que deve ser aplicada a cada instante da análise • Ex: cmd ::= if ( expr ) cmd else cmd |while ( expr ) cmd |{ cmd_list }
Construção da Tabela • Para cada produção A ::= α da gramática faça: • Para cada terminal a em FIRST(A), inclua A::=α em M[A,a] • Se FIRST(α) inclui a palavra vazia, então adicione A::= α a M[A,b] para cada b em FOLLOW(A) E::=TE’ E::=TE’ E’::=ε E’::=+TE’ T::=FT’ T::=FT’ T’::=ε T’::=ε T’::=ε T’::=*FT’ F::=id F::=(E)
Análise Ascendente • Corresponde a construção de uma árvore de derivação para uma cadeia de entrada a partir das folhas (parte de baixo) em direção à raiz (topo) id * id T * id T * F F * id T * F F id T * F id
Analisadores LR(K) • Analisadores redutores eficientes que lêem a sentença em análise da esquerda para a direita (left-to-right) e produzem uma derivação mais à direita (rightmost) ao reverso, considerando k símbolos em cada leitura • São capazes de reconhecer, praticamente todas as estruturas sintáticas definidas por gramáticas livres de contexto • Tem como desvantagem a dificuldade da implementação do mesmo, sendo necessário, em muitos casos, a utilização de ferramentas automatizadas para construção da tabela de análise
Analisadores LR(K) • Os analisadores LR são classificados quanto ao tipo de tabela de análise que utilizam em: • SLR (Simple LR), fáceis de implementar, porém aplicáveis a uma classe restrita de gramáticas • LR Canônicos, mais poderosos, podendo ser aplicados a um grande número de linguagens livres de contexto • LALR (Look Ahead LR), nível intermediário de complexidade e implementação eficiente que funciona para a maioria das linguagens de programação• É utilizado pelo gerador de analisadores sintáticos yacc
Analisadores LR(K) - Funcionamento Xi - símbolo da gramática Ei - estado a1 ••• ai •••• an$ Analisador LR Em Tabela de análise Xm ••• X1 E0 (1) E ::= E + T (2) E ::= T (3) T ::= T * F (4) T ::= F (5) F ::= (E) (6) F ::= id
Analisadores LR(K) - Funcionamento • Seja Em o estado no topo da pilha e ai o token sob o cabeçote de leitura• O analisador consulta a tabela AÇÃO[Em, ai], que pode assumir um dos valores • empilha Ex: causa o empilhamento de "aiEx" • reduz n (onde n é o número da produção A::=β): causa o desempilhamento de 2r símbolos, onde r = |β | e o empilhamento de "AEy" onde Ey resulta da consulta à tabela de TRANSIÇÃO [Em-r*, A]; • aceita: o analisador reconhece a sentença como válida • erro: o analisador para a execução, identificando um erro sintático * Em-r é o estado do topo da pilha após a operação de redução
Construção da Tabela para Analisadores SLR • A construção da tabela de controle para analisadores SLR, baseia-se no Conjunto Canônico de Itens LR(0) o qual serve de base para a construção de um AFD p/ o reconhecimento • Um item LR(0), para uma gramática G, é uma produção com um ponto em alguma posição do lado direito A ::= •XYZ A ::= X•YZ A ::= XY•Z A ::= XYZ• • O ponto é a indicação de até onde uma produção já foi analisado no processo de reconhecimento Inicio da busca por uma cadeia derivável de XYZ X já foi encontrada, continua a busca por YZ XY já foi encontrada, continua a busca por Z Fim da busca• XYZ foi encontrada, podendo ser reduzida p/ A
Funções Closure e Goto • Fechamento de conjuntos de itens (CLOSURE) • Se I é um conjunto de itens para a gramática G, então CLOSURE(I) é construído a partir das duas regras: • Inicialmente, acrescente todo item de I no CLOSURE(I) • Se A→α•Bβ está em CLOSURE(I) e B → γ é uma produção, então adicione o item B →•γ em CLOSURE(I), se ele ainda não estiver lá• Aplique esta regra até que nenhum outro item possa ser incluído no CLOSURE(I) • Exemplo: sendo I = {E´ → •E}, calcule CLOSURE(I) CLOSURE(I) = { E’ → E E → E + T | T T → T * F | F F → (E) | id E → •E+T, E → •T, T → •T*F, T → •F, E´ → •E, F → •(E), F → •id} Aplica regra 1 a I Aplica regra 2
Funções Closure e Goto • Função de Transição (GOTO) • É definida como GOTO(I,X), onde I é um conjunto de itens e X é um símbolo da gramática • Formalmente, GOTO(I,X) é a função CLOSURE do conjunto dos itens A→αX•β, tais que A→α•Xβ pertence a I • Informalmente, consiste em coletar as produções com o ponto no lado esquerdo de X, passar o ponto para a direita de X, e obter a função CLOSURE desse conjunto • Exemplo: sendo I={E’ →E•, E →E•+T}, calcule GOTO(I,+) GOTO(I,+) = { E→ E+•T, E’ → E E → E + T | T T → T * F | F F → (E) | id T→ •T*F, T→ •F, F→•(E), F→ •id } Passa o ponto para o lado direito do símbolo X Calcula CLOSURE(E→ E+•T) Adiciona a novo produção
Conjunto Canônico de Itens void itens(G’){ C = CLOSURE({S’→•S}); repeat for (cada conjunto de itens I em C) for (cada X símbolo da gramática) if (GOTO(I,X) não vazio em não está em C) adicione GOTO(I,X) em C; until nenhum novo conjunto de itens seja adicionado em uma rodada }
I1 E’→E• E→E•+T I9 E→E+T•T→T•*F + E T I0 E’→•E I6 E→E+•T E→•E+T E→•T T→•T*F T→•F F→•(E) F→•id T→•T*F T→•F F→•(E) F→•id id T I2 E→T• T→T•*F * I7 T→T*•F * F F→•(E) F→•id I10 T→T*F• id I5 F→id• id + I8 E→E•+T F→(E•) ( E ( I11 F→(E)• I4 F→(•E) E→•E+T E→•T T→•T*F T→•F F→•(E) F→•id ( T ( id (1) E → E + T (4) T → F (2) E → T (5) F → (E) (3) T → T * F (6)F → id ( F F F I3 T→F•
Construção da Tabela SLR • Seja C={I0, I1, ..., In}. Os estados doa analisador são 0, 1, ..., n. A linha i da tabela é construída a partir do conjunto Ii, como segue: • As ações do analisador para o estado i são determinadas: • Se GOTO(Ii,a) = Ij, então faça AÇÃO[i,a] = empilha j; • Se A→α• está em Ii, então para todo FOLLOW(A), faça AÇÃO[i,a] = reduz n, sendo n o número da produção A→α • Se S’→S• está em Ii, então faça AÇÃO[i,$] = aceita • As transições para o estado i são construídas: • Se GOTO(Ii,A) = Ij, então TRANSIÇÂO(i,A) = j Obs: se ocorrer algum conflito resultante da aplicação das regras descritas, podemos afirmar que a gramática não é SLR(1)
(1) E ::= E + T (2) E ::= T (3) T ::= T * F (4) T ::= F (5) F ::= (E) (6) F ::= id
Tabela SLR • Representação eficiente do autômato de pilha que reconhece a linguagem. Onde: • O topo da pilha contém sempre o estado atual do autômato • Dado o estado atual e o token de entrada, a tabela indica a ação a ser executada • No caso da ação ser uma redução a tabela Transição indica o próximo estado a ser assumido pelo autômato • As entradas em branco correspondem a situações de erro