web-dev-qa-db-fra.com

Pourquoi le C++ ne peut-il pas être analysé avec un analyseur LR (1)?

Je lisais des informations sur les analyseurs syntaxiques et les générateurs d'analyseurs syntaxiques et j'ai trouvé cette déclaration dans la page d'analyse syntaxique de wikipedia:

De nombreux langages de programmation peuvent être analysés en utilisant une variante d’un analyseur syntaxique LR. Une exception notable est le C++.

Pourquoi est-ce? Quelle propriété particulière de C++ rend impossible l'analyse avec les analyseurs syntaxiques LR?

En utilisant Google, j'ai seulement constaté que C pouvait être parfaitement analysé avec LR (1), mais C++ requiert LR (). 

141
Cheery

Il existe un fil intéressant sur Lambda the Ultimate qui traite de la grammaire LALR pour C++

Il inclut un lien vers une thèse de doctorat qui inclut une discussion sur l'analyse syntaxique C++, qui stipule que:

"La grammaire C++ est ambiguë, Dépendante du contexte et potentiellement Nécessite un regard infini pour résoudre Certaines ambiguïtés".

Il donne ensuite un certain nombre d’exemples (voir page 147 du document PDF).

L'exemple est:

int(x), y, *const z;

sens

int x;
int y;
int *const z;

Comparer aux:

int(x), y, new int;

sens

(int(x)), (y), (new int));

(une expression séparée par des virgules).

Les deux séquences de jetons ont la même sous-séquence initiale, mais des arbres d'analyse différents, qui dépendent du dernier élément. Il peut y avoir arbitrairement beaucoup de jetons avant celui qui est ambigu.

87
Rob Walker

Les analyseurs syntaxiques LR ne peuvent pas gérer des règles de grammaire ambiguës, de par leur conception. (Faciliter la théorie dans les années 1970, lorsque les idées étaient en cours d’élaboration).

C et C++ permettent tous les deux la déclaration suivante:

x * y ;

Il a deux analyses différentes:

  1. Ce peut être la déclaration de y, en tant que pointeur sur le type x
  2. Il peut s'agir d'une multiplication de x et y, rejetant la réponse.

Maintenant, vous pourriez penser que ce dernier est stupide et devrait être ignoré… La plupart seraient d'accord avec vous; Cependant, dans certains cas, cela pourrait avoir un effet secondaire (par exemple, si Multiply est surchargé). mais ce n'est pas le point . Le point est là sont deux analyses syntaxiques différentes, et donc un programme .__ peut signifier différentes choses selon la façon dont ce devrait a été analysé.

Le compilateur doit accepter celui qui convient dans les circonstances appropriées et, en l'absence de toute autre information (par exemple, connaître le type de x), il doit collecter les deux afin de décider ultérieurement de la marche à suivre. Ainsi, une grammaire doit permettre cela. Et cela rend la grammaire ambiguë.

Ainsi, l'analyse LR pure ne peut pas gérer cela. De nombreux autres générateurs d’analyseurs largement disponibles, tels que Antlr, JavaCC, YACC, Bison traditionnel ou même des analyseurs syntaxiques de type PEG, ne peuvent pas être utilisés de manière "pure".

Il y a beaucoup de cas plus compliqués (l'analyse syntaxique des modèles nécessite une anticipation arbitraire, alors que LALR (k) peut regarder en avant vers le plus de k jetons), mais il suffit d'un contre-exemple pour éliminer pure LR d'autres) l'analyse.

La plupart des analyseurs syntaxiques C/C++ réels gèrent cet exemple en utilisant un type d'analyseur déterministe Avec un hack supplémentaire: ils mêlent l'analyse à la table de symboles Collection ... de sorte qu'au moment où "x" soit rencontré, l'analyseur sait si x est un type ou non, et peut donc choisir entre les deux analyses potentielles. Mais un analyseur Qui le fait n'est pas libre de contexte, et les analyseurs syntaxiques de LR (Les purs, etc.) sont (au mieux) exempts de contexte.

On peut tricher et ajouter des contrôles sémantiques de réduction de temps par règle dans les analyseurs À LR afin de lever cette ambiguïté. (Ce code n'est souvent pas simple). La plupart des autres types d'analyseurs Ont certains moyens d'ajouter des contrôles sémantiques à différents moments. Dans l'analyse, cela peut être utilisé pour cela.

Et si vous trichez suffisamment, vous pouvez faire fonctionner les analyseurs syntaxiques LR pour C et C++. Les gars de GCC l'ont fait pendant un certain temps, mais ils ont donné Pour l'analyse syntaxique manuelle, je pense parce qu'ils voulaient De meilleurs diagnostics d'erreur.

Il existe une autre approche: Nice and clean Et analyser C et C++ sans aucune table des symboles Hackery: Analyseurs GLR . Ce sont des analyseurs syntaxiques complets sans contexte (ayant effectivement une infinité lookahead). Les analyseurs GLR acceptent simplement les deux analyses, Produisant un "arbre" (en fait, un graphe acyclique dirigé qui ressemble le plus souvent à un arbre) Qui représente l'analyse ambiguë. Une passe post-analyse peut résoudre les ambiguïtés.

Nous utilisons cette technique dans les frontaux C et C++ pour notre Tookit de réingénierie logicielle DMS (à compter de juin 2017 Ils gèrent les langages C++ 17 complets en dialectes MS et GNU) . ont été utilisés pour traiter des millions de lignes de grands systèmes C et C++, avec des analyses syntaxiques complètes et précises produisant des AST avec des détails complets du code source. (Voir le AST pour l'analyse la plus frustrante de C++. )

223
Ira Baxter

Le problème n'est jamais défini comme ça, alors qu'il devrait être intéressant:

quel est le plus petit ensemble de modifications à la grammaire C++ qui serait nécessaire pour que cette nouvelle grammaire puisse être parfaitement analysée par un analyseur yacc "non dépourvu de contexte"? (utilisant seulement un 'hack': la désambiguïsation typename/identifier, l'analyseur informant le lexer de chaque typedef/class/struct)

J'en vois quelques uns:

  1. Type Type; est interdit. Un identificateur déclaré en tant que nom de type ne peut pas devenir un identificateur autre que de nom de nom (notez que struct Type Type n'est pas ambigu et qu'il peut être autorisé).

    Il y a 3 types de names tokens:

    • types: type intégré ou à cause d'un typedef/class/struct
    • fonctions de template
    • identifiants: fonctions/méthodes et variables/objets

    Considérer les fonctions de modèle comme des jetons différents résout l'ambiguïté func<. Si func est un nom de fonction modèle, alors < doit être le début d'une liste de paramètres de modèle, sinon func est un pointeur de fonction et < est l'opérateur de comparaison.

  2. Type a(2); est une instanciation d'objet. Type a(); et Type a(int) sont des prototypes de fonction.

  3. int (k); est complètement interdit, devrait être écrit int k;

  4. typedef int func_type(); et typedef int (func_type)(); sont interdits.

    Une fonction typedef doit être un pointeur de fonction typedef: typedef int (*func_ptr_type)();

  5. la récursion de modèles est limitée à 1024, sinon une augmentation maximale pourrait être transmise en option au compilateur.

  6. int a,b,c[9],*d,(*f)(), (*g)()[9], h(char); pourrait également être interdit, remplacé par int a,b,c[9],*d;int (*f)();

    int (*g)()[9];

    int h(char);

    une ligne par prototype de fonction ou déclaration de pointeur de fonction.

    Une alternative hautement préférée serait de changer la syntaxe de pointeur de fonction terrible,

    int (MyClass::*MethodPtr)(char*);

    être resyntaxé comme:

    int (MyClass::*)(char*) MethodPtr;

    ceci étant cohérent avec l'opérateur de casting (int (MyClass::*)(char*))

  7. typedef int type, *type_ptr; pourrait également être interdit: une ligne par typedef. Ainsi, il deviendrait

    typedef int type;

    typedef int *type_ptr;

  8. sizeof int, sizeof char, sizeof long long et co. pourrait être déclaré dans chaque fichier source. Ainsi, chaque fichier source utilisant le type int devrait commencer par

    #type int : signed_integer(4)

    et unsigned_integer(4) serait interdit en dehors de cette directive #type, ce serait un pas important dans l'ambiguïté sizeof int présente dans tant d'en-têtes C++

Le compilateur implémentant le C++ resyntaxé, s'il rencontrait une source C++ utilisant une syntaxe ambiguë, déplaçait source.cpp ainsi qu'un dossier ambiguous_syntax et créerait automatiquement un source.cpp traduit avant de le compiler.

Veuillez ajouter vos syntaxes C++ ambiguës si vous en connaissez!

14
reuns

Comme vous pouvez le voir dans mon répondre ici , C++ contient une syntaxe qui ne peut pas être analysée de manière déterministe par un analyseur LL ou LR en raison de l’étape de résolution de type (généralement une post-analyse) modifiant le ordre des opérations, et donc la forme fondamentale de AST (normalement attendue par une analyse de première étape).

8
Sam Harwell

Je pense que vous êtes assez proche de la réponse. 

LR (1) signifie que l'analyse de gauche à droite ne nécessite qu'un seul jeton pour regarder en avant pour le contexte, alors que LR () signifie une perspective infinie. C'est-à-dire que l'analyseur devrait savoir tout ce qui se passait pour savoir où il se trouve maintenant.

5
casademora

Le problème "typedef" en C++ peut être analysé avec un analyseur LALR (1) qui construit une table de symboles lors de l'analyse (pas un analyseur LALR pur). Le problème de "modèle" ne peut probablement pas être résolu avec cette méthode. L'avantage de ce type d'analyseur LALR (1) est que la grammaire (illustrée ci-dessous) est une grammaire LALR (1) (sans ambiguïté). 

/* C Typedef Solution. */

/* Terminal Declarations. */

   <identifier> => lookup();  /* Symbol table lookup. */

/* Rules. */

   Goal        -> [Declaration]... <eof>               +> goal_

   Declaration -> Type... VarList ';'                  +> decl_
               -> typedef Type... TypeVarList ';'      +> typedecl_

   VarList     -> Var /','...     
   TypeVarList -> TypeVar /','...

   Var         -> [Ptr]... Identifier 
   TypeVar     -> [Ptr]... TypeIdentifier                               

   Identifier     -> <identifier>       +> identifier_(1)      
   TypeIdentifier -> <identifier>      =+> typedefidentifier_(1,{typedef})

// The above line will assign {typedef} to the <identifier>,  
// because {typedef} is the second argument of the action typeidentifier_(). 
// This handles the context-sensitive feature of the C++ language.

   Ptr          -> '*'                  +> ptr_

   Type         -> char                 +> type_(1)
                -> int                  +> type_(1)
                -> short                +> type_(1)
                -> unsigned             +> type_(1)
                -> {typedef}            +> type_(1)

/* End Of Grammar. */

L'entrée suivante peut être analysée sans problème:

 typedef int x;
 x * y;

 typedef unsigned int uint, *uintptr;
 uint    a, b, c;
 uintptr p, q, r;

Le générateur d’analyseur LRSTAR lit la notation grammaticale ci-dessus et génère un analyseur qui gère le problème "typedef" sans ambiguïté dans l’arbre d’analyse ou dans AST. (Divulgation: je suis le gars qui a créé LRSTAR .)

0
Paul B Mann