J'ai récemment essayé de m'apprendre comment fonctionnent les analyseurs (pour les langues/grammaires sans contexte), et la plupart semblent avoir du sens, sauf pour une chose. Je concentre mon attention en particulier sur grammaires LL (k), pour lequel les deux algorithmes principaux semblent être le parseur LL ( (en utilisant la table stack/parse) et le analyseur de descente récursive (simplement en utilisant la récursivité).
Pour autant que je puisse voir, l'algorithme de descente récursive fonctionne sur toutes les grammaires LL (k) et peut-être plus, alors qu'un analyseur LL fonctionne sur toutes les grammaires LL (k). Un analyseur de descente récursif est clairement beaucoup plus simple à implémenter qu'un analyseur LL (tout comme un analyseur LL est plus simple qu'un analyseur LR).
Donc, ma question est, quels sont les avantages/problèmes que l'on pourrait rencontrer lors de l'utilisation de l'un des algorithmes? Pourquoi pourrait-on choisir LL plutôt que la descente récursive, étant donné qu'il fonctionne sur le même ensemble de grammaires et est plus difficile à mettre en œuvre?
LL est généralement une technique d'analyse plus efficace que la descente récursive. En fait, un analyseur de descente récursive naïf sera en fait O (k ^ n) (où n est la taille d'entrée) dans le pire des cas. Certaines techniques telles que la mémorisation (qui donne un analyseur Packrat ) peuvent améliorer cela et étendre la classe de grammaires acceptées par l'analyseur, mais il y a toujours un compromis d'espace. Les analyseurs LL sont (à ma connaissance) toujours du temps linéaire.
D'un autre côté, vous avez raison dans votre intuition que les analyseurs de descente récursive peuvent gérer une plus grande classe de grammaires que LL. La descente récursive peut gérer n'importe quelle grammaire qui est LL (*) (c'est-à-dire, illimité lookahead) ainsi qu'un petit ensemble de grammaires ambiguës. En effet, la descente récursive est en fait une implémentation directement codée des PEG, ou grammaire (s) d'expression de l'analyseur . Plus précisément, l'opérateur disjonctif (a | b
) n'est pas commutatif, ce qui signifie que a | b
n'est pas égal b | a
. Un analyseur de descente récursive essaiera chaque alternative dans l'ordre. Donc, si a
correspond à l'entrée, il réussira même si b
aurait correspond à l'entrée. Cela permet de gérer les ambiguïtés classiques de "correspondance la plus longue" comme le problème de balancement else
simplement en ordonnant les disjonctions correctement.
Cela dit, il est possible d'implémenter un analyseur LL (k) en utilisant la descente récursive afin qu'il s'exécute en temps linéaire. Cela se fait en alignant essentiellement les ensembles de prédiction de sorte que chaque routine d'analyse détermine la production appropriée pour une entrée donnée en temps constant. Malheureusement, une telle technique élimine toute une classe de grammaires d'être manipulée. Une fois que nous entrons dans l'analyse prédictive, les problèmes tels que le balancement else
ne peuvent plus être résolus avec une telle facilité.
Quant à savoir pourquoi LL serait préféré à la descente récursive, c'est principalement une question d'efficacité et de maintenabilité. Les analyseurs de descente récursive sont nettement plus faciles à implémenter, mais ils sont généralement plus difficiles à maintenir car la grammaire qu'ils représentent n'existe sous aucune forme déclarative. La plupart des cas d'utilisation d'analyseurs non triviaux utilisent un générateur d'analyseurs tels que ANTLR ou Bison. Avec de tels outils, peu importe que l'algorithme soit une descente récursive directement codée ou une LL (k) pilotée par table.
Il est également intéressant de s'intéresser à ascension récursive , qui est un algorithme d'analyse directement encodé à la manière de la descente récursive, mais capable de gérer n'importe quelle grammaire LALR. Je creuserais également dans combinateurs d'analyseurs , qui sont un moyen fonctionnel de composer ensemble des analyseurs à descente récursive.