Gramáticas LR(0)
Tal como antes para as gramáticas LL, o interesse aqui está nas gramáticas LR(1) porém o caso LR(0) é importante como introdução para o processo de construção do analisador sintático.
Contextos LR(0)
Problema. Quando é que uma produção pode ser aplicada numa derivação "válida"?
Isto é, será possível determinar quando a aplicação de uma produção numa derivação contribui para uma palavra gerada pela gramática das outras aplicações, em que o resultado não é gerado pela gramática?
No caso das derivações direitas
Contexto-LR(0). Prefixo Viável. Seja uma GIC com terminador e em que não é recursivo.
A palavra é um Contexto-LR(0) da produção se existir uma derivação direita
- Isto é, há uma redução que começa por .
Um prefixo viável é um prefixo de um contexto-LR(0).
Dada uma produção, o conjunto dos seus contextos-LR(0) é uma linguagem regular.
N.B. Na definição acima é importante ter presente que se tratam de derivações direitas. Portanto, quando é aplicada em :
- O sufixo é formado só por terminais.
- O prefixo é o "contexto" para a aplicação da regra .
Intuitivamente pretende-se que os Contextos-LR(0) tenham informação suficiente para determinar que produção aplicar em cada passo de uma derivação. Isto é, se os Contextos-LR(0) forem disjuntos tem-se um método determinista para fazer derivações direitas.
Por exemplo, dada a GIC
As derivações direitas têm uma das seguintes formas
pelo que os Contextos-LR(0) das produções são:
Neste caso, é um contexto-LR(0) tanto de como de (com em ambos os casos).
O que é que isto significa? Quando se procura descobrir como poderá ter sido derivada à esquerda, há duas possibilidades:
- Ou .
- Ou .
Isto é, em não "há informação suficiente" para saber qual foi a última produção aplicada, ou .
Considerando agora a GIC
os contextos-LR(0) são:
Os contextos-LR(0) podem ser descobertos explorando a árvore das derivações direitas:
Árvore das Derivações Direitas |
---|
Os contextos-LR(0) estão sublinhados. |
Agora, para encontrar uma derivação de :
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, a produção aplicada foi , à palavra .
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, foi aplicada a .
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, foi aplicada a .
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, foi aplicada a .
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, foi aplicada a .
- O maior prefixo de que é um contexto-LR(0) é , de .
- Portanto, foi aplicada e a derivação está encontrada:
Note-se que:
- Foi encontrada uma derivação direita (rightmost derivation).
- A "pilha" é lida da esquerda-para-a-direita (left-to-right).
- O processo encontra a derivação em reverso (in reverse).
A derivação anterior pode ser organizada numa tabela com quatro tipos de ações:
- Reduzir quando a pilha tem um Contexto-LR(0) de .
- Transferir o primeiro símbolo da palavra para a pilha, quando a pilha tem um prefixo viável que não é um Contexto-LR(0).
- Aceitar quando a pilha é e a palavra .
- Rejeitar quando a pilha não é um prefixo viável.
O resultado é:
A derivação de pode ser recuperada da coluna "ação", lida em reverso.
Para procurar uma derivação de , que não é gerada pela gramática:
Itens LR(0)
Os exemplos acima ilustram a utilidade dos contextos-LR(0) para a análise sintática. Embora não sejam facilmente encontrados a partir da definição, o conjunto dos Contextos-LR(0) de uma produção é uma linguagem regular que pode ser definida por um certo autómato finito determinista.
Para definir esse AFD é preciso começar pelos seus estados:
Item LR(0). Seja uma GIC.
- Os itens LR(0) de são as "produções" que se obtêm de acrescentado um em todas as posições possíveis:
- Se então é um item LR(0) de .
- Se então é um item LR(0) de .
- Um item completo é um item em que o está o mais à direita possível.
- Um item é válido para o prefixo viável se é um contexto LR(0); Isto é, se é candidato a reduzir.
Usando a gramática do exemplo anterior, os seus itens LR(0) são com os itens completos assinalados assim: .
Fecho. Seja um conjunto de itens. O fecho de , denotado define-se recursivamente por:
- base .
- passo Se com então, para cada produção , também .
Por exemplo, usando a gramática anterior,
Autómato Finito dos Itens LR(0) Válidos
Os fechos dos itens são usado para construir um autómato finito que determina a ação nas tabelas acima.
Autómato dos Itens Válidos (AIV). Seja uma GIC. O autómato dos itens válidos, que reconhece os prefixos viáveis de é o AFD em que:
- estado inicial .
- transição Para cada e
Continuando com a gramática anterior, o seu autómato dos itens válidos tem os seguintes estados e transição:
Note-se que há mais um estado, , que não se representa e que recebe todas as transições que não estão especificadas.
Este AFD pode ser representado numa tabela mais convencional (todas as transições não assinaladas vão para ):
Porém, pode ser mais simples calcular graficamente os estados e a transição do autómato dos itens válidos.
Diagrama do Autómato dos Itens Válidos |
---|
Em cada estado os itens completos são assinalados com . O estado não está representado. |
Neste diagrama cada estado tem dois ou três "andares". De cima para baixo:
- O "nome" do estado, um inteiro
- A "raiz" dos itens, que resultam da transição.
- Restantes itens, para se obter o fecho da "raiz".
Analisador Sintático LR(0)
O Autómato dos itens válidos serve para determinar a ação na derivação. Em cada passo o AIV processa a pilha e a ação é:
- Aceitar se a pilha tem apenas .
- Reduzir se terminar num estado com um item completo. Nesse caso a redução é da produção do item completo.
- Transferir o próximo símbolo da palavra se terminar num estado sem itens completos.
- Rejeitar se não puder processar a palavra (isto é, vai parar a ).
Recuperando o exemplo anterior, o processamento de é:
enquanto que para , que não é gerada pela gramática:
Tabela de Análise Sintática LR(0)
A tabela de análise sintática (TAS) estende a transição do AIV com as ações associadas aos respetivos estados. Tem uma linha para cada estado e uma coluna para cada símbolo de . Além disso tem também uma coluna, , que indica que ação corresponde a um prefixo que termine nesse estado.
Para a gramática que tem vindo a servir de exemplo, a TAS é:
Nesta tabela:
- O estado , a que corresponde a ação "rejeitar", não é representado e portanto todos os estados mostrados na TAS são finais.
- As transições não representadas levam ao estado .
- Por convenção, o estado é o inicial.
- Nos estados com itens completos do símbolo inicial da GIC (no exemplo acima, o estado ) a ação é "aceitar", , em vez de ser "reduzir", .
Dado que a coluna determina se no analisador sintático é feita uma transferência ou uma redução (e nesse caso, de que produção), se essa informação for ambígua não é possível fazer análise sintática determinista LR(0).
Portanto é necessário que cada estado defina exatamente uma ação.
Como as reduções são feitas em estados com itens completos (por exemplo, os estados acima) e as transferência são feitas em estados de que sai alguma "aresta com um terminal" (os estados acima), as ambiguidades (conflitos) possíveis são:
- Conflito Redução/Redução LR(0): Se num estado estiverem dois itens completos então esse estado define duas reduções possíveis.
- Conflito Redução/Transferência LR(0): Se num estado com um item completo sair uma aresta "com um terminal" então esse estado define uma redução e uma transferência.
Estas condições permitem caraterizar as gramáticas LR(0):
Teorema das Gramáticas LR(0). Uma GIC é LR(0) se e só se o seu AIV não tem conflitos redução/redução LR(0) nem redução/transferência LR(0).
Autómato de Pilha LR(0)
Pretende-se não só fazer análise sintática determinista mas também que esta seja livre de passos manuais (human in the middle). O AIV proporciona dois passos desse processo:
- Determina se a GIC dada é LR(0).
- Define a ação em cada passo do analisador sintático.
Falta definir formalmente o processamento do próprio analisador sintático, o que será feito com um autómato de pilha.
Autómato de Pilha Reconhecedor LR(0). Seja uma GIC LR(0) e o seu AIV. O Autómato de Pilha Reconhecedor (APR) de , que reconhece a linguagem gerada por , é em que a transição, , é definida pelos seguintes elementos:
- iniciar: .
- transferir: se com .
- reduzir: Para cada estado com um item completo com e , quando no AIV existe a computação
- aceitar: Para cada estado com um item completo do símbolo inicial da GIC, quando no AIV existe a computação
Intuitivamente a pilha do APR intercala estados do AIC com símbolos de de forma a descrever "fragmentos de computação do AIV". Por exemplo, a computação de enquanto que de é:
Estes exemplos ilustram como o topo da pilha determina a transição do APR: O primeiro símbolo identifica o estado do AIV e este define ou uma transferência ou uma redução (incluindo o caso particular da aceitação).
- No caso de reduzir (ou aceitar) são considerados mais símbolos da pilha, de forma a "capturar" todo o lado direito da produção, que é substituído pela respetiva variável.
- No caso da operação ser uma transferência o terminal da palavra é comparado com a parte correspondente na pilha e, se coincidirem, ambos são "consumidos".
Neste processo é mantido o registo dos estados do AIV percorridos pelas respetivas computações.
Alternativamente as transições do APR podem ser definidas pela seguinte tabela:
Por exemplo para a GIC anterior, com a TAS (calculada acima, repetida aqui)
as transições do APR são:
-
iniciar é a única transição .
-
transferir Estas transições são :
-
reduzir Estas transições são :
-
aceitar Estas transições são :
O diagrama do APR é:
Diagrama do APR |
---|
As transições para transferir estão no ciclo superior, aceitar no direito e reduzir no inferior. |
O APR replica os passos (informais) do analisador sintático. A computação de pelo APR é:
e pára porque atinge uma configuração sem transições definidas (o topo é e o símbolo da entrada é ). Por outro lado a computação de é: e termina numa configuração com um estado final e com a pilha e a entrada ambas vazias. Neste caso, lendo em reverso as produções obtém-se a derivação direita da palavra dada:
Este exemplo ilustra o tratamento "automático" da análise sintática das gramáticas LR(k).
- Dada uma GIC, a construção do AIV e a respetiva TAS permite determinar se a GIC é, ou não LR(0).
- Se o for, a TAS é usada para construir o APR que reconhece a linguagem gerada pela GIC.
- Além disso, o processamento de uma palavra aceite pelo APR permite encontrar a derivação direita dessa palavra.
- Em todo este processo, desde a construção do AIV e da TAS, do APR e a computação de palavras os algoritmos são eficientes (isto é, o número de passos é polinomial no tamanho do input.)
Resta saber se as gramáticas LR(0) são adequadas.
Considerando a seguinte gramática de expressões algébricas simplificadas:
Na construção do AIV desta GIC o estado inicial tem os seguintes itens:
Deste estado sai uma aresta que leva a um novo estado com os seguintes itens onde existe um conflito redução/transferência (a redução de com a transferência de ).
Este exemplo mostra que as gramáticas LR(0) são demasiado simples para definir expressões algébricas e portanto não são adequadas para definir as linguagens de programação.
A solução para este problema consiste em "adaptar" a ideia das gramática LL(1), considerando os símbolos de avanço, ao processo das gramáticas LR(0).
Conclusão
- O que conseguimos resolver
- O que falta resolver.