Les passes d'analyse et de lexing séparées sont-elles une bonne pratique avec les combinateurs d'analyseurs?


18

Quand j'ai commencé à utiliser des combinateurs d'analyseurs, ma première réaction a été un sentiment de libération de ce qui semblait être une distinction artificielle entre l'analyse et lexing. Tout d'un coup, tout n'était que de l'analyse!

Cependant, j'ai récemment rencontré cette publication sur codereview.stackexchange illustrant quelqu'un rétablissant cette distinction. Au début, je pensais que c'était très stupide de leur part, mais ensuite le fait que des fonctions existent dans Parsec pour soutenir ce comportement me conduit à me remettre en question.

Quels sont les avantages / inconvénients de l'analyse sur un flux déjà lexé dans les combinateurs d'analyseurs?


S'il vous plaît quelqu'un pourrait-il ajouter la balise [parser-combinator]?
Eli Frey

Réponses:


15

Sous l'analyse, nous comprenons le plus souvent l'analyse des langages sans contexte. Un langage sans contexte est plus puissant qu'un langage ordinaire, donc l'analyseur peut (le plus souvent) faire le travail de l'analyseur lexical tout de suite.

Mais, c'est a) assez peu naturel b) souvent inefficace.

Pour a), si je pense à quoi ifressemble par exemple une expression, je pense que SI expr ALORS expr ELSE expr et pas 'i' 'f', peut-être quelques espaces, alors tout caractère avec lequel une expression peut commencer, etc. vous obtenez le idée.

Pour b), il existe des outils puissants qui font un excellent travail pour reconnaître les entités lexicales, comme les identifiants, les littéraux, les crochets de toutes sortes, etc. Ils feront leur travail en un rien de temps et vous donneront une interface agréable: une liste de jetons. Plus besoin de sauter des espaces dans l'analyseur, votre analyseur sera beaucoup plus abstrait lorsqu'il traitera de jetons et non de personnages.

Après tout, si vous pensez qu'un analyseur devrait être occupé avec des trucs de bas niveau, pourquoi alors traiter les personnages? On pourrait aussi l'écrire au niveau des bits! Vous voyez, un tel analyseur qui fonctionne au niveau du bit serait presque incompréhensible. C'est la même chose avec les personnages et les jetons.

Juste mes 2 cents.


3
Juste pour des raisons de précision: un analyseur peut toujours faire le travail d'un analyseur lexical.
Giorgio

Aussi, en ce qui concerne l'efficacité: je ne sais pas si un analyseur serait moins efficace (plus lent). Je m'attendrais à ce que la grammaire résultante contienne une sous-grammaire qui décrit un langage normal, et le code de cette sous-grammaire serait aussi rapide qu'un analyseur lexical correspondant. À mon avis, le véritable point est (a): à quel point il est naturel et intuitif de travailler avec un analyseur plus simple et plus abstrait.
Giorgio

@Giorgio - Concernant votre 1er commentaire: vous avez raison. Ce que j'avais à l'esprit ici, ce sont des cas où le lexer fait pragmatiquement un travail qui rend la grammaire plus facile, afin que l'on puisse utiliser LALR (1) au lieu de LALR (2).
Ingo

2
J'ai retiré mon acceptation de votre réponse après de nouvelles expérimentations et réflexions. Il semble que vous venez tous deux du monde d'Antlr et de tous. Compte tenu de la nature de première classe des combinateurs d'analyseurs, je finis souvent par définir un analyseur d'encapsuleur pour mes analyseurs de jetons, en laissant chaque jeton comme un nom unique dans la couche d'analyse des analyseurs. par exemple, votre exemple if ressemblerait if = string "if" >> expr >> string "then" >> expr >> string "else" >> expr.
Eli Frey

1
La performance est toujours une question ouverte, je vais faire quelques benchmarks.
Eli Frey

8

Tout le monde suggérant que séparer lexing et analyse est une "bonne pratique" - je ne suis pas d'accord - dans de nombreux cas, effectuer lexing et l'analyse en une seule passe donne beaucoup plus de pouvoir, et les implications en termes de performances ne sont pas aussi mauvaises qu'elles sont présentées dans le autres réponses (voir Packrat ).

Cette approche brille quand on doit mélanger un certain nombre de langues différentes dans un seul flux d'entrée. Cela n'est pas seulement nécessaire pour les langages étranges orientés métaprogrammation comme Katahdin et similaires , mais aussi pour des applications beaucoup plus courantes, comme la programmation alphabétisée (mélange de latex et, par exemple, C ++), l'utilisation de HTML dans les commentaires, le bourrage de Javascript dans HTML, et bientôt.


Dans ma réponse, j'ai suggéré qu'il s'agissait d'une "bonne pratique dans certains contextes" et non pas d'une "meilleure pratique dans tous les contextes".
Giorgio

5

Un analyseur lexical reconnaît un langage normal et un analyseur reconnaît un langage sans contexte. Étant donné que chaque langue régulière est également sans contexte (elle peut être définie par une grammaire dite linéaire à droite ), un analyseur peut également reconnaître un langage régulier et la distinction entre analyseur et analyseur lexical semble ajouter une complexité inutile: un contexte unique -la grammaire libre (analyseur) pourrait faire le travail d'un analyseur et d'un analyseur lexical.

D'un autre côté, il peut être utile de capturer certains éléments d'un langage sans contexte à travers un langage normal (et donc un analyseur lexical) car

  1. Souvent, ces éléments apparaissent si souvent qu'ils peuvent être traités de manière standard: reconnaissance des littéraux de nombre et de chaîne, des mots clés, des identifiants, saut des espaces blancs, etc.
  2. La définition d'une langue régulière de jetons rend la grammaire sans contexte résultante plus simple, par exemple, on peut raisonner en termes d'identificateurs, pas en termes de caractères individuels, ou on peut ignorer complètement les espaces blancs si cela n'est pas pertinent pour cette langue particulière.

Ainsi, séparer l'analyse syntaxique de l'analyse lexicale présente l'avantage de pouvoir travailler avec une grammaire sans contexte plus simple et d'encapsuler certaines tâches de base (souvent routinières) dans l'analyseur lexical (divide et impera).

ÉDITER

Je ne suis pas familier avec les combinateurs d'analyseurs, donc je ne sais pas comment les considérations ci-dessus s'appliquent dans ce contexte. Mon impression est que même si avec les combinateurs d'analyseurs, on n'a qu'une seule grammaire sans contexte, la distinction entre deux niveaux (analyse lexicale / analyse syntaxique) pourrait aider à rendre cette grammaire plus modulaire. Comme indiqué, la couche d'analyse lexicale inférieure peut contenir des analyseurs de base réutilisables pour les identificateurs, les littéraux, etc.


2
Les lexèmes tombent dans les grammaires normales non pas naturellement, mais par convention, puisque tous les lexers sont construits sur des moteurs d'expression régulière. Cela limite le pouvoir expressif des langues que vous pouvez concevoir.
SK-logic

1
Pouvez-vous donner un exemple de langue pour laquelle il serait approprié de définir des lexèmes qui ne peuvent pas être décrits comme une langue régulière?
Giorgio

1
par exemple, dans quelques-uns des langages spécifiques au domaine que j'ai construits, les identifiants auraient pu être des expressions TeX, ce qui a simplifié l'impression du code, par exemple, une expression comme \alpha'_1 (K_0, \vec{T}), où \ alpha'_1, K_0 et \ vec {T} sont des identifiants.
SK-logic

1
Étant donné une grammaire sans contexte, vous pouvez toujours prendre un N non terminal et traiter les mots qu'il peut dériver comme des unités qui ont une signification utile en eux-mêmes (par exemple une expression, un terme, un nombre, une déclaration). Cela peut être fait indépendamment de la façon dont vous analysez cette unité (analyseur, analyseur + lexer, etc.). IMO le choix d'un analyseur + lexer est plus technique (comment implémenter l'analyse) que sémantique (quelle est la signification des blocs de code source que vous analysez). Peut-être que je néglige quelque chose, mais les deux aspects me semblent orthogonaux.
Giorgio

3
Donc, je suis d'accord avec vous: si vous définissez des blocs de construction de base arbitraires ( lexèmes ) et que vous souhaitez utiliser un analyseur lexical pour les reconnaître, ce n'est pas toujours possible. Je me demande simplement si c'est le but d'un lexer. Pour autant que je sache, l'objectif d'un analyseur lexical est plus technique: retirer à l'analyseur certains détails d'implémentation de bas niveau et fastidieux.
Giorgio

3

Simplement, lexing et l'analyse doivent être séparés car ce sont des complexités différentes. Lexing est un DFA (automate fini déterministe) et un analyseur est un PDA (automate push-down). Cela signifie que l'analyse syntaxique consomme intrinsèquement plus de ressources que lexing, et il existe des techniques d'optimisation spécifiques disponibles uniquement pour les DFA. De plus, l'écriture d'une machine à états finis est beaucoup moins complexe et plus facile à automatiser.

Vous gaspillez en utilisant un algorithme d'analyse syntaxique pour lex.


Si vous utilisez un analyseur pour effectuer une analyse lexicale, le PDA n'utilisera jamais la pile, il fonctionnerait essentiellement comme un DFA: il suffit de consommer des entrées et de sauter entre les états. Je ne suis pas sûr à 100%, mais je pense que les techniques d'optimisation (réduisant le nombre d'états) qui peuvent être appliquées à un DFA peuvent également être appliquées à un PDA. Mais oui: il est plus facile d'écrire l'analyseur lexical en tant que tel sans utiliser un outil plus puissant, puis d'écrire un analyseur plus simple par-dessus.
Giorgio

De plus, cela rend le tout plus flexible et maintenable. Par exemple, supposons que nous ayons un analyseur pour le langage Haskell sans la règle de mise en page (c'est-à-dire avec des points-virgules et des accolades). Si nous avons un lexer séparé, nous pourrions maintenant ajouter les règles de disposition en faisant simplement un autre passage sur les jetons, en ajoutant des accolades et des points-virgules si nécessaire. Ou, pour un exemple plus simple: supposons que nous avons commencé avec un langage prenant en charge les caractères ASCII uniquement dans les identifiants et maintenant nous voulons prendre en charge les lettres unicode dans les identifiants.
Ingo

1
@Ingo, et pourquoi auriez-vous besoin de le faire dans un lexer séparé? Il suffit de prendre en compte ces terminaux.
SK-logic

1
@ SK-logic: Je ne suis pas sûr de comprendre votre question. Pourquoi un lexer séparé peut être un bon choix, j'ai essayé de le prouver dans mon post.
Ingo

Giorgio, non. La pile est un élément crucial d'un analyseur de style LALR normal. Faire lexing avec un analyseur est un gaspillage de mémoire affreux (à la fois le stockage statique et alloué dynamiquement) et sera beaucoup plus lent. Le modèle Lexer / Parser est efficace - utilisez-le :)
riwalk

1

L'un des principaux avantages de l'analyse séparée / lex est la représentation intermédiaire - le flux de jetons. Cela peut être traité de différentes manières qui ne seraient pas possibles autrement avec une combinaison lex / parse.

Cela dit, j'ai trouvé qu'un bon décent récursif peut être moins compliqué et plus facile à travailler qu'avec l'apprentissage d'un générateur d'analyseur, et avoir à trouver comment exprimer la faiblesse du grammeur dans les règles du générateur d'analyseur.


Pourriez-vous expliquer davantage les grammaires qui s'expriment plus facilement sur un flux préfabriqué puis exécutées au moment de l'analyse? Je n'ai qu'une expérience dans la mise en œuvre de langages jouets et de quelques formats de données, donc j'ai peut-être raté quelque chose. Avez-vous remarqué des caractéristiques de performance entre vos combos RD parser / lex roulés à la main et les générateurs alimentés par BNF (je suppose)?
Eli Frey
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.