Forum Programmation.autre YACC: retour-chariot comme séparateur

Posté par  .
Étiquettes : aucune
0
2
mai
2008
Hello everybody

J'écris un micro-langage de commande et je cherche vainement un exemple de grammaire Yacc qui gère automatiquement le retour-chariot comme séparateur d'instructions. Parce que si on ne fait rien (on laisse le lexer manger les retours-chariot), toutes les instructions peuvent être mises bout à bout, sur la même ligne. Ce que je ne souhaite pas. De plus je ne veux pas rendre le retour-chariot final obligatoire.

La seule solution que j'entrevois, c'est de gérer un booléen dans l'analyseur lexical qui indique un début de ligne logique : c'est-à-dire qu'après un retour-chariot, je n'ai rien rencontré que des espaces ou tabulations. Mais ce pose le problème de la première instruction placée sur la première ligne.

Voila, voila :-)
  • # Séparateur ?

    Posté par  . Évalué à 4.

    Je ne vois pas ou est la difficulté. Mais je n'ai peut-être pas bien saisi le problème.
    Le lexer intercepte le retour chariot seulement s'il n'a pas été intercepté avant, c'est-à-dire dans tes propres règles. Il peut l'être explicitement, ou via un ".", par exemple.

    Le retour à la ligne peut être spécifié à Lex facilement en utilisant \n. Dès lors, tu mets ce retour et ton caractère régulier (comme ";") de séparation d'instruction dans une même expression, tu retournes un token quand tu la vérifies, et tu laisse le lexer ignorer les blancs de façon silencieuse. Donc :

    Dans Lex :


    [A-Za-z0-9]+ return MOT;
    [;\n] return SEP;
    [ \t]+ /* Ignore les blancs */ ;

    . printf ("Erreur. Caractère non reconnu.\n");


    Et dans Yacc


    instruction: phrase SEP { Execute($1); }
    | phrase { Execute ($1); }
    | SEP /* Ne fais rien de particulier si tu trouves un séparateur isolé */

    phrase: MOT phrase
    | MOT


    De cette façon, les blancs ne remontent jamais jusqu'au niveau grammatical.

    On remarque que j'aurais pu coller un "+" au bout de la regexp de SEP, mais ce n'est pas forcément souhaitable d'un point de vue sémantique. Un blanc peut être long de plusieurs caractères, mais chaque séparateur doit être reconnu quand même.
    • [^] # Re: Séparateur ?

      Posté par  . Évalué à 3.

      D'ailleurs, en l'état, la première clause "phrase SEP" est inutile ...
    • [^] # Re: Séparateur ?

      Posté par  . Évalué à 2.

      En fait le problème qu'il y a à gérer le séparateur dans la grammaire, c'est que ça l'alourdit. De plus il faut distinguer la notion de séparateur et de terminateur. Et puis il faut accepter de façon transparente les lignes vides en début de script...
      Pour les cas simples, la technique que tu décris fonctionne mais j'ai des difficultés lorsque je veux prendre en compte les répétitions de non-terminaux.

      Bon, en enlevant le superflu, ça donne ceci :

      script
      : separator resize_list
      | separator when_list
      | separator resize_list '\n' when_list
      ;
      when_list
      : when_expr
      | when_list '\n' when_expr
      ;
      when_expr
      : WHEN cond_list '\n' resize_list
      ;
      cond_list
      : cond_expr
      | cond_list AND cond_expr
      ;
      resize_list
      : resize_expr
      | resize_list '\n' resize_expr
      ;

      Il refuse d'interpréter plus d'une fois une when_list. Grrr.

      Quant à l'analyseur lexical, j'ai trouvé une méthode pour forcer la reconnaissance d'un lexème en début de ligne (avec ou sans espaces) en utilisant une start condition.
      Mais il me pourrit la vie avec les commentaires qui ne sont pas reconnus comme fin de ligne, malgré ça :

      %{
      #include <stdio.h>
      #include <string.h>
      #include "y.tab.h"
      %}
      %s tol
      %%
      ^[ \t]+ BEGIN(tol);
      "resize" { BEGIN(0); return RESIZE; }
      "when" { BEGIN(0); return WHEN; }

      ^"resize" return RESIZE;
      ^"when" return WHEN;

      "and" return AND;
      "to" return TO;

      [,()=] return *yytext;

      [0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
      [a-z]+ { yylval.type = strdup(yytext); return TYPE; }
      \"[^\"]*["\n] { yylval.string = strdup(yytext); return STRING; }
      '[^']*['\n] { yylval.string = strdup(yytext); return STRING; }

      "//".* ; /* comments */
      [ \t]+ ; /* spaces */
      [\n]+ return '\n';

      . fprintf(stderr, "invalid character: '%c'\n", *yytext);
      %%

      To be continued.

      Merci pour tes remarques.
      • [^] # Re: Séparateur ?

        Posté par  . Évalué à 2.

        Alors à vue de nez :

        Ton lexon « commentaires » ne spécifie absolument pas qu'il est censé s'arrêter en bout de ligne. De cette façon, il pourrait bouffer tout ton fichier. Essaie de mettre un $ après "//".* ...

        Je te déconseille d'utiliser return '\n' puisque tu es censé renvoyer un token défini, codé par un entier. Ton '\n' pourrait en fait passer pour le dixième lexon défini et là, pour retrouver le bug ...

        En fait le problème qu'il y a à gérer le séparateur dans la grammaire, c'est que ça l'alourdit.

        Non, pas si c'est bien géré. En rédigeant une grammaire propre, tu peux presque n'avoir à le spécifier qu'une seule fois.

        De plus il faut distinguer la notion de séparateur et de terminateur.

        En fait, non, justement. Et cela à cause du fait que la lecture de ton source est purement linéaire. On ne revient pas sur ce qui a été lu. En réalité, ton problème est de reconnaître sans ambigüité les cas où tes instructions sont proprement clôturées. Et elles peuvent l'être par trois choses différentes : Un caractère donné (par exemple, le point-virgule), un retour à la ligne, ou la fin du fichier.

        C'est donc cette dernière qu'il faut reconnaître au niveau lexical et Lex te propose un mot-clé spécial pour cela : <<EOF>>

        Néanmoins, le séparateur-terminateur est bel est bien un élément grammatical, ne serait-ce que parce qu'il est principalement contextuel , mais également parce qu'il peut prendre plusieurs formes.

        Dès lors, ta grammaire est très simple à exprimer. une expression correcte est une « instruction dûement et proprement terminée », qui donc s'écrit :

        %token TERM

        expression: instruction TERM { Execute ($1); }


        avec TERM :

        [;\n] return TERM;
        <<EOF>> return TERM;


        Enfin, pour que plusieurs séparateurs puissent se succéder, qu'ils s'agisse de ; ou de retours à la ligne, il faut faire l'hypothèse que l'on fait tous implicitement quand on regarde du C ou autre langage qui le permette : le séparateur clôt une instruction vide. La grammaire devient alors :

        expression
        : TERM { /* non-opération */ }
        | instruction TERM { Execute ($1); }




        J'ajoute que ta grammaire serait ambigüe si le WHEN était optionnel et postérieur à ton RESIZE. Par contre, si c'est en préfixe, la clause RESIZE qui suit est obligatoire et lève l'ambigüité. Mais il faut toujours écrire d'une part la grammaire d'une instruction propre et d'autre part, celle qui définit comment ces instructions peuvent s'enchaîner dans une script. Ce n'est pas le cas de la tienne actuellement : ton script: embarque en vrac toutes sortes de définitions, sans les lier forcément entre elles. Et ceci pourrait te causer des problèmes pour les interpréter après les avoir reconnues.
        • [^] # Re: Séparateur ?

          Posté par  . Évalué à 2.

          J'ai trouvé mon problème. Il ne fallait pas retourner les fins de ligne. Donc je les consomme silencieusement (comme des commentaires et des espaces en fait). Le WE et tes remarques ont porté conseil :-)

          Pour le terminateur, je ne peux pas l'utiliser car le parser doit compiler et tourner sous AIX, et la version de Lex disponible ne comprend pas <<EOF>> (il semble même que ce soit spécifique à Flex). Mais en forçant la reconnaissance des RESIZE et WHEN en début de ligne, je m'en sors très bien. Même si la syntaxe autorise de découper les instructions en morceaux, c'est un moindre mal (d'ailleurs je vois pas comment l'empêcher).

          Effectivement, les WHEN doivent être suivis d'un ou plusieurs RESIZE, donc il n'y a pas d'ambiguïté je pense.

          Pour les commentaires, il n'est pas nécessaire d'ajouter $ à la fin de la regex car le point ne matche pas la fin de ligne.

          Quant au return '\n', ça ne doit pas poser de problème car les tokens définis par Bison/Yacc commencent à 256 ou 257, justement pour pouvoir retourner des caractères individuels. Mais du coup ça ne doit pas être compatible avec l'UTF-8.

          Merci beaucoup pour tes commentaires.
          Sous MVS, j'ai développé mes lexers et parsers en COBOL (!), mais comme c'est pour Unix, je voulais réviser Lex et Yacc :-)
          • [^] # Re: Séparateur ?

            Posté par  . Évalué à 1.

            Pour la postérité :-), la start condition ne sert plus à rien.
            Les règles suivantes fonctionnent très bien :
            ^[ \t]*"resize" return RESIZE;
            ^[ \t]*"when" return WHEN;

            Et puis, il ne faut pas fusionner les règles
            [ \t]+ ;
            [\n]+ ;

            sinon ça flingue le code ci-dessus.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.