Di seguito è la mia (attuale) dimostrazione preferita del perché l'analisi del C ++ è (probabilmente) Turing completa , poiché mostra un programma sintatticamente corretto se e solo se un dato intero è primo.
Quindi asserisco che C ++ non è né privo di contesto né sensibile al contesto .
Se consenti sequenze di simboli arbitrarie su entrambi i lati di qualsiasi produzione, produci una grammatica di tipo 0 ("senza restrizioni") nella gerarchia di Chomsky , che è più potente di una grammatica sensibile al contesto; le grammatiche senza restrizioni sono complete di Turing. Una grammatica sensibile al contesto (Tipo 1) consente più simboli di contesto sul lato sinistro di una produzione, ma lo stesso contesto deve apparire sul lato destro della produzione (da cui il nome "sensibile al contesto"). [1] Le grammatiche sensibili al contesto sono equivalenti alle macchine di Turing con limite lineare .
Nel programma di esempio, il calcolo primo potrebbe essere eseguito da una macchina di Turing con limiti lineari, quindi non dimostra del tutto l'equivalenza di Turing, ma la parte importante è che il parser deve eseguire il calcolo per eseguire l'analisi sintattica. Potrebbe essere stato qualsiasi calcolo esprimibile come un'istanza del modello e ci sono tutte le ragioni per credere che l'istanza del modello C ++ sia Turing completa. Vedi, ad esempio, l'articolo di Todd L. Veldhuizen del 2003 .
Indipendentemente da ciò, C ++ può essere analizzato da un computer, quindi potrebbe sicuramente essere analizzato da una macchina di Turing. Di conseguenza, una grammatica senza restrizioni potrebbe riconoscerla. In realtà scrivere una tale grammatica sarebbe poco pratico, motivo per cui lo standard non prova a farlo. (Vedi sotto.)
Il problema con "ambiguità" di alcune espressioni è principalmente un'aringa rossa. Per cominciare, l'ambiguità è una caratteristica di una grammatica particolare, non una lingua. Anche se si può dimostrare che una lingua non ha grammatiche inequivocabili, se può essere riconosciuta da una grammatica senza contesto, è senza contesto. Allo stesso modo, se non può essere riconosciuto da una grammatica senza contesto ma può essere riconosciuto da una grammatica sensibile al contesto, è sensibile al contesto. L'ambiguità non è rilevante.
Ma in ogni caso, come nella riga 21 (cioè auto b = foo<IsPrime<234799>>::typen<1>();
) nel programma in basso, le espressioni non sono affatto ambigue; vengono semplicemente analizzati in modo diverso a seconda del contesto. Nell'espressione più semplice del problema, la categoria sintattica di alcuni identificatori dipende da come sono stati dichiarati (tipi e funzioni, ad esempio), il che significa che il linguaggio formale dovrebbe riconoscere il fatto che due stringhe di lunghezza arbitraria in lo stesso programma è identico (dichiarazione e uso). Questo può essere modellato dalla grammatica "copia", che è la grammatica che riconosce due copie esatte consecutive della stessa parola. È facile dimostrarlo con il lemma del pompaggioche questa lingua non è senza contesto. Una grammatica sensibile al contesto per questa lingua è possibile e una grammatica di tipo 0 è fornita nella risposta a questa domanda: /math/163830/context-sensitive-grammar-for-the- copia-lingua .
Se si dovesse tentare di scrivere una grammatica sensibile al contesto (o senza restrizioni) per analizzare il C ++, molto probabilmente riempirebbe l'universo di scarabocchi. Scrivere una macchina di Turing per analizzare il C ++ sarebbe un'impresa altrettanto impossibile. Anche scrivere un programma C ++ è difficile e per quanto ne so nessuno è stato dimostrato corretto. Questo è il motivo per cui lo standard non tenta di fornire una grammatica formale completa e perché sceglie di scrivere alcune delle regole di analisi in inglese tecnico.
Ciò che sembra una grammatica formale nello standard C ++ non è la definizione formale completa della sintassi del linguaggio C ++. Non è nemmeno la definizione formale completa della lingua dopo la preelaborazione, che potrebbe essere più facile da formalizzare. (Questo non sarebbe il linguaggio, però: il linguaggio C ++ come definito dallo standard include il preprocessore, e il funzionamento del preprocessore è descritto in modo algoritmico poiché sarebbe estremamente difficile da descrivere in qualsiasi formalismo grammaticale. È in quella sezione dello standard in cui è descritta la decomposizione lessicale, comprese le regole in cui deve essere applicato più di una volta.)
Le varie grammatiche (due grammatiche sovrapposte per l'analisi lessicale, una che si svolge prima della preelaborazione e l'altra, se necessario, in seguito, più la grammatica "sintattica") sono raccolte nell'Appendice A, con questa nota importante (enfasi aggiunta):
Questo riassunto della sintassi C ++ è inteso come un aiuto alla comprensione. Non è un'affermazione esatta della lingua . In particolare, la grammatica qui descritta accetta un superset di costrutti C ++ validi . Le regole di chiarimento delle ambiguità (6.8, 7.1, 10.2) devono essere applicate per distinguere le espressioni dalle dichiarazioni. Inoltre, le regole di controllo dell'accesso, ambiguità e tipo devono essere utilizzate per eliminare costrutti sintatticamente validi ma privi di significato.
Infine, ecco il programma promesso. La riga 21 è sintatticamente corretta se e solo se N in IsPrime<N>
è primo. Altrimenti, typen
è un numero intero, non un modello, quindi typen<1>()
viene analizzato come (typen<1)>()
sintatticamente errato perché ()
non è un'espressione sintatticamente valida.
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] Per dirla più tecnicamente, ogni produzione in una grammatica sensibile al contesto deve avere la forma:
αAβ → αγβ
dove A
è un non terminale e α
, β
possibilmente , sono sequenze vuote di simboli grammaticali, ed γ
è una sequenza non vuota. (I simboli grammaticali possono essere terminali o non terminali).
Questo può essere letto come A → γ
solo nel contesto [α, β]
. In una grammatica senza contesto (Tipo 2) α
e β
deve essere vuoto.
Si scopre che puoi anche limitare le grammatiche con la restrizione "monotona", dove ogni produzione deve essere nella forma:
α → β
dove |α| ≥ |β| > 0
( |α|
significa "la lunghezza di α
")
È possibile dimostrare che l'insieme delle lingue riconosciute dalle grammatiche monotone è esattamente lo stesso dell'insieme delle lingue riconosciute dalle grammatiche sensibili al contesto, ed è spesso il caso che sia più facile basare le prove sulle grammatiche monotone. Di conseguenza, è abbastanza comune vedere "sensibile al contesto" usato come se significasse "monotonico".