Il semble que GCC et LLVM-Clang utilisent des analyseurs de descente récursifs manuscrits , et pas généré par machine, basé sur Bison-Flex, analyse ascendante.
Quelqu'un ici pourrait-il confirmer que c'est le cas? Et si oui, pourquoi les frameworks de compilation traditionnels utilisent-ils des analyseurs manuscrits?
Mise à jour : blog intéressant sur ce sujet ici
Oui:
GCC a utilisé un analyseur yacc (bison) une fois, mais il a été remplacé par un analyseur de descente récursif manuscrit à un moment donné de la série 3.x: voir http://gcc.gnu.org/ wiki/New_C_Parser pour les liens vers les soumissions de correctifs pertinentes.
Clang utilise également un analyseur de descente récursif manuscrit: voir la section "Un analyseur unifié unique pour C, Objective C, C++ et Objective C++" vers la fin de http://clang.llvm.org/features. html .
Il y a un théorème populaire qui dit que C est difficile à analyser, et C++ essentiellement impossible.
Ce n'est pas vrai.
Ce qui est vrai, c'est que C et C++ sont assez difficiles à analyser en utilisant les analyseurs LALR (1) sans pirater la machine d'analyse et s'emmêler dans les données de la table des symboles. GCC en fait avait l'habitude de les analyser, en utilisant YACC et des hackeries supplémentaires comme ça, et oui c'était moche. Maintenant, GCC utilise des analyseurs manuscrits, mais toujours avec le piratage de la table des symboles. Les gens de Clang n'ont jamais essayé d'utiliser des générateurs d'analyseurs automatisés; AFAIK l'analyseur Clang a toujours été descente récursive codée à la main.
Ce qui est vrai, c'est que C et C++ sont relativement faciles à analyser avec des analyseurs plus puissants générés automatiquement, par exemple analyseurs GLR , et vous n'avez pas besoin de hacks. L'analyseur Elsa C++ en est un exemple. Notre C++ Front End en est un autre (comme le sont tous nos frontaux "compilateurs", GLR est une technologie d'analyse assez merveilleuse).
Notre frontal C++ n'est pas aussi rapide que GCC, et certainement plus lent qu'Elsa; nous avons mis peu d'énergie à le régler avec soin parce que nous avons d'autres problèmes plus urgents (néanmoins, il a été utilisé sur des millions de lignes de code C++). Elsa est probablement plus lente que GCC simplement parce qu'elle est plus générale. Étant donné la vitesse du processeur de nos jours, ces différences peuvent ne pas avoir beaucoup d'importance dans la pratique.
Mais les "vrais compilateurs" qui sont largement distribués aujourd'hui trouvent leurs racines dans des compilateurs d'il y a 10 ou 20 ans ou plus. Les inefficacités importaient alors beaucoup plus, et personne n'avait entendu parler des analyseurs GLR, alors les gens faisaient ce qu'ils savaient faire. Clang est certes plus récent, mais les théorèmes populaires conservent longtemps leur "persuasion".
Vous n'avez plus à le faire de cette façon. Vous pouvez très raisonnablement utiliser GLR et d'autres analyseurs similaires comme frontaux, avec une amélioration de la maintenabilité du compilateur.
Ce qui est vrai, c'est qu'il est difficile d'obtenir une grammaire qui correspond au comportement de votre compilateur de voisinage amical. Alors que pratiquement tous les compilateurs C++ implémentent (la plupart) de la norme d'origine, ils ont également tendance à avoir beaucoup d'extensions de coins sombres, par exemple, DLL spécifications dans les compilateurs MS, etc. Si vous avez un moteur d'analyse puissant , vous pouvez passer votre temps à essayer d'obtenir la grammaire finale pour qu'elle corresponde à la réalité, plutôt que d'essayer de plier votre grammaire pour qu'elle corresponde aux limites de votre générateur d'analyseur.
EDIT novembre 2012: depuis la rédaction de cette réponse, nous avons amélioré notre frontal C++ pour gérer l'intégralité de C++ 11, y compris les dialectes de variantes ANSI, GNU et MS. Bien qu'il y ait beaucoup de choses supplémentaires, nous n'avons pas à changer notre moteur d'analyse; nous venons de réviser les règles de grammaire. Nous l'avons fait devons changer l'analyse sémantique; C++ 11 est sémantiquement très compliqué, et ce travail submerge l'effort pour faire fonctionner l'analyseur.
EDIT février 2015: ... gère désormais le C++ 14 complet. (Voir obtenir une lecture humaine AST à partir du code c ++ pour les analyses GLR d'un simple morceau de code, et l'infâme "analyse la plus contrariante" de C++).
EDIT avril 2017: gère désormais (brouillon) C++ 17.
L'analyseur de Clang est un analyseur de descente récursive manuscrite, tout comme plusieurs autres frontaux C et C++ open source et commerciaux.
Clang utilise un analyseur à descente récursive pour plusieurs raisons:
Dans l'ensemble, pour un compilateur C++, cela n'a pas beaucoup d'importance: la partie d'analyse de C++ n'est pas triviale, mais c'est toujours l'une des parties les plus faciles, il est donc avantageux de rester simple. L'analyse sémantique - en particulier la recherche de nom, l'initialisation, la résolution de surcharge et l'instanciation de modèle - est des ordres de grandeur plus compliqués que l'analyse. Si vous voulez une preuve, allez vérifier la distribution du code et valide dans le composant "Sema" de Clang (pour l'analyse sémantique) vs son composant "Parse" (pour l'analyse).
l'analyseur de gcc est manuscrit. . Je soupçonne la même chose pour clang. C'est probablement pour plusieurs raisons:
Ce n'est probablement pas un cas de syndrome "pas inventé ici", mais plutôt "il n'y avait rien d'optimisé spécifiquement pour ce dont nous avions besoin, alors nous avons écrit le notre".
Des réponses étranges là-bas!
Les grammaires C/C++ ne sont pas sans contexte. Ils sont contextuels en raison de la barre Foo *; ambiguïté. Nous devons construire une liste de typedefs pour savoir si Foo est un type ou non.
Ira Baxter: Je ne vois pas l'intérêt de votre truc GLR. Pourquoi construire un arbre d'analyse qui comporte des ambiguïtés. Analyser signifie résoudre des ambiguïtés, construire l'arbre de syntaxe. Vous résolvez ces ambiguïtés dans un deuxième passage, donc ce n'est pas moins laid. Pour moi c'est bien plus moche ...
Yacc est un générateur d'analyseur LR (1) (ou LALR (1)), mais il peut être facilement modifié pour être sensible au contexte. Et il n'y a rien de laid dedans. Yacc/Bison a été créé pour aider à analyser le langage C, donc ce n'est probablement pas l'outil le plus laid pour générer un analyseur C ...
Jusqu'à GCC 3.x, l'analyseur C est généré par yacc/bison, avec une table typedefs construite pendant l'analyse. Avec la construction de table typedefs "en analyse", la grammaire C devient localement libre de contexte et de plus "localement LR (1)".
Maintenant, dans Gcc 4.x, c'est un analyseur de descente récursif. C'est exactement le même analyseur que dans Gcc 3.x, c'est toujours LR (1), et a les mêmes règles de grammaire. La différence est que l'analyseur yacc a été réécrit à la main, le décalage/réduction sont maintenant cachés dans la pile d'appels, et il n'y a pas "state454: if (nextsym == '(') goto state398" comme dans gcc 3.x yacc's analyseur, il est donc plus facile de corriger, de gérer les erreurs et d'imprimer des messages plus agréables, et d'effectuer certaines des étapes de compilation suivantes pendant l'analyse. Au prix d'un code beaucoup moins "facile à lire" pour un noob gcc.
Pourquoi sont-ils passés du yacc à la descente récursive? Parce qu'il est tout à fait nécessaire d'éviter que yacc analyse le C++, et parce que GCC rêve d'être un compilateur multilingue, c'est-à-dire de partager le maximum de code entre les différents langages qu'il peut compiler. C'est pourquoi le C++ et le parseur C sont écrits de la même manière.
C++ est plus difficile à analyser que C car il n'est pas "localement" LR (1) comme C, ce n'est même pas LR (k). Regarder func<4 > 2>
qui est une fonction de modèle instanciée avec 4> 2, c'est-à-dire func<4 > 2>
doit être lu comme func<1>
. Ce n'est certainement pas LR (1). Considérez maintenant, func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>
. C'est là qu'une descente récursive peut facilement résoudre l'ambiguïté, au prix de quelques appels de fonction supplémentaires (parse_template_parameter est la fonction d'analyse ambiguë. Si parse_template_parameter (17tokens) a échoué, essayez à nouveau parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... Ça marche).
Je ne sais pas pourquoi il ne serait pas possible d'ajouter dans les sous-grammaires récursives yacc/bison, peut-être que ce sera la prochaine étape dans le développement de l'analyseur gcc/GNU?