Ci-dessous est ma démonstration préférée (actuelle) de la raison pour laquelle l'analyse C ++ est (probablement) Turing-complète , car elle montre un programme qui est syntaxiquement correct si et seulement si un entier donné est premier.
J'affirme donc que C ++ n'est ni sans contexte ni sensible au contexte .
Si vous autorisez des séquences de symboles arbitraires des deux côtés d'une production, vous produisez une grammaire de type 0 ("sans restriction") dans la hiérarchie Chomsky , qui est plus puissante qu'une grammaire contextuelle; les grammaires libres sont Turing-complete. Une grammaire contextuelle (Type-1) autorise plusieurs symboles de contexte sur le côté gauche d'une production, mais le même contexte doit apparaître sur le côté droit de la production (d'où le nom "context-sensitive"). [1] Les grammaires contextuelles sont équivalentes aux machines de Turing à limites linéaires .
Dans l'exemple de programme, le calcul principal pourrait être effectué par une machine de Turing linéaire, donc cela ne prouve pas tout à fait l'équivalence de Turing, mais l'important est que l'analyseur doit effectuer le calcul afin d'effectuer une analyse syntaxique. Il aurait pu s'agir de n'importe quel calcul exprimable sous forme d'instanciation de modèle et il y a tout lieu de croire que l'instanciation de modèle C ++ est Turing-complete. Voir, par exemple, l'article de Todd L. Veldhuizen de 2003 .
Quoi qu'il en soit, C ++ peut être analysé par un ordinateur, il pourrait donc certainement être analysé par une machine de Turing. Par conséquent, une grammaire illimitée pourrait le reconnaître. En fait, écrire une telle grammaire ne serait pas pratique, c'est pourquoi la norme n'essaie pas de le faire. (Voir ci-dessous.)
Le problème de «l'ambiguïté» de certaines expressions est principalement un hareng rouge. Pour commencer, l'ambiguïté est une caractéristique d'une grammaire particulière, pas une langue. Même s'il peut être prouvé qu'une langue n'a pas de grammaires sans ambiguïté, si elle peut être reconnue par une grammaire sans contexte, elle est sans contexte. De même, si elle ne peut pas être reconnue par une grammaire hors contexte mais qu'elle peut être reconnue par une grammaire contextuelle, elle est contextuelle. L'ambiguïté n'est pas pertinente.
Mais de toute façon, comme la ligne 21 (ie auto b = foo<IsPrime<234799>>::typen<1>();
) dans le programme ci-dessous, les expressions ne sont pas du tout ambiguës; ils sont simplement analysés différemment selon le contexte. Dans l'expression la plus simple du problème, la catégorie syntaxique de certains identificateurs dépend de la façon dont ils ont été déclarés (types et fonctions, par exemple), ce qui signifie que le langage formel devrait reconnaître le fait que deux chaînes de longueur arbitraire dans les mêmes programmes sont identiques (déclaration et utilisation). Cela peut être modélisé par la grammaire "copie", qui est la grammaire qui reconnaît deux copies exactes consécutives du même mot. C'est facile à prouver avec le lemme de pompageque cette langue n'est pas sans contexte. Une grammaire contextuelle pour cette langue est possible, et une grammaire de type 0 est fournie dans la réponse à cette question: /math/163830/context-sensitive-grammar-for-the- copie-langue .
Si l'on tentait d'écrire une grammaire contextuelle (ou non restreinte) pour analyser le C ++, cela remplirait très probablement l'univers de gribouillages. Écrire une machine de Turing pour analyser le C ++ serait une entreprise tout aussi impossible. Même l'écriture d'un programme C ++ est difficile, et pour autant que je sache, aucun n'a été prouvé correct. C'est pourquoi la norme n'essaie pas de fournir une grammaire formelle complète, et pourquoi elle choisit d'écrire certaines des règles d'analyse en anglais technique.
Ce qui ressemble à une grammaire formelle dans la norme C ++ n'est pas la définition formelle complète de la syntaxe du langage C ++. Ce n'est même pas la définition formelle complète de la langue après le prétraitement, qui pourrait être plus facile à formaliser. (Ce ne serait pas le langage, cependant: le langage C ++ tel que défini par la norme inclut le préprocesseur, et le fonctionnement du préprocesseur est décrit de manière algorithmique car il serait extrêmement difficile à décrire dans n'importe quel formalisme grammatical. C'est dans cette section de la norme où la décomposition lexicale est décrite, y compris les règles où elle doit être appliquée plus d'une fois.)
Les différentes grammaires (deux grammaires qui se chevauchent pour l'analyse lexicale, l'une qui a lieu avant le prétraitement et l'autre, si nécessaire, par la suite, plus la grammaire "syntaxique") sont rassemblées dans l'annexe A, avec cette note importante (non souligné dans l'original):
Ce résumé de la syntaxe C ++ est destiné à être une aide à la compréhension. Ce n'est pas un énoncé exact de la langue . En particulier, la grammaire décrite ici accepte un sur - ensemble de constructions C ++ valides . Des règles de désambiguïsation (6.8, 7.1, 10.2) doivent être appliquées pour distinguer les expressions des déclarations. De plus, les règles de contrôle d'accès, d'ambiguïté et de type doivent être utilisées pour éliminer les constructions syntaxiquement valides mais dénuées de sens.
Enfin, voici le programme promis. La ligne 21 est syntaxiquement correcte si et seulement si N in IsPrime<N>
est premier. Sinon, il typen
s'agit d'un entier et non d'un modèle, il typen<1>()
est donc analysé comme (typen<1)>()
étant syntaxiquement incorrect car il ()
ne s'agit pas d'une expression syntaxiquement valide.
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] Pour le dire plus techniquement, chaque production dans une grammaire contextuelle doit être de la forme:
αAβ → αγβ
où A
est un non-terminal et α
, β
sont éventuellement des séquences vides de symboles de grammaire, et γ
est une séquence non-vide. (Les symboles de grammaire peuvent être terminaux ou non terminaux).
Cela peut être lu A → γ
uniquement dans le contexte [α, β]
. Dans une grammaire sans contexte (Type 2), α
et β
doit être vide.
Il s'avère que vous pouvez également restreindre les grammaires avec la restriction "monotone", où chaque production doit être de la forme:
α → β
où |α| ≥ |β| > 0
( |α|
signifie "la longueur de α
")
Il est possible de prouver que l'ensemble des langues reconnues par les grammaires monotones est exactement le même que l'ensemble des langues reconnues par les grammaires contextuelles, et il arrive souvent qu'il soit plus facile de baser les preuves sur des grammaires monotones. Par conséquent, il est assez courant de voir «sensible au contexte» utilisé comme s'il signifiait «monotone».