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
Árvore das Derivações Direitas
Os contextos-LR(0) estão sublinhados.

Agora, para encontrar uma derivação de :

  1. O maior prefixo de que é um contexto-LR(0) é , de .
  2. Portanto, a produção aplicada foi , à palavra .
  3. O maior prefixo de que é um contexto-LR(0) é , de .
  4. Portanto, foi aplicada a .
  5. O maior prefixo de que é um contexto-LR(0) é , de .
  6. Portanto, foi aplicada a .
  7. O maior prefixo de que é um contexto-LR(0) é , de .
  8. Portanto, foi aplicada a .
  9. O maior prefixo de que é um contexto-LR(0) é , de .
  10. Portanto, foi aplicada a .
  11. O maior prefixo de que é um contexto-LR(0) é , de .
  12. Portanto, foi aplicada e a derivação está encontrada:

Note-se que:

  1. Foi encontrada uma derivação direita (rightmost derivation).
  2. A "pilha" é lida da esquerda-para-a-direita (left-to-right).
  3. 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
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:

  1. O "nome" do estado, um inteiro
  2. A "raiz" dos itens, que resultam da transição.
  3. 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
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.