Gramáticas Independentes de Contexto
Uma especificação formal de linguagens mais capaz do que as expressões regulares.
Introdução
Nos capítulos anteriores desenvolveu-se, para as ER, AFD e AFND, um conjunto de técnicas computacionais eficientes, com vista a resolver o Problema Principal de ALP. Porém, com o Pumping Lemma, conclui-se que é necessária uma forma mais geral de definir linguagens.
As linguagens naturais têm uma certa estrutura que resulta de regras gramaticais. Por exemplo, "O gato bebe leite." é válida em português mas "bebe. gato leite O" não. (N.B: neste exemplo os símbolos do alfabeto são as palavras da língua portuguesa — por exemplo, de um dicionário — e as palavras são sequências desses símbolos.)
Como descrever essas regras gramaticais? Por exemplo:
Frase : Sujeito Predicado
Sujeito : Artigo Substantivo
Predicado : Verbo Advérbio
Artigo : "o"
| "as"
Substantivo : "gato"
| "galinhas"
Verbo : "bebe"
| "voam"
Advérbio : "devagar"
| "baixinho"
Os termos Assim
são variáveis e "entre aspas"
são terminais.
Aplicando estas regras, obtém-se, por exemplo:
Frase : Sujeito Predicado
: Artigo Substantivo Predicado
: o Substantivo Predicado
: o gato Predicado
: o gato Verbo Advérbio
: o gato bebe Advérbio
: o gato bebe devagar
Também se obtêm frases com pouco sentido, como as gato voam devagar
. Esses problemas, em princípio, podem ser tratados com regras mais sofisticadas. No entanto, também este exemplo sem sentido mantém a estrutura entre as diferentes partes da palavra: o Artigo
está no início, etc. Isto é, usando aquelas regras nunca se obtém, por exemplo, devagar bebe o gato
.
Gramáticas e Linguagens Independentes de Contexto
As Gramáticas Independentes do Contexto são uma forma simples de representar rigorosamente regras gramaticais como as ilustradas acima.
Gramática Independente do Contexto (GIC). Uma Gramática Independente do Contexto é um tuplo em que:
- Variáveis (ou Não Terminais): é um conjunto de símbolos, designados variáveis ou não terminais.
- Terminais: é um conjunto de símbolos, disjunto de (isto é, ), designados terminais.
- Produções (ou Regras): é um conjunto finito. Os seus elementos são pares da forma com e , designados produções ou regras e denotados .
- Símbolo Inicial: .
Numa GIC há dois tipos de símbolos: Os terminais, sem produções associadas e as variáveis, com produções associadas. Intuitivamente, as variáveis vão ser sucessivamente transformadas, até restarem apenas símbolos terminais.
É importante notar que o alfabeto das palavras é isto é, as palavras têm terminais e variáveis.
Esta mistura justifica a definição seguinte:
Prefixo (resp. Sufixo) Terminal. Seja uma palavra formada por (zero ou mais) terminais e (zero ou mais) variáveis. Então, escrevendo em que e começa e termina por variáveis.
- O prefixo terminal de é .
- O sufixo terminal de é .
Isto é, é o maior prefixo só de terminais de e é o maior sufixo só de terminais.
Por exemplo, em o prefixo terminal é e o sufixo terminal é .
A aplicação de regras de uma GIC transforma palavras. Formalmente:
Derivação. Sejam uma GIC, .
- Se , então deriva diretamente e escreve-se .
- Se existem tais que então deriva em passos e escreve-se .
- Se existe tal que então deriva e escreve-se .
As regras de uma gramática são aplicadas às variáveis de uma palavra e o resultado é uma nova palavra. A esse processo chama-se "derivação direta". Se forem aplicadas derivações diretas obtém-se uma "derivação em passos". Por fim, se o número exato de passos não for importante, tem-se uma "derivação".
Por exemplo, dada a gramática :
- pela regra .
- Também pela regra .
- pela regra .
Em certas circunstâncias é necessário especificar a GIC de onde estão a ser usadas as derivações. Nesse caso acrescenta-se o índice e a notação das derivações fica para indicar que são usadas apenas produções de .
Para escrever uma GIC é comum usarem-se as seguintes regras para simplificar a notação:
- Os terminais são representados por minúsculas e as variáveis por maiúsculas.
- Se nada for dito, é o símbolo inicial.
- As produções de cada variável ficam agrupadas e, entre si, separadas por "".
Por exemplo, a gramática acima fica completamente definida por
As produções de uma gramática transformam palavras noutras palavras. Essa transformação tem a designação técnica de derivação e é a base para associar uma linguagem a uma GIC.
Linguagem Gerada. Seja uma GIC.
- O conjunto das palavras deriváveis a partir de é .
- A linguagem gerada por é .
- Neste caso diz-se que é uma Linguagem Independente do Contexto.
- Duas gramáticas são equivalentes se geram a mesma linguagem.
Observação. As palavras da linguagem gerada são só de terminais. Isto é: .
Tal como foi feito para as ER, os AFD e AFND, a equivalência entre GIC assenta na linguagem associada.
Por exemplo, qual é a linguagem gerada por ?
- Da produção resulta a derivação . Portanto .
- Da derivação resulta que .
- Igualmente, de conclui-se que .
- Sucessivamente, .
Portanto, gera a linguagem simplificada dos parêntesis equilibrados, .
A linguagem , usada para mostrar que as linguagens recursivas não são adequadas para definir linguagens de programação é a linguagem gerada por uma GIC com apenas duas produções.
Este exemplo é um sinal positivo para resolução do Problema Principal de ALP, pelo menos em termos da adequação. Falta ainda aprofundar casos mais complexos de adequação e tratar dos aspetos da computação e da eficiência.
Recursividade
Donde vem a capacidade das GIC para gerar tão facilmente , quando as ER falharam?
Olhando de novo para a GIC em questão, , destaca-se a regra . O "" em "" permite "repetir indefinidamente" o , como um ciclo.
Esta possibilidade de "repetições indefinidamente" está além da capacidade expressiva das ER. Porém, é uma espada de dois gumes, que levanta problemas novos. Por exemplo, que palavras são geradas pela GIC ?
Formalmente, a produção é "diretamente recursiva" e a recursão pode surgir de várias formas:
Produção Recursiva. Não Terminal Recursivo, Derivação Recursiva. Sejam uma GIC, e .
- Uma produção da forma é diretamente recursiva.
- Uma variável é recursiva se (isto é, em um ou mais passos).
- Uma derivação da forma onde não ocorre em é indiretamente recursiva.
Por exemplo,
- é uma produção diretamente recursiva.
- No exemplo anterior, é uma variável recursiva.
- Na gramática , a derivação é indiretamente recursiva.
Mais tarde serão tratadas várias situações relacionadas com a recursividade mas por enquanto importa aprofundar mais aspetos das derivações.
Em primeiro lugar importa assegurar que, numa derivação , as sub-derivações de uma variável em não são afetadas pelas sub-derivações de outras variáveis também em .
Por exemplo, supondo que numa certa CIG , é intuitivamente evidente que os resultam de e apenas de e que os resultam apenas do e que, no total, fizeram-se derivações.
Mas a evidência intuitiva precisa de um apoio rigoroso:
Independência das Sub-Derivações. Seja uma GIC e uma derivação em onde
com os (portanto, palavras só de terminais) e os .
Então existem palavras tais que:
- Para cada , .
- .
- .
A independência está na afirmação de que o resultado é obtido isoladamente em cada variável, .
Por exemplo, dada a gramática , a derivação em passos tem sub-derivações:
- com dois passos; .
- com três passos;
- .
Derivação Esquerda/Direita
Considerando a GIC , a partir do símbolo inicial, , há três opções para "escolher a produção". Se a escolha for , fica-se com a derivação , se for a derivação é e também é uma derivação possível.
Em há duas opções para "escolher a variável". A primeira ou a segunda? Se for a primeira, obtém-se , e mas se for a segunda os resultados possíveis são , e .
As regras das derivações permitem duas escolhas:
- Que variável substituir?
- Com que produção dessa variável?
Se por um lado estas escolhas permitem "capacidade expressiva" (o que é bom: as ER não são suficientemente expressivas) por outro lado as escolhas têm um custo elevado (exponencial) na eficiência computacional de futuros algoritmos sobre CIG e LIC.
Uma boa solução para este dilema restringe as escolhas sem sacrificar a "capacidade expressiva".
Derivação Esquerda. Derivação Direita.
Numa derivação esquerda é escolhida a variável mais à esquerda em todos os passos. Nas derivações esquerdas o símbolo é substituído por .
Numa derivação direita é escolhida a variável mais à direita em todos os passos. Nas derivações direitas o símbolo é substituído por .
Seja uma GIC. Para cada ,
Isto é, numa derivação esquerda escolhe-se sempre a primeira variável e numa derivação direita escolhe-se sempre a última. A "capacidade expressiva" não fica prejudicada porque qualquer palavra só de terminais que seja gerada pela GIC também é gerada por uma derivação esquerda e também é gerada por uma derivação direita.
Por exemplo, continuando com a gramática , como derivar ?
- Sem restrições, uma solução é . Nuns passos foi escolhida a primeira variável, noutros a última.
- Derivação esquerda: .
- Derivação direita: .
Árvore de Derivação
As derivações podem ser representadas graficamente por diagramas em árvore.
Árvore de Derivação. Seja uma GIC e uma derivação de .
A árvore da derivação é formada por:
- Raiz: O símbolo inicial .
- Filhos: Se a produção , com for a produção usada para substituir a variável então o vértice tem filhos por essa ordem.
- Folhas Vazias: Se for a produção usada para substituir a variável então o vértice tem como único filho.
Uma palavra tem árvore de derivação se for a concatenação das folhas de lidas da esquerda para a direita.
Por exemplo, a derivação tem a seguinte árvore:
Exemplo de Árvore de Derivação |
---|
Lendo as folhas da esquerda para a direita obtém-se , a palavra derivada.
Ambiguidade
Um problema (ver Dangling Else na wikipedia) que ocorre frequentemente em várias linguagens de programação, e que é responsável por erros difíceis de detetar e com consequências graves, pode ser ilustrado no seguinte exemplo:
if (a) if (b) s1; else s2;
Este fragmento tanto pode ser tratado como
if (a) {
if (b) s1;
else s2;
}
ou como
if (a) {
if (b) s1;
}
else s2;
No primeiro caso, se a
for falso não corre nem s1
nem s2
. No segundo caso, se a
for falso corre s2
.
O que acontece se a
for "inimigo detetado", s2
"lançar mísseis" e b
testa "cenário simulado"?
Este problema resulta diretamente da gramática porque a palavra if (a) if (b) s1; else s2;
tem duas derivações diferentes, uma que associa o else
ao primeiro if
e outra que o associa ao segundo.
Formalmente,
Gramática Ambígua. Linguagem Inerentemente Ambígua.
- Uma gramática é ambígua se alguma palavra de tem:
- duas árvores de derivação distintas ou
- duas derivações esquerdas distintas ou
- duas derivações direitas distintas.
- Uma linguagem é inerentemente ambígua se não for gerada por uma gramática não ambígua.
Nesta definição é importante lembrar e distinguir o seguinte:
- Uma linguagem pode ser gerada por várias gramáticas equivalentes. Algumas dessas gramáticas podem ser ambíguas e outras não. Por isso, "ambígua" é uma propriedade que diz respeito às gramáticas.
- Também pode acontecer que todas as gramáticas que geram uma certa linguagem sejam ambíguas. Esta situação é uma caraterística da linguagem, não das gramáticas que a geram. Por isso "inerentemente ambígua" é uma propriedade que diz respeito às linguagens.
Um exemplo de uma linguagem inerentemente ambígua é:
A gramática é ambígua porque e são duas derivações direitas de . Esta gramática é equivalente a que não é ambígua (exercício: porquê?).
Como é que as LIC se relacionam com as LR? O que foi feito nos capítulos anteriores está "perdido" ou as GIC estendem as ER?
Gramáticas Regulares e Simulação de AFND
Como definir linguagens regulares com GIC?
Gramática Regular. Uma gramática é regular se todas as suas produções têm uma das formas seguintes: com e .
O interesse desta definição é que
Se é uma GIC regular então é uma linguagem regular.
A demonstração desta afirmação está fora do âmbito de ALP. A consequência das gramáticas regulares é que as GIC podem gerar algumas linguagens regulares*. Será que podem gerar todas?
Simulação de AFND por GIC. Seja um AFND. A GIC equivalente a é em que:
- variáveis - estados. .
- símbolo inicial - estado inicial. .
- produções - estados finais. Se então .
- produções - transições . Para cada , se então .
- produções - transições . Se então .
Neste caso .
Dado um AFND qualquer, aplicando este método, obtém-se uma GIC equivalente no sentido em que a linguagem aceite pelo AFND é gerada pela GIC. Portanto as LIC incluem todas as LR.
Por exemplo, dado o AFND antes usado para ilustrar o Algoritmo de Kleene:
AFND equivalente a |
---|
vai construir-se a GIC em que:
- As variáveis são .
- O símbolo inicial é .
- As transições e os estados finais definem as seguintes produções:
A palavra abaabb
é aceite pelo AFND, pela computação
Será gerada pela GIC?
Este exemplo também ilustra como a produção da GIC representa a transição do AFND.
Conclusão
As GIC foram introduzidas depois de se ter constatado, no capítulo anterior, que as linguagens regulares são insuficientes para definir as linguagens de programação.
- Logo um dos primeiros exemplos de GIC permitiu definir a linguagem simplificada dos parêntesis equilibrados, o que é um indicador positivo para as GIC.
- Outro exemplo, as expressões algébricas, permitiu ilustrar como tratar o problema da ambiguidade.
- A questão da relação entre as LR e as LIC ficou resolvida com a simulação de AFND por uma GIC regular.
Embora as GIC pareçam um candidato razoável para descrever as linguagens de programação, falta tratar as questões da computação: Como responder algoritmicamente, de forma eficiente, à questão ?