Esta é uma reformulação dos programas de gramática São? solicitado anteriormente por Vag e com muitas sugestões dos comentaristas.
De que maneira uma gramática pode ser vista como especificando um modelo de computação? Se, por exemplo, usarmos uma gramática simples, livre de contexto, como
G ::= '1' -> '0' '+' '1'
'1' -> '1' '+' '0'
'2' -> '2' '+' '0'
'2' -> '1' '+' '1'
'2' -> '0' '+' '2'
'3' -> '3' '+' '0'
'3' -> '2' '+' '1'
'3' -> '1' '+' '2'
'3' -> '1' '+' '2'
Assumindo que o analisador não diferenciar entre terminais e não terminais símbolos como tenho demonstrado aqui, então é possível realizar operações aritméticas simples para números até 3.
Por exemplo, pegue a string
"2 + 0 + 1"
A execução de um analisador LR (1) nessa cadeia de caracteres deve fornecer a seguinte árvore de sintaxe concreta, na qual o resultado da computação é armazenado na raiz da árvore:
'3'
/ | \
/ | \
'2' '+' '1'
/ | \
/ | \
'2' '+' '0'
Assim, se considerarmos uma gramática um programa e um gerador de analisador um compilador , poderemos ver a linguagem de especificação gramatical como uma linguagem de programação ?
Além disso, poderíamos criar programas completos de Turing especificando gramáticas semelhantes a como você pode criar programas completos de turing com autômatos celulares ou o cálculo lambda ?
Em outras palavras, sabe-se que, no sentido de reconhecer uma linguagem, linguagens regulares correspondem a autômatos de estados finitos , linguagens livres de contexto correspondem a autômatos push-down e linguagens sensíveis a contexto correspondem a autômatos limitados lineares . No entanto, se considerarmos as gramáticas como dispositivos computacionais (ou seja, programas no sentido do exemplo acima), como classificamos a força computacional de cada classe de gramática na hierarquia de Chomsky?
- Gramáticas regulares
- Gramáticas sem contexto
- Gramáticas sensíveis ao contexto
- Gramáticas irrestritas (para idiomas recursivamente enumeráveis )
Além disso, que tal as subclasses menos conhecidas de gramáticas, como
- Gramáticas determinísticas livres de contexto (também LR (k) / LL (k) / SLR / LALR etc)
- Gramáticas de palavras aninhadas
- Gramáticas adjacentes à árvore
- Gramáticas indexadas
EDIT: A propósito, este é um detalhe na minha própria pergunta, mas eu não mencionei que não dei nenhum símbolo inicial para a gramática de exemplo e acenei com a mão na necessidade de distinguir entre terminais e não terminais. Tecnicamente ou tradicionalmente, acho que a gramática provavelmente teria que ser escrita de uma forma mais complicada como esta (onde S é o símbolo inicial e $ representa o terminal de final de fluxo):
G ::= S -> R0 '$'
S -> R1 '$'
S -> R2 '$'
R0 -> '0'
R0 -> R0 '+' '0'
R1 -> '1'
R1 -> R0 '+' '1'
R1 -> '1' '+' R0
R1 -> R0 '+' '1' '+' R0
R2 -> '2'
R2 -> R0 '+' '2'
R2 -> '2' '+' R0
R2 -> R0 '+' '2' '+' R0
R2 -> R1 '+' '1'
R2 -> R1 '+' '1' '+' R0
... não que isso realmente mude alguma coisa, mas achei que deveria mencionar.
Edição: Outra coisa que me veio à mente quando li a resposta de Gasche é que cada ramo da árvore no meu exemplo representa uma sub-computação. Se você observar cada regra de produção como uma função em que o LHS representa o resultado e o RHS representa seus argumentos, a estrutura da gramática determina como as funções são compostas.
Em outras palavras, o contexto do analisador, juntamente com seu mecanismo de orientação, ajuda a determinar não apenas quais funções aplicar ('como' um polimorfismo paramétrico), mas como elas devem ser compostas para formar novas funções.
Pelo menos, eu acho que você poderia olhar desta maneira para CFGs inequívocos, para outras gramáticas a ginástica mental é um pouco demais para mim agora.
Respostas:
Existe uma correspondência individual entre as gramáticas Chomsky Tipo 0 e as máquinas de Turing.
Isso é explorado na linguagem de programação Thue, que permite escrever programas completos de Turing especificados por uma string inicial e um conjunto de regras de reescrita de strings (uma gramática semi-Thue , que é equivalente a uma gramática tipo 0).
ATUALIZAR:
Além de linguagens esotéricas de "Turing tar-pit" como Thue, várias linguagens de uso geral que permitem ao programador estender sua própria sintaxe podem ser usadas para executar o cálculo completo de Turing durante o estágio de análise-compilação.
Os idiomas da família Lisp , em particular o Common Lisp , são provavelmente os exemplos mais óbvios, mas também, de maneira geral, idiomas com verificação estática de tipo que nem sempre precisam ser interrompidos, como C ++ com modelos , Scala e Qi .
fonte
Minha resposta não tem a intenção de ser formal, precisa e absolutamente in-topic. Acho que a resposta de Marc Hamman é sólida, mas sua pergunta me fez pensar em um tópico relacionado.
As gramáticas podem ser consideradas casos especiais de sistemas dedutivos: a entrada é um julgamento e a árvore de análise é uma derivação do julgamento, ou prova de que o julgamento é válido de acordo com as regras (gramaticais).
Nesse sentido, sua pergunta pode estar relacionada à abordagem de alguma parte da comunidade de programação lógica / pesquisa de prova (estou pensando em Dale Miller, por exemplo), que é que a pesquisa de prova tem conteúdo computacional, em oposição ao mais clássico ponto de vista da teoria do tipo / prova em que o cálculo é a normalização da prova .
Observação: relendo minha resposta, acho que a idéia de que "a construção de árvore de análise é prova de pesquisa" é um pouco exagerada aqui. A pesquisa de provas flui na outra direção: uma parte de um julgamento bastante complexo e, pelo uso repetido de regras de inferência que trabalham na estrutura da prova, esperamos alcançar axiomas mais simples que não precisam ser mais provados. Portanto, seria mais natural ver, em termos gramaticais, julgamentos complexos como não terminais, átomos como terminais e a pesquisa de provas como um problema de geração de palavras ou teste de não-vazio.
fonte
Não sei se entendi corretamente sua pergunta, mas se você estiver procurando por uma linguagem de programação baseada em um tipo de sistema de reescrita de strings, provavelmente estará interessado em Refal , que é baseado no formalismo do algoritmo Markov (um método de Turing- formalismo completo, que também é um sistema de reescrita de sequências gramaticais).
fonte
S -> A a; S -> B b; A -> 0; B -> 0
. Se programarmos isso revertendo as regras, precisaremos escolher regras diferentes para o processamento0
em tempo de execução para avaliar "0a" ou "0b" comoS
.(Apenas algumas considerações triviais. Pode ser um comentário, mas muito longo.)
De fato, o que você descreve parece ser a visão muito natural sobre o que é uma língua (no entendimento humano da "linguagem", seu objetivo) e como a gramática define uma linguagem.
Uma linguagem compreende (infinitamente muitas) formas sintáticas corretas que são interpretadas para fornecer os valores semânticos .
Se a interpretação for computável, as formas sintáticas de uma linguagem podem ser vistas como programas que calculam os valores semânticos.
Se assumirmos que uma linguagem é implementada como um dispositivo finito, podemos chamar essa representação finita de uma linguagem de "gramática". De acordo com esse entendimento, uma gramática se preocupa com a sintaxe, mas também com a semântica, ou seja, como calcular o valor semântico de uma expressão inteira a partir dos valores de suas partes (as partes atômicas e seus valores são armazenados em um "léxico") .
Algumas teorias da linguagem natural têm essa forma (a forma que é consistente com as considerações acima; já foi mencionada na resposta de @ gasche aqui): um sistema dedutivo que busca uma derivação da entrada (juntamente com o cálculo da semântica valor ou a construção do termo de prova; cf. correspondência de Curry-Horward). Portanto, se olharmos para sistemas como esse e os considerarmos gramáticas, sua pergunta será trivial : esses sistemas são exatamente criados para realizar cálculos da maneira que você descreve.
(De fato, os compiladores reais para linguagens de programação se parecem mais com um sistema com sintaxe e semântica: eles transformam a forma sintática de um programa em um executável, que é o significado semântico do programa, e não apenas em um símbolo inicial da gramática.)
fonte
Apenas para adicionar:
Uma visão gramatical da programação lógica por Pierre Deransart e Jan Maluszynski.
fonte
Que tal algo como números Peano:
ele reconhecerá qualquer string (número) deste formulário:
e deve retornar uma estrutura aninhada, com a profundidade sendo o número.
Mas começa a ficar complicado quando se quer implementos, basta dizer adição:
Faz todo sentido, pois reconhecerá apenas ints bem formados como este:
Mas essa gramática introduz uma divisão na árvore de análise sempre que houver uma soma. Portanto, em vez de termos uma árvore ramificada agradável, que mapeia diretamente para um número, temos a estrutura da expressão, ainda a alguns cálculos da efetiva valor. Portanto, nenhum cálculo é feito, apenas reconhecimento. O problema pode não ser a gramática, mas o analisador. Em vez disso, pode-se usar outra coisa, idk ... Outro ponto que vem à mente é a adequação do formalismo gramatical para expressar a computação. Quando você olha o axioma de um Peano (em notação semelhante a Haskell):
A terceira regra declara explicitamente uma transformação. Alguém poderia imaginar ter a mesma quantidade de significado em uma regra gramatical livre de contexto. E se sim, como!?
fonte