Abaixo está minha demonstração favorita (atual) de por que a análise de C ++ é (provavelmente) concluída em Turing , pois mostra um programa sintaticamente correto se e somente se um número inteiro for primo.
Portanto, afirmo que o C ++ não é isento de contexto nem sensível ao contexto .
Se você permitir seqüências arbitrárias de símbolos nos dois lados de qualquer produção, produza uma gramática Tipo 0 ("irrestrita") na hierarquia de Chomsky , que é mais poderosa que uma gramática sensível ao contexto; gramáticas irrestritas são completas de Turing. Uma gramática sensível ao contexto (Tipo 1) permite vários símbolos de contexto no lado esquerdo de uma produção, mas o mesmo contexto deve aparecer no lado direito da produção (daí o nome "sensível ao contexto"). [1] Gramáticas sensíveis ao contexto são equivalentes a máquinas de Turing de limite linear .
No programa de exemplo, o cálculo principal pode ser realizado por uma máquina de Turing de limite linear, portanto não prova a equivalência de Turing, mas a parte importante é que o analisador precisa executar o cálculo para realizar a análise sintática. Poderia ter sido qualquer computação expressável como uma instanciação de modelo e há todos os motivos para acreditar que a instanciação de modelo C ++ é Turing-complete. Veja, por exemplo, o artigo de Todd L. Veldhuizen em 2003 .
Independentemente disso, o C ++ pode ser analisado por um computador, portanto certamente pode ser analisado por uma máquina de Turing. Conseqüentemente, uma gramática irrestrita poderia reconhecê-la. Escrever essa gramática seria impraticável, e é por isso que o padrão não tenta fazê-lo. (Ver abaixo.)
O problema com a "ambiguidade" de certas expressões é principalmente um arenque vermelho. Para começar, a ambiguidade é um recurso de uma gramática específica, não de um idioma. Mesmo que se prove que um idioma não possui gramáticas inequívocas, se pode ser reconhecido por uma gramática livre de contexto, é livre de contexto. Da mesma forma, se não puder ser reconhecido por uma gramática livre de contexto, mas puder ser reconhecido por uma gramática sensível ao contexto, é sensível ao contexto. A ambiguidade não é relevante.
Mas, de qualquer forma, como a linha 21 (ou seja auto b = foo<IsPrime<234799>>::typen<1>();
) no programa abaixo, as expressões não são ambíguas; eles são simplesmente analisados de maneira diferente, dependendo do contexto. Na expressão mais simples do problema, a categoria sintática de certos identificadores depende de como eles foram declarados (tipos e funções, por exemplo), o que significa que a linguagem formal precisaria reconhecer o fato de que duas seqüências de comprimento arbitrário em o mesmo programa é idêntico (declaração e uso). Isso pode ser modelado pela gramática "copy", que é a gramática que reconhece duas cópias exatas consecutivas da mesma palavra. É fácil provar com o lema de bombeamentoque essa linguagem não é livre de contexto. Uma gramática sensível ao contexto para esse idioma é possível e uma gramática Tipo 0 é fornecida na resposta a esta pergunta: /math/163830/context-sensitive-grammar-for-the- linguagem de cópia .
Se alguém tentasse escrever uma gramática sensível ao contexto (ou irrestrita) para analisar o C ++, isso provavelmente preencheria o universo com rabiscos. Escrever uma máquina de Turing para analisar C ++ seria uma tarefa igualmente impossível. Até escrever um programa em C ++ é difícil, e até onde eu sei, nenhum deles foi provado correto. É por isso que o padrão não tenta fornecer uma gramática formal completa e por que escolhe escrever algumas das regras de análise em inglês técnico.
O que parece uma gramática formal no padrão C ++ não é a definição formal completa da sintaxe da linguagem C ++. Nem sequer é a definição formal completa da linguagem após o pré-processamento, que pode ser mais fácil de formalizar. (Porém, essa não seria a linguagem: a linguagem C ++, conforme definida pelo padrão, inclui o pré-processador, e a operação do pré-processador é descrita algoritmicamente, pois seria extremamente difícil de descrever em qualquer formalismo gramatical. É nessa seção. do padrão em que a decomposição lexical é descrita, incluindo as regras em que deve ser aplicada mais de uma vez.)
As várias gramáticas (duas gramáticas sobrepostas para análise lexical, uma que ocorre antes do pré-processamento e a outra, se necessário, posteriormente, além da gramática "sintática") são coletadas no Apêndice A, com esta nota importante (ênfase adicionada):
Este resumo da sintaxe do C ++ pretende ser um auxílio à compreensão. Não é uma afirmação exata do idioma . Em particular, a gramática descrita aqui aceita um superconjunto de construções válidas em C ++ . Regras de desambiguação (6.8, 7.1, 10.2) devem ser aplicadas para distinguir expressões de declarações. Além disso, regras de controle de acesso, ambiguidade e tipo devem ser usadas para eliminar construções sintaticamente válidas, mas sem sentido.
Finalmente, aqui está o programa prometido. A linha 21 está sintaticamente correta se e somente se o N in IsPrime<N>
for primo. Caso contrário, typen
é um número inteiro, não um modelo, portanto, typen<1>()
é analisado como (typen<1)>()
sintaticamente incorreto, porque ()
não é uma expressão sintaticamente válida.
template<bool V> struct answer { answer(int) {} bool operator()(){return V;}};
template<bool no, bool yes, int f, int p> struct IsPrimeHelper
: IsPrimeHelper<p % f == 0, f * f >= p, f + 2, p> {};
template<bool yes, int f, int p> struct IsPrimeHelper<true, yes, f, p> { using type = answer<false>; };
template<int f, int p> struct IsPrimeHelper<false, true, f, p> { using type = answer<true>; };
template<int I> using IsPrime = typename IsPrimeHelper<!(I&1), false, 3, I>::type;
template<int I>
struct X { static const int i = I; int a[i]; };
template<typename A> struct foo;
template<>struct foo<answer<true>>{
template<int I> using typen = X<I>;
};
template<> struct foo<answer<false>>{
static const int typen = 0;
};
int main() {
auto b = foo<IsPrime<234799>>::typen<1>(); // Syntax error if not prime
return 0;
}
[1] Para colocá-lo mais tecnicamente, toda produção em uma gramática sensível ao contexto deve ter a forma:
αAβ → αγβ
onde A
é um terminal e não α
, β
são possivelmente sequências vazias de símbolos gramaticais e γ
é uma sequência não vazia. (Os símbolos gramaticais podem ser terminais ou não terminais).
Isso pode ser lido como A → γ
apenas no contexto [α, β]
. Em uma gramática livre de contexto (Tipo 2) α
e β
deve estar vazia.
Acontece que você também pode restringir gramáticas com a restrição "monotônica", em que toda produção deve ter a forma:
α → β
onde |α| ≥ |β| > 0
( |α|
significa "o comprimento de α
")
É possível provar que o conjunto de idiomas reconhecidos pelas gramáticas monotônicas é exatamente o mesmo que o conjunto de idiomas reconhecidos pelas gramáticas sensíveis ao contexto, e geralmente é mais fácil basear as provas em gramáticas monotônicas. Consequentemente, é bastante comum ver "sensível ao contexto" usado como se quisesse "monotônico".