Rédaction d'un compilateur Compilateur - Aperçu sur l'utilisation et les fonctionnalités


10

Cela fait partie d'une série de questions qui se concentre sur le projet frère du projet Abstraction, qui vise à résumer les concepts utilisés dans la conception du langage sous la forme d'un cadre. Le projet sœur s'appelle OILexer, qui vise à construire un analyseur à partir de fichiers de grammaire, sans utiliser d'injection de code sur les correspondances.

Quelques autres pages associées à ces questions, liées au typage structurel, peuvent être consultées ici , et la facilité d'utilisation, ici . Le méta-sujet associé à une enquête sur le cadre et le bon endroit pour publier peut être trouvé ici .

J'arrive au point où je suis sur le point de commencer à extraire l'arbre d'analyse d'une grammaire donnée, suivi d'un analyseur de descente récursive qui utilise DFA pour discerner les chemins avant (similaire à LL (*) d'ANTLR 4, donc je pensé que je l'ouvrirais pour avoir un aperçu.

Dans un compilateur d'analyseur, quels types de fonctionnalités sont idéales?

Jusqu'à présent, voici un bref aperçu de ce qui est mis en œuvre:

  1. Modèles
  2. Anticipez la prévision, sachant ce qui est valable à un moment donné.
  3. Règle de «délitéralisation» prenant les littéraux dans les règles et déterminant de quel jeton ils proviennent.
  4. Automates non déterministes
  5. Automates déterministes
  6. Machine d'état lexicale simple pour la reconnaissance des jetons
  7. Méthodes d'automatisation des jetons:
    • Scan - utile pour les commentaires: Commentaire: = "/ *" Scan ("* /");
    • Soustraire - Utile pour les identifiants: Identifier: = Soustraire (IdentifierBody, Keywords);
      • Garantit que l'identifiant n'accepte pas les mots clés.
    • Encode - Encode une automatisation sous la forme d'un décompte série X des transitions N de base.
      • UnicodeEscape: = "\\ u" BaseEncode (IdentifierCharNoEscape, 16, 4);
        • Fait un échappement unicode en hexadécimal, avec 4 transitions hexadécimales. La différence entre ceci et: [0-9A-Fa-f] {4} est que l'automatisation résultante avec Encode limite l'ensemble autorisé de valeurs hexadécimales à la portée d'IdentifierCharNoEscape. Donc, si vous lui donnez \ u005c, la version d'encodage n'acceptera pas la valeur. Des choses comme celle-ci ont une sérieuse mise en garde: utilisez avec parcimonie. L'automatisation qui en résulte pourrait être assez complexe.

Ce qui n'est pas implémenté est la génération CST, j'ai besoin d'ajuster les automatisations déterministes pour conserver le contexte approprié pour que cela fonctionne.

Pour toute personne intéressée, j'ai téléchargé une jolie copie de la forme originale du projet T * y♯ . Chaque fichier devrait être lié à un autre, j'ai commencé à lier les règles individuelles pour les suivre, mais cela aurait pris beaucoup trop de temps (cela aurait été plus simple à automatiser!)

Si plus de contexte est nécessaire, veuillez poster en conséquence.

Edit 5-14-2013 : J'ai écrit du code pour créer des graphiques GraphViz pour les machines à états dans une langue donnée. Voici un digraphe GraphViz du AssemblyPart . Les membres liés dans la description de la langue doivent avoir un rulename.txt dans leur dossier relatif avec le digraphe de cette règle. Une partie de la description de la langue a changé depuis que j'ai posté l'exemple, cela est dû à la simplification des choses sur la grammaire. Voici une image graphique intéressante .


8
Mur de texte. Ne prenez pas cela mal, j'apprécie un problème bien expliqué. Dans ce cas, c'est tout simplement un peu trop verbeux. D'après ce que j'ai rassemblé, vous demandez quelles fonctionnalités devraient être incluses dans un analyseur de grammaire ou comment en créer une sans recommencer à zéro? Veuillez modifier pour répondre aux questions suivantes (vous n'avez pas à réécrire, ajoutez simplement à la fin en résumé): Quel est votre problème? À quelles contraintes êtes-vous lié dans les solutions possibles à votre problème (ça doit être rapide, ça doit être LL *, etc.)?
Neil

1
Je demande un aperçu de l'ensemble des fonctionnalités. L'accent est mis sur la facilité d'utilisation. La difficulté consiste à obtenir quelqu'un qui ne connaît pas le projet, un aperçu du projet afin qu'ils soient informés de son orientation. Je ne demande pas «comment faire», je pose des questions sur la convivialité. Des suggestions sur la façon de rogner la question sont appréciées.
Allen Clark Copeland Jr

1
Pour moi, la nature du projet n'est pas évidente. Par exemple, depuis l'époque de yacc, nous avons vu beaucoup de générateurs d'analyseurs. Qu'est-ce qui est différent dans votre OILexer? Quel est le nouveau?
Ingo

1
Le but de ce projet est de simplifier la génération d'analyseurs. Oui similaire à YACC / Bison et FLEX / LEX. La principale différence est d'éviter la complexité de ces programmes. Garder les choses simples et au point est l'objectif principal. C'est pourquoi j'ai créé un format qui est dépourvu de sectionnement étrange, mais plutôt le but est de le rendre similaire à la programmation régulière: uniquement spécifique au développement du langage. Les jetons sont définis en utilisant ': =' après leur nom, les règles sont définies en utilisant :: = après leur nom. Les modèles utilisent '<' et '>' pour leurs arguments suivis de ":: =" car ils partagent la syntaxe des règles.
Allen Clark Copeland Jr

3
Cette concentration infernale sur l'analyse semble mal placée; c'est un problème bien résolu, et il peine à peine ce dont vous avez besoin pour traiter les langages de programmation. Google pour mon essai sur "la vie après l'analyse".
Ira Baxter

Réponses:


5

Ceci est une excellente question.

J'ai travaillé sur beaucoup d'analyses récemment, et à mon humble avis, certaines des fonctionnalités clés sont:

  • une API de programmation - afin qu'elle puisse être utilisée à partir d'un langage de programmation, idéalement en important simplement une bibliothèque. Il peut également avoir une interface GUI ou BNF, mais la programmation est la clé, car vous pouvez réutiliser vos outils, IDE, analyse statique, tests, installations d'abstraction de langage, familiarité avec les programmeurs, générateur de documentation, processus de construction, etc. De plus, vous pouvez jouer de manière interactive avec de minuscules analyseurs, ce qui réduit considérablement la courbe d'apprentissage. Ces raisons le placent en haut de ma liste des "fonctionnalités importantes".

  • rapport d'erreurs, comme l'a mentionné @guysherman. Lorsqu'une erreur est trouvée, je veux savoir où était l'erreur et ce qui se passait quand elle s'est produite. Malheureusement, je n'ai pas pu trouver de bonnes ressources pour expliquer comment générer des erreurs décentes lorsque le retour en arrière entre en jeu. (Bien que notez le commentaire de Sk-logic ci-dessous).

  • résultats partiels. Lorsque l'analyse échoue, je veux pouvoir voir ce qui a été analysé avec succès à partir de la partie de l'entrée qui se trouvait avant l'emplacement de l'erreur.

  • abstraction. Vous ne pouvez jamais créer suffisamment de fonctions, et l'utilisateur en aura toujours besoin de plus, donc essayer de comprendre toutes les fonctions possibles à l'avance est voué à l'échec. Est-ce ce que vous entendez par modèles?

  • Je suis d'accord avec votre n ° 2 (prédiction prospective). Je pense que cela aide à générer de bons rapports d'erreur. Est-ce utile pour autre chose?

  • prise en charge de la construction d'un arbre d'analyse au fur et à mesure de l'analyse, peut-être:

    • un arbre de syntaxe concret, pour lequel la structure de l'arbre correspond directement à la grammaire et comprend des informations de mise en page pour le rapport d'erreurs des phases ultérieures. Dans ce cas, le client ne devrait pas avoir à faire quoi que ce soit pour obtenir la structure de l' arbre droit - il devrait dépendre directement de la grammaire.
    • un arbre de syntaxe abstraite. Dans ce cas, l'utilisateur peut jouer avec tous les arbres d'analyse
  • une sorte de journalisation. Je ne suis pas sûr de celui-ci; peut-être pour afficher une trace des règles que l'analyseur a essayées, ou pour garder une trace des jetons indésirables tels que les espaces ou les commentaires, au cas où (par exemple) vous souhaitez utiliser les jetons pour générer de la documentation HTML.

  • capacité à gérer des langages contextuels. Je ne sais pas non plus à quel point celui-ci est important - en pratique, il semble plus propre d'analyser un sur-ensemble d'une langue avec une grammaire sans contexte, puis de vérifier les contraintes contextuelles dans des passages ultérieurs supplémentaires.

  • messages d'erreur personnalisés, afin que je puisse régler les rapports d'erreur dans des situations spécifiques et peut-être plus rapidement comprendre et résoudre les problèmes.

D'un autre côté, je ne trouve pas la correction d'erreurs particulièrement importante - bien que je ne sois pas au courant des progrès actuels. Les problèmes que j'ai remarqués sont que les corrections potentielles fournies par les outils sont: 1) trop nombreuses et 2) ne correspondent pas aux erreurs réelles commises et ne sont donc pas très utiles. Espérons que cette situation s'améliorera (ou peut-être l'a déjà fait).


J'ai édité le corps de la question pour inclure un lien vers le PrecedenceHelper dans la puce qui lit «Modèles». Il permet des tuples de tableau de paramètres, donc si vous avez quatre paramètres, chaque tableau de paramètres, le modèle doit être utilisé dans des ensembles d'arguments de quatre.
Allen Clark Copeland Jr

La principale raison pour laquelle vous construisez le CST est la disposition générale du fichier analysé. Si vous vouliez imprimer le document de façon jolie , votre meilleur pari est d'utiliser un CST parce que les AST comme leur nom l'indique manquent d'informations pour gérer l'espacement impair qu'un CST capturerait. Transformer un CST est généralement assez facile s'il s'agit d'un bon CST.
Allen Clark Copeland Jr

J'ai à nouveau édité la question sur le sujet des fonctions intégrées disponibles.
Allen Clark Copeland Jr

Je pense que je n'ai pas fait un excellent travail en exprimant mon point de vue sur les modèles / fonctions: je voulais dire que parce que vous n'en avez jamais assez, un système ne devrait pas essayer de les comprendre à l'avance: l'utilisateur doit pouvoir créer son propre.

1
J'ai trouvé une approche particulièrement utile pour le rapport d'erreurs avec retour arrière infini (Packrat): chaque règle de production est annotée avec des messages d'erreur (libellés comme "bla-bla-bla attendus"), et en cas d'échec, ce message est stocké dans le flux de la même manière comme jetons mémorisés. Si tous les échecs ne sont pas récupérables (analyse terminée avant d'atteindre la fin du flux), le message d'erreur le plus à droite (ou une collection de ces messages) est le plus approprié. C'est la chose la plus simple à faire, bien sûr, il existe des moyens de l'affiner davantage avec plus d'annotations.
SK-logic

5

Je n'ai pas d'expérience en conception de langage, mais j'ai déjà essayé d'écrire un analyseur, quand je créais et IDE pour un moteur de jeu.

À mon avis, ce qui est important pour vos utilisateurs finaux ultimes, ce sont les messages d'erreur qui ont du sens. Ce n'est pas un point particulièrement bouleversant, je sais, mais en le suivant à l'envers, l'une des principales implications de cela est que vous devez être en mesure d'éviter les faux positifs. D'où viennent les faux positifs? Ils proviennent de l'analyseur tombant à la première erreur et ne se remettant jamais tout à fait. C / C ++ est connu pour cela (bien que les nouveaux compilateurs soient un peu plus intelligents).

Alors, de quoi avez-vous besoin à la place? Je pense que plutôt que de simplement savoir ce qui est / n'est pas valide à un moment donné, vous devez savoir comment prendre ce qui n'est pas valide et apporter une modification minimale pour le rendre valide - afin que vous puissiez continuer l'analyse sans générer de fausses erreurs concernant à votre descente récursive se confondre. Être capable de construire un analyseur qui peut générer ces informations vous donne non seulement un analyseur très robuste, mais il ouvre des fonctionnalités fantastiques pour le logiciel qui utilise l'analyseur.

Je me rends compte que je suggère peut-être quelque chose de vraiment difficile, ou stupidement évident, désolé si c'est le cas. Si ce n'est pas le genre de chose que vous recherchez, je supprimerai volontiers ma réponse.


C'est une chose que je compte faire. Pour m'aider dans ma connaissance du domaine, un de mes amis a suggéré d'écrire un véritable analyseur à la main pour obtenir le coup de l'automatisation. Une chose que j'ai réalisée assez rapidement: les analyseurs sont complexes et il y a des choses que nous faisons à la main qui simplifient le processus. Les règles et les modèles partagent la même syntaxe; cependant, il y a des éléments de langage qui sont valides dans les modèles mais pas les règles, il y a des états internes qui gèrent cette tâche. Ce qui a fait penser à une idée: les règles devraient pouvoir spécifier des aides de chemin pour faciliter le partage des sous-règles.
Allen Clark Copeland Jr

... cela devrait être assez simple à transférer dans l'automatisation, mais exigerait que les automatisations aient des conditions basées sur l'état. Je vais y travailler et je vous répondrai. ANTLR utilise des automatisations à états finis pour gérer les cycles de dire: "T" *, où comme je vais l'utiliser pour gérer la plupart du processus d'analyse car les réductions devraient être plus propres en tant qu'états lorsqu'il y a plus de 800 variations dans une règle (cela gonflerait rapidement sous forme de code spaghetti sous la forme standard if / else.)
Allen Clark Copeland Jr

0

La grammaire ne doit pas avoir de restrictions comme "ne pas laisser de règles récursives". Il est ridicule que les outils largement utilisés aujourd'hui aient cela et ne puissent comprendre que les grammaires de succion LL - près de 50 ans après que yacc l' ait fait correctement.

Un exemple de récursivité droite (en utilisant la syntaxe yacc):

list: 
      elem                  { $$ = singleton($1); }
    | elem ',' list         { $$ = cons($1, $2);  }
    ;

Un exemple pour la récursion gauche (en utilisant le synatx yacc):

funapp:
    term                    { $$ = $1; }
    | funapp term           { $$ = application($1, $2); }
    ;

Maintenant, cela pourrait peut-être "refactoriser" quelque chose d'autre, mais dans les deux cas, le type spécifique de récursivité est juste la "bonne" façon d'écrire ceci, car (dans l'exemple de langage) les listes sont récursives à droite tandis que l'application de fonction est récursive à gauche .

On peut s'attendre à ce que les outils avancés prennent en charge la manière naturelle d'écrire des trucs, au lieu d'exiger que l'on "refactorise" tout pour qu'il soit récursif gauche / droite sous la forme imposée par l'outil.


Oui, c'est bien de ne pas avoir de restrictions arbitraires comme ça. Cependant, quel est un exemple de récursion à gauche qui ne peut pas être remplacé par un opérateur de répétition (tel que des expressions régulières *ou des +quantificateurs)? J'admets librement que mes connaissances dans ce domaine sont limitées, mais je n'ai jamais rencontré une utilisation de la récursion à gauche qui ne pouvait pas être refactorisée en répétition. Et j'ai aussi trouvé la version de répétition plus claire (bien que ce soit juste une préférence personnelle).

1
@MattFenwick Voir ma modification. Notez comment la syntaxe mène directement à des actions sémantiques simples et naturelles (par exemple) pour créer un arbre de syntaxe. Avec la répétition, (qui n'est pas disponible en yacc, btw), je suppose que vous devez souvent vérifier si vous avez une liste vide, un singleton, etc.
Ingo

Merci pour votre réponse. Je pense que je comprends mieux maintenant - je préférerais écrire ces exemples au fur list = sepBy1(',', elem)et à mesure funapp = term{+}(et bien sûr sepBy1et +serait implémenté en termes de récursion gauche / droite, et produire des arbres de syntaxe standard). Ce n'est donc pas que je pense que la récursion gauche et droite sont mauvaises, c'est juste que je pense qu'elles sont de bas niveau et que j'aimerais utiliser une abstraction de niveau supérieur si possible pour rendre les choses plus claires. Merci encore!

1
Vous êtes les bienvenus @MattFenwick. Mais alors c'est peut-être une question de goût. Pour moi, la récursivité est (du moins dans le contexte des langages, qui sont tous intrinsèquement récursifs ou totalement inintéressants) la façon la plus naturelle d'y penser. En outre, un arbre est une structure de données récursive, donc je ne vois pas besoin de se replier à l' itération de récursion Simuler. Mais, bien sûr, les préférences sont différentes.
Ingo
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.