Formas Normais
Gramáticas Adaptadas para Análise Sintática por Pesquisa Geral.
Introdução
A análise sintática é representada como um problema de pesquisa em grafos orientados que, em geral, pode ser resolvido por quatro estratégias diferentes: (ascendente ou descendente) x (largura ou profundidade). Nenhuma destas estratégias gerais é eficiente.
Nesta secção é apresentado um processo (a normalização de uma gramática) que, numa sequência de passos, transforma numa gramática noutra, equivalente, e adaptada (tanto quanto possível) aos algoritmos de pesquisa: uma forma normal.
Conteúdo
O bom funcionamento (mínimo número de passos) da pesquisa no grafo de uma GIC depende da própria gramática.
Problemas como:
- Pesquisas infinitas quando a palavra não é derivada pela gramática.
- Exploração repetida de ramos quando a gramática é ambígua.
reduzem significativamente o desempenho da pesquisa.
Em certos caso é possível transformar a gramática dada noutra equivalente mas que “favorece” a pesquisa, reduzindo o número de ramificações, desambiguando, e/ou detetando “cedo” se a palavra não é gerada pela gramática.
Esta secção descreve um processo algorítmico de transformação de gramáticas. No fim a gramática que se obtém proporciona pesquisas gerais mais eficientes.
Todo este processo vai ser ilustrado com um exemplo de uma GIC (especialmente desenhada) para ilustrar a aplicação de cada passo:
$$ \begin{aligned} G^0 =& \del{\set{L, M, N, O}, \set{a, b, c, d}, \ldots, L} \cr &\begin{aligned} L &\to Mb \alt aLb \alt \nil \cr M &\to Lb \alt MLN \alt \nil \cr N &\to NaN \alt NbO \cr O &\to cO \alt \nil \end{aligned} \end{aligned} $$
1. Símbolo Inicial Não Recursivo
A recursividade é a “vantagem” das GIC sobre as ER, mas é também, uma fonte de dificuldades. Embora não seja desejável, nem sequer possível, remover completamente a recursividade das produções de uma GIC esta pode ser, em certos casos, controlada.
Símbolo Inicial Não Recursivo. Seja $G = \del{V, \Sigma, P, S}$ uma GIC. Existe uma GIC $G’ = \del{V’, \Sigma, P’, S’}$ equivalente a $G$ e onde o símbolo inicial não é recursivo.
Porque:
- Se o símbolo inicial de $G$ não for recursivo então $G’ = G$.
- Se o símbolo inicial de $G$ for recursivo então, fazendo $S’$ uma variável nova:
$$ G’ = \del{V \cup \set{S’}, \Sigma, P \cup \set{S’ \to S}, S’}. $$
Exemplo. Em $G^0$ o símbolo inicial é recursivo porque $L \to aLb$ é uma produção de $G^0$. Portanto esta gramática é transformada em
$$ \begin{aligned} G^1 =& \del{\set{\alert{L’}, L, M, N, O}, \set{a, b, c, d}, \ldots, \alert{L’}} \cr & \begin{aligned} \alert{L’} &\alert{\to L} \cr L &\to Mb \alt aLb \alt \nil \cr M &\to Lb \alt MLN \alt \nil \cr N &\to NaN \alt NbO \cr O &\to cO \alt \nil \end{aligned} \end{aligned} $$
Em $G^1$ o símbolo inicial já não é recursivo. Além disso, $L\at{G^0} = L\at{G^1}$.
2. Eliminação de Produções Vazias
Antes de tratar do segundo passo na normalização de uma gramática é necessário definir alguns conceitos e técnicas.
Quase todas as produções “aumentam” o tamanho da palavra. Por exemplo $A \to bB$ faz com que $aAa$ passe para $abBa$, isto é de comprimento três para quatro. E esta é uma propriedade desejável pois permite cortar “filhos demasiado grandes” durante a pesquisa.
Há duas exceções de nota: as produções vazias $A \to \nil$ e as produções unitárias $A \to B$. Formalmente:
Produções Vazias. Produções Unitárias. Gramática Contraível. Seja $G = \del{V, \Sigma, P, S}$ uma GIC.
- Uma produção vazia tem a forma $A \to \nil$.
- Uma produção unitária tem a forma $A \to B$.
- O conjunto dos geradores da palavra vazia é $$\Lambda = \set{A \in V \st A \ider \nil}.$$
- Numa gramática não contraível não existem geradores da palavra vazia. Isto é $\Lambda = \empty$.
- Numa gramática essencialmente não contraível o único gerador de vazio é o símbolo inicial. Isto é $\Lambda = \set{S}$.
O interesse das gramáticas essencialmente não contraíveis é que:
Numa gramática essencialmente não contraível os passos intermédios de uma derivação só podem diminuir de tamanho por aplicação de $S \to \nil$.
Não é possível, nem desejável, descartar completamente os geradores de vazio. Por exemplo, se $\nil \in L\at{G}$ então em qualquer gramática equivalente a $G$ tem de existir pelo menos uma produção vazia. Neste caso, o melhor que se pode fazer é “concentrar” todas as produções vazias em $S \to \nil$.
A transformação de uma gramática noutra equivalente envolve a introdução de novas produções e a substituição de uma produção por outras, de forma a manter a linguagem gerada.
Introdução de Produções. Substituição de Produções. Seja $G = \del{V, \Sigma, P, S}$ uma CIG.
- Se $A \ider u$ então $G’ = \del{V, \Sigma, P \cup \set{A \to u}, S}$ é equivalente a $G$.
- Se $A \to uBv \in P$ e $B \to w_1 \alt w_2 \alt \cdots \alt w_n$ são todas as produções de $B$ então a gramática $G’ = \del{V, \Sigma, P’, S}$ em que $$P’ = P \setminus \set{A \to uBv} \cup \set {A \to uw_1v \alt uw_2v \alt \cdots \alt uw_n v}$$ é equivalente a $G$.
Intuitivamente, estas duas regras permitem “saltar” passos intermédios numa derivação.
O primeiro passo para retirar as produções que reduzem o comprimento das palavras trata das produções vazias.
Eliminação de Produções Vazias. Sejam $G = \del{V, \Sigma, P, S}$ uma GIC em que o símbolo inicial não é recursivo. e $G’ = \del{V, \Sigma, P’, S}$ em que as produções são, em conjunto:
- As produções de $P$ que não são produções da palavra vazia.
- A produção $S \to \nil$ se $\nil \in L\at{G}$.
- Todas as produções que se obtêm eliminando um ou mais símbolos de $\Lambda$ do corpo $w$ de cada produção $A \to w$, desde que o resultado, $w’$, tenha pelo menos um símbolo.
Então $G’$ é equivalente a $G$ e essencialmente não contraível.
Exemplo. Na gramática $G^1$ o símbolo inicial não é recursivo. Além disso, os seus geradores da palavra vazia são $$ \Lambda = \set{L’, L, M, O}. $$
A gramática $G^2$, que resulta de $G^1$ por eliminação das produções vazias é:
$$ \begin{aligned} G^2 =& \del{\set{L’, L, M, N, O}, \set{a, b, c, d}, \ldots, L’} \cr & \begin{aligned} L’ & \to L \alt \alert{\nil} \cr L &\to Mb \alt \alert{b} \alt aLb \alt \alert{ab} \alt \xcancel{\nil} \cr M &\to Lb \alt \alert{b} \alt MLN \alt \alert{LN} \alt \alert{MN} \alt \alert{N} \alt \xcancel{\nil} \cr N &\to NaN \alt NbO \alt \alert{Nb}\cr O &\to cO \alt \alert{c} \alt \xcancel{\nil} \end{aligned} \end{aligned} $$
Note-se que:
- Foram retiradas as produções $L \to \nil$ e $M \to \nil$.
- Como $\nil \in L\at{G^1}$, foi acrescentada $L’ \to \nil$.
- De cada produção em cujo corpo ocorrem geradores da palavra vazia foram acrescentadas variantes sem esses símbolos. Por exemplo:
- Em $L \to Mb$ no corpo, $Mb$, ocorre $M \in \Lambda$. Portanto é acrescentada a produção $L \to b$, que resulta de $Mb$ removendo $M$.
- Mais interessante, no corpo da produção $M \to MLN$ tanto $M$ como $L$ são geradores da palavra vazia. As combinações possíveis sem algum desses elementos são $LN, MN, N$.
Sem os cálculos auxiliares:
$$ \begin{aligned} G^2 =& \del{\set{L’, L, M, N, O}, \set{a, b, c, d}, \ldots, L’} \cr & \begin{aligned} L’ & \to L \alt \nil \cr L &\to Mb \alt b \alt aLb \alt ab \cr M &\to Lb \alt b \alt MLN \alt LN \alt MN \alt N \cr N &\to NaN \alt NbO \alt Nb\cr O &\to cO \alt c \end{aligned} \end{aligned} $$
3. Eliminação de Produções Unitárias
As produções unitárias substituem uma variável por outra variável e podem ser eliminadas usando cadeias.
Cadeia. Seja $G = \del{V, \Sigma, P, S}$ uma GIC essencialmente não contraível.
Para cada $A \in V$, a cadeia de $A$ é o conjunto $\cadeia\at{A} = \set{B \in V \st A \ider B}$.
Qualquer gramática $G’ = \del{V, \Sigma, \ldots, S}$ cujas produções $A \to w$ são tais que:
- $w \not\in V$.
- Existe $B \in \cadeia\at{A}$ tal que $B \to w \in P$.
é equivalente a $G$.
Este enunciado pode ser difícil de interpretar. O primeiro ponto afirma que na gramática $G’$ não existem produções unitárias. O segundo ponto permite substituir a produção unitária $A \to B$ por $A \to w$ para cada $B \to w$.
Exemplo. As cadeias de $G^2$ são
$$ \begin{array}{l|l} A \in V & \cadeia\at{A} \cr \hline L’ & L’, L\cr L & L \cr M & M, N\cr N & N \cr O & O \end{array} $$
e as produções unitária são $L’ \to L$ e $M \to N$. As respetivas substituições são:
- de $L’ \to \alert{L}$, dado que $L \to Mb \alt b \alt aLb \alt ab$: $L’ \to \alert{Mb \alt b \alt aLb \alt ab}$.
- de $M \to \alert{N}$, dado que $N \to NaN \alt NbO \alt Nb$: $M \to \alert{NaN \alt NbO \alt Nb}$.
A gramática que se obtém, equivalente a $G^2$, é:
$$ \begin{aligned} G^3 =& \del{\set{L’, L, M, N, O}, \set{a, b, c, d}, \ldots, L’} \cr & \begin{aligned} L’ & \to Mb \alt b \alt aLb \alt ab \alt \nil \cr L &\to Mb \alt b \alt aLb \alt ab \cr M &\to Lb \alt b \alt MLN \alt LN \alt MN \alt NaN \alt NbO \alt Nb \cr N &\to NaN \alt NbO \alt Nb\cr O &\to cO \alt c \end{aligned} \end{aligned} $$
4. Eliminação de Símbolos Inúteis
A definição de linguagem gerada pela GIC $G = \del{V, \Sigma, P, S}$ é $$ L\at{G} = \set{p \in \cl{\Sigma} \st S \ider p} $$
Uma inspeção rápida a $N \to NaN \alt NbO \alt Nb$ mostra que a recursividade desta variável impede-a de gerar palavras só de terminais. Portanto, em termos de linguagem gerada, este é um símbolo que pode ser retirado da gramática. Também podem ser retirados os símbolos que não podem ser atingidos a partir do símbolo inicial.
Símbolo Útil. Símbolo Acessível. Gramática Limpa. Seja $G = \del{V, \Sigma, P, S}$ uma GIC.
Um símbolo $x \in V \cup \Sigma$ é:
- Útil se existe uma derivação $S \ider u\alert{x}v \ider p \in \cl{\Sigma}$.
- Inútil se não é útil.
- Acessível se existe uma derivação $S \ider u\alert{x}v$.
- Inacessível se não é acessível.
Uma variável $A \in V$ é:
- Produtiva se $A \ider p \in \cl{\Sigma}$.
- Improdutiva se não é produtiva.
Uma variável é útil se e só se for acessível e produtiva.
Uma gramática sem símbolos inúteis e inacessíveis diz-se limpa (ou reduzida).
Para limpar uma gramática de símbolos inúteis e inacessíveis:
- Eliminar as variáveis improdutivas:
- Encontrar as variáveis produtivas (por exemplo, derivando delas uma palavra só de terminais).
- Remover as produções onde ocorrem as variáveis improdutivas.
- Eliminar as variáveis inacessíveis:
- Encontrar os símbolos acessíveis (por exemplo, fazendo uma derivação a partir do símbolo inicial onde o símbolo ocorra).
- Remover as produções das variáveis inacessíveis.
Exemplo. Como foi observado acima, $N$ é inútil. Portanto as produções onde $N$ ocorre podem ser removidas. As novas produções são:
$$ \begin{aligned} L’ & \to Mb \alt b \alt aLb \alt ab \alt \nil \cr L &\to Mb \alt b \alt aLb \alt ab \cr M &\to Lb \alt b \alt \xcancel{MLN} \alt \xcancel{LN} \alt \xcancel{MN} \alt \xcancel{NaN} \alt \xcancel{NbO} \alt \xcancel{Nb} \cr \xcancel{N} &\xcancel{\to NaN \alt NbO \alt Nb}\cr O &\to cO \alt c \end{aligned} $$
Como resultado destas remoções $O$ deixou de ser acessível: Nenhuma derivação $L’ \ider \cdots$ “contém” $O$. Portanto, também as produções de $O$ podem ser removidas e o resultado é:
$$ \begin{aligned} G^4 =& \del{\set{L’, L, M}, \set{a, b}, \ldots, L’} \cr & \begin{aligned} L’ & \to Mb \alt b \alt aLb \alt ab \alt \nil \cr L &\to Mb \alt b \alt aLb \alt ab \cr M &\to Lb \alt b \end{aligned} \end{aligned} $$
Note-se que, removendo as produções de $O$, o terminal $c$ ficou inacessível, pelo que foi retirado de $\Sigma$, tal como $d$, que nunca constou nas produções.
Finalmente, é fácil de verificar que $L’, L$ e $M$ são produtivos e acessíveis. Também $a$ e $b$ são acessíveis.
Numa gramática limpa todos os símbolos “contam”. Além disso, e de se ter tratado das produções vazias e unitárias, pouco se fez para melhorar o processo de pesquisa no grafo da gramática. Para esse efeito é preciso “controlar a forma” das produções.
5. Forma Normal de Chomsky
O corpo das produções de uma CIG é uma palavra de $\clpar{V \cup \Sigma}$, pelo que pode ter vários terminais e variáveis misturados. Aqui pretende-se “arrumar” as formas possíveis que as produções podem ter.
Forma Normal de Chomsky (FNC). Uma GIC $G = \del{V, \Sigma, P, S}$ está na forma normal de Chomsky se cada uma das suas produções tem uma das formas seguintes:
- $A \to BC$ com $B, C \in V \setminus \set{S}$.
- $A \to a$ com $a \in \Sigma$.
- $S \to \nil$.
A forma normal de Chomsky é bastante fácil de obter, substituindo terminais “inconvenientes” por variáveis e usando novas variáveis para “agrupar” cadeias três ou mais símbolos.
Exemplo. Na gramática
$$ \begin{aligned} G^4 =& \del{\set{L’, L, M}, \set{a, b}, \ldots, L’} \cr & \begin{aligned} L’ & \to Mb \alt b \alt aLb \alt ab \alt \nil \cr L &\to Mb \alt b \alt aLb \alt ab \cr M &\to Lb \alt b \end{aligned} \end{aligned} $$
acrescentam-se $A \to a$ e $B \to b$ para retirar estes terminais das outras produções: $$ \begin{aligned} L’ & \to M\alert{B} \alt b \alt \alert{A}L\alert{B} \alt \alert{AB} \alt \nil \cr L &\to M\alert{B} \alt b \alt \alert{A}L\alert{B} \alt \alert{AB} \cr M &\to L\alert{B} \alt b \cr \alert{A} &\alert{\to a} \cr \alert{B} &\alert{\to b} \end{aligned} $$
Para se obter a FNC ainda falta tratar das produções $L’ \to ALB$ e $L \to ALB$. Para tal acrescenta-se a produção $X \to LB$ e substitui-se $L’, L \to AX$. O resultado final é:
$$ \begin{aligned} G^5 =& \del{\set{L’, L, M, \alert{A, B, X}}, \set{a, b}, \ldots, L’} \cr & \begin{aligned} L’ & \to MB \alt b \alt A\alert{X} \alt AB \alt \nil \cr L &\to MB \alt b \alt A\alert{X} \alt AB \cr M &\to LB \alt b \cr A &\to a \cr B &\to b \cr \alert{X} &\alert{\to LB} \end{aligned} \end{aligned} $$
6. Forma Normal de Greibach
Uma forma de reduzir o número de ramificações durante a pesquisa consiste em “olhar para o próximo símbolo” da palavra e escolher apenas produções que colocam esse símbolo no início.
Por exemplo, dadas a GIC $S \to aA \alt bB; A \to aA \alt \nil; B \to bB \alt \nil$ e a palavra $aa$, é simples de ver que a única derivação possível é $S \nder{S \to aA} aA \nder{A \to aA} aaA \nder{A \to \nil} aa$ porque em cada passo há só um candidato possível.
Neste caso, em cada passo da pesquisa é muito simples descartar todos os candidatos que não começam pelo terminal correto. Esta observação motiva a seguinte definição:
Forma Normal de Greibach (FNG). Uma GIC $G = \del{V, \Sigma, P, S}$ está na forma normal de Greibach se cada uma das suas produções tem uma das formas seguintes:
- $A \to aB_1 B_2 \cdots B_n$ com $a \in \Sigma, B_i \in V \setminus \set{S}, i \in 1\ldots n$.
- $A \to a$ com $a \in \Sigma$.
- $S \to \nil$.
Note-se que, em relação à forma normal de Chomsky, a única diferença na primeira forma das produções: $A \to aB_1 B_2 \cdots B_n$ em vez de $A \to BC$.
As gramáticas na FNG são úteis para:
- evitar recursões infinitas nos algoritmos de pesquisa.
- guiar (com o primeiro símbolo) a escolha das regras a expandir.
- produzir AP equivalente à gramática dada.
Construção de uma GIC na FNG
Ao contrário da construção da FNC, que é simples de obter, a construção de uma GIC na FNG requer alguma orientação.
Construção da FNG. Dada uma GIC $G = \del{V, \Sigma, P, S}$ na FNC:
- Ordenar as variáveis. Define-se uma ordem total nas variáveis de $G$ com uma única condição: $S$ é o primeiro elemento.
- Passo Descendente. Segue-se essa ordem para transformar todas as produções de modo que, se $\alert{A \to Bp}, B\in V, p \in \clpar{V\cup \Sigma}$ então $\alert{A \lt B}$.
- Passo Ascendente. Segue-se essa ordem invertida para substituir cada produção $\alert{A \to Bp}$ por $\alert{A \to qp}$ para cada produção $\alert{B \to q}, q \in \clpar{V\cup \Sigma}$.
Exemplo. Continuando com $G^5$, que está na FNC:
$$ \begin{aligned} L’ & \to MB \alt b \alt AX \alt AB \alt \nil \cr L &\to MB \alt b \alt AX \alt AB \cr M &\to LB \alt b \cr A &\to a \cr B &\to b \cr X &\to LB \end{aligned} $$
Ordenar as variáveis. A única condição é “$L’$ é o primeiro elemento”. Por exemplo $$L’ \lt X \lt B \lt A \lt L \lt M.$$
Passo Descendente. Ordenar as produções seguindo essa ordem.
$$ \begin{aligned} L’ & \to MB \alt b \alt AX \alt AB \alt \nil & L \lt M, A\cr X &\to LB & X < L\cr B &\to b \cr A &\to a \cr L &\to MB \alt b \alt \alert{A}X \alt \alert{A}B & L \lt M; \alert{L \not\lt A}\cr M &\to \alert{L}B \alt b & \alert{M \not\lt L} \end{aligned} $$
As produções de $L$ e de $M$ não verificam a condição do passo descendente. No caso de $L$ esse problema resolve-se facilmente substituindo o $A$ no início da produções de $L$ por $a$ (porque $A \to a$).
$$ \begin{aligned} L’ & \to MB \alt b \alt AX \alt AB \alt \nil & L \lt M, A\cr X &\to LB & X < L\cr B &\to b \cr A &\to a \cr L &\to MB \alt b \alt \underline{a}X \alt \underline{a}B & L \lt M\cr M &\to \alert{L}B \alt b & \alert{M \not\lt L} \end{aligned} $$
Resta o problema com a produção $ M \to \alert{L}B$. Segue-se também o método anterior:
$$ \begin{aligned} L’ & \to MB \alt b \alt AX \alt AB \alt \nil & L \lt M, A\cr X &\to LB & X < L\cr B &\to b \cr A &\to a \cr L &\to MB \alt b \alt aX \alt aB & L \lt M\cr M &\to \underline{\alert{M}B}B \alt \underline{b}B \alt \underline{aX}B \alt \underline{aB}B \alt b & \alert{M \not\lt M} \end{aligned} $$
Só que desta vez o problema não ficou resolvido e resultou numa Recursão Direta à Esquerda da variável $M$.
Eliminação da Recursividade Direta à Esquerda. Se
$$A \to A x_1 \alt \cdots \alt A x_n \alt y_1\alt \cdots \alt y_m$$
são as produções de $A$, organizadas de forma que:
- Cada $A x_1, \ldots, A x_n$ começa por $A$ e
- Nenhuma $y_1, \ldots, y_m$ começa por $A$.
Então obtém-se uma gramática equivalente:
- Acrescentando uma nova variável, por exemplo $Z$.
- Substituindo as produções de $A$ por
$$ \begin{aligned} A &\to y_1 Z \alt \cdots \alt y_m Z \alt y_1 \alt \cdots \alt y_m \cr Z &\to x_1 Z \alt \cdots \alt x_n Z \alt x_1 \alt \cdots \alt x_n \end{aligned} $$
Uma mnemónica para este processo é a seguinte:
Substituir $\alert{A \to A x \alt y}$ por $\alert{A \to yZ \alt y}$ e $\alert{Z \to xZ \alt x}$.
Intuitivamente percebe-se porque esta substituição é válida observando que a linguagem gerada por $A \to Ax \alt y$ é: $$ y, yx, yxx, yxxx, \ldots, yx^n, \ldots $$
Agora, outra forma de descrever esta linguagem é “Um $y$ seguido de zero ou mais $x$.” Neste caso:
- $Z \to xZ \alt x$ gera um ou mais $x$.
- $A \to yZ \alt y$ gera um $y$ seguido de zero ou um $Z$.
A eliminação da RDE de
$$ \overbrace{M}^{A} \to \overbrace{M}^{A}\ \overbrace{BB}^{x_1} \alt \overbrace{bB}^{y_1} \alt \overbrace{aXB}^{y_2} \alt \overbrace{aBB}^{y_3} \alt \overbrace{b}^{y_4} $$
define as produções
$$ \begin{aligned} M &\to bBZ \alt aXBZ \alt aBBZ \alt bZ &&\alt bB \alt aXB \alt aBB \alt b \cr Z &\to BBZ &&\alt BB \end{aligned} $$
pelo que a ordem inicialmente definida para as variáveis tem de ser estendida de forma que $Z < B$. Por exemplo:
$$L’ \lt X \lt \alert{Z} \lt B \lt A \lt L \lt M.$$
A nova gramática equivalente fica:
$$ \begin{aligned} L’ & \to MB \alt b \alt AX \alt AB \alt \nil & L \lt M, A\cr X &\to LB & X < L\cr \alert{Z} &\alert{\to BBZ \alt BB} & Z \lt B\cr B &\to b \cr A &\to a \cr L &\to MB \alt b \alt aX \alt aB & L \lt M\cr \alert{M} &\alert{\to bBZ \alt aXBZ \alt aBBZ \alt bZ \alt bB \alt aXB \alt aBB \alt b} \end{aligned} $$
No fim do passo descendente cada produção ou é vazia ou começa por um terminal ou começa por uma variável “mais abaixo”.
Passo Ascendente. Substituir, “de baixo para cima” a primeira variável de cada produção:
$$ \begin{aligned} L’ & \to \underbrace{bBZB \alt aXBZB \alt aBBZB \alt bZB \alt bBB \alt aXBB \alt aBBB \alt bB}{MB} \alt b \alt \underbrace{aX}{AX} \alt \underbrace{aB}{AB} \alt \nil \cr X &\to \underbrace{bBZBB \alt aXBZBB \alt aBBZBB \alt bZBB \alt bBBB \alt aXBBB \alt aBBBB \alt bBB \alt bB \alt aXB \alt aBB}{LB}\cr Z &\to \underbrace{bBZ}{BBZ} \alt \underbrace{bB}{BB}\cr B &\to b \cr A &\to a \cr L &\to \underbrace{bBZB \alt aXBZB \alt aBBZB \alt bZB \alt bBB \alt aXBB \alt aBBB \alt bB}_{MB} \alt b \alt aX \alt aB\cr M &\to bBZ \alt aXBZ \alt aBBZ \alt bZ \alt bB \alt aXB \alt aBB \alt b \end{aligned} $$
Sem anotações, a GIC final, $$ \begin{aligned} G^6 =& \del{\set{L’, L, M, A, B, X, Z}, \set{a, b}, \ldots, L’} \cr & \begin{aligned} L’ & \to bBZB \alt aXBZB \alt aBBZB \alt bZB \alt bBB \alt aXBB \alt aBBB \alt bB \alt b \alt aX \alt aB \alt \nil \cr X &\to bBZBB \alt aXBZBB \alt aBBZBB \alt bZBB \alt bBBB \alt aXBBB \alt aBBBB \alt bBB \alt bB \alt aXB \alt aBB\cr Z &\to bBZ \alt bB\cr B &\to b \cr A &\to a \cr L &\to bBZB \alt aXBZB \alt aBBZB \alt bZB \alt bBB \alt aXBB \alt aBBB \alt bB \alt b \alt aX \alt aB\cr M &\to bBZ \alt aXBZ \alt aBBZ \alt bZ \alt bB \alt aXB \alt aBB \alt b \end{aligned} \end{aligned} $$ está na Forma Normal de Greibach e é equivalente à GIC inicial $$ \begin{aligned} G^0 =& \del{\set{L, M, N, O}, \set{a, b, c, d}, \ldots, L} \cr &\begin{aligned} L &\to Mb \alt aLb \alt \nil \cr M &\to Lb \alt MLN \alt \nil \cr N &\to NaN \alt NbO \cr O &\to cO \alt \nil \end{aligned} \end{aligned} $$
Embora $G^6$ tenha muito mais produções do que $G^0$ e ainda por cima aparentemente redundantes (mas não o são) o facto de $G^6$ estar na FNG torna-a mais adequada e eficiente do que $G^0$ em termos de pesquisa. Por exemplo:
- A derivação de uma palavra com $n$ símbolos tem, no máximo, $n$ passos porque cada produção “produz” um terminal.
- O primeiro símbolo “filtra” as possíveis produções aplicáveis.
- A pesquisa de uma palavra não gerada pela GIC, $p \not\in L$, “falha cedo”, no máximo em $\len{p}$ passos.
Ver o exemplo no código deste capítulo.
Processo de Normalização de uma Gramática
Resumindo todos os passos, a normalização de uma GIC consiste em:
- Eliminar a recursividade do símbolo inicial.
- Eliminar as produções vazias exceto, se necessário, do símbolo inicial.
- Eliminar as produções unitárias.
- Eliminar os símbolos inúteis.
- Transformar na Forma Normal de Chomsky.
- Transformar na Forma Normal de Greibach.
Conclusão
O problema da Análise Sintática, que se está a tentar resolver, é uma reformulação do Problema Principal de ALP para as GIC e pergunta “como determinar se $p \in L$” quando $p$ é uma palavra/programa e $L$ é uma linguagem gerada por uma gramática independente de contexto.
Inicialmente tentou-se formalizar as linguagens $L$ usando expressões regulares mas o Pumping Lemma mostrou a necessidade de uma abordagem mais geral — as gramáticas independentes de contexto.
Embora as GIC resolvam a adequação (com as expressões algébricas) e os autómatos de pilha proporcionem um modelo computacional, este não é eficiente.
A Normalização de uma GIC é um processo que começa com uma GIC e que, ao longo de vários passos, produz outras GIC equivalentes, de forma a obter-se uma gramática “adequada” aos algoritmos de pesquisa geral. A Forma Normal de Greibach impõe restrições fortes às produções, que podem ser bem aproveitadas para reduzir a complexidade/ramificação da pesquisa.
Mesmo com a FNG a ajudar na pesquisa, a resposta a “$p \in L$” ainda permite ramificações ao pesquisar a árvore das derivações. Portanto o problema do não determinismo persiste e, com ele, a eficiência fica por resolver.
Os algoritmos deterministas são o próximo assunto e assentam, tanto quanto possível, nas propriedades das gramáticas e das derivações.