Forum Programmation.c Parser une chaine de caractère en C avec Flex/Bison

Posté par  (site web personnel) .
Étiquettes : aucune
0
28
avr.
2009
Bonjour,

je débute avec Flex/Bison.
J'ai utilisé le tuto sur Lex/Yacc suivant
http://ds9a.nl/lex-yacc/cvs/lex-yacc-howto.html
Mon programme connait désormais le langage suivant
heat on
-> heat is on !
heat off
-> heat is off !
get heat
-> heat is off !
set temperature 10
-> temperature set to 10
get temperature
-> temperature is 10

voir le code sur
Accès web
http://svn.berlios.de/viewcvs/openphysic/compilation/2_yacc/(...)
Accès anonyme
svn checkout svn://svn.berlios.de/openphysic/compilation/2_yacc/thermostat3/

Rem : il faudra que j'améliore le programme pour pouvoir définir des températures en nombre à virgule flottante au lieu d'être en entier

Le problème c'est que j'obtiens un programme qui parse les informations envoyées sur stdin... je sais que l'on peut modifier ce comportement
il suffit de redéfinir la variable yyin juste avant d'appeler yyparse
mais moi je ne veux pas parser les informations en provenance d'un fichier...
je veux juste parser les informations stockées dans un char*

Comment procéder ?

Merci d'avance
  • # Bourrin

    Posté par  . Évalué à 1.

    Méthode bourrine (il y a peut-être plus fin, par exemple des fonctions proposées par bison) :
    Créer un pipe(2) (man 2 pipe), passer une extrémité à bison, et dans l'autre, écrire ta chaine de caractères.
    • [^] # Re: Bourrin

      Posté par  (site web personnel) . Évalué à 2.

      Sauf que mon bidule doit tourner à la fin sur un microcontrôleur ATMEL AVR ATmega16...
      donc j'imagine que c'est pas gagné ! (il n'y a pas d'OS...)
  • # YY_INPUT

    Posté par  . Évalué à 1.

    Cette solution est spécifique à flex (il n'y a pas de solution générique pour toutes les implémentations de lex) mais tu peux redéfinir la macro YY_INPUT.

    Tu dois pouvoir faire quelque comme ceci :


    %{
    #under YY_INPUT
    #define YY_INPUT(b,r,ms) (r = my_yyinput(b, ms))
    %}

    int my_yyinput(char* buf, int max_size)
    {
    //on peut copier dans buf au maximum max_size octets et renvoyer le nombre d'octets copiés
    }



    Étienne
    • [^] # Re: YY_INPUT

      Posté par  (site web personnel) . Évalué à 1.

      Je ne suis pas certain d'avoir bien compris...
      peux-tu préciser... on va dire que la chaine à parser est
      stockée dans une variable globale char* g_str;
      • [^] # Re: YY_INPUT

        Posté par  (site web personnel) . Évalué à 1.

      • [^] # Re: YY_INPUT

        Posté par  . Évalué à 1.

        La fonction my_yyinput ressemblerait à (je n'ai pas testé le code) :



        extern char* g_str;
        extern size_t g_str_size; /* taille de g_str */
        extern int g_str_read; /* nombre de caractère déjà envoyés à lex, initializé au départ à 0 */

        int my_yyinput(char* buf, int max_size)
        {
            /* on calcule le nombre de caractères à copier dans buf,
             * soit on a assez de caractères pour remplir, buf, soit on prend le reste de la chaine
             */
            int num = min(max_size, g_str_size-g_str_read);
            

            /* on copie notre chaine dans buf */
            if (num > 0)
            {
                memcpy(buf, g_str+g_str_read, num);
                g_str_read += num;
            }
            return num;
        }


        La macro YY_INPUT est appelée par flex pour reremplir le buffer interne sur lequel il travail. Ce qui permet d'optimiser la lecture en ne lisant pas caractère par caractère comme c'était le cas de lex d'AT&T. Donc ta fonction my_yyinput va être appelée à chaque fois que le buffer interne de flex est vide et tu lui donne la suite de ta chaîne.

        Étienne
      • [^] # Re: YY_INPUT

        Posté par  . Évalué à 1.

        Tu peux regarder les sources ici : http://www.sfr-fresh.com/unix/misc/am-utils-6.2a2.tar.gz:a/a(...)

        En particulier les lignes 90 à 108 et la fonction sun_map_input ligne 205 qui fait exactement ce que tu cherche.

        Étienne
        • [^] # Re: YY_INPUT

          Posté par  (site web personnel) . Évalué à 1.

          Merci... je vais essayer d'intégrer ça

          par contre on ne peux pas simplement avoir une lecture caractère par caractère depuis la chaîne de caractère dans mon cas ça me semble plus adapté. non ?
          • [^] # Re: YY_INPUT

            Posté par  . Évalué à 1.

            Il est sans doute toujours possible de ne remplir qu'un caractère à la fois mais tu perd en performance à cause de la bufferization qui ne se fait plus.

            Étienne
            • [^] # Re: YY_INPUT

              Posté par  (site web personnel) . Évalué à 1.

              Désolé mais ça ne marche pas... il ne se passe rien quand j'exécute !!!

              main.l

              %{
              #include <string.h>
              #include "main.tab.h"

              #undef YY_INPUT
              #define YY_INPUT(b,r,ms) (r = my_yyinput(b, ms))

              extern char* g_str;
              extern size_t g_str_size; /* taille de g_str */
              extern int g_str_read; /* nombre de caractère déjà envoyés à lex, initializé au départ à 0 */

              int my_yyinput(char* buf, int max_size)
              {
              /* on calcule le nombre de caractères à copier dans buf,
              * soit on a assez de caractères pour remplir, buf, soit on prend le reste de la chaine
              */
              int num = fmin(max_size, g_str_size-g_str_read);


              /* on copie notre chaine dans buf */
              if (num > 0)
              {
              memcpy(buf, g_str+g_str_read, num);
              g_str_read += num;
              }
              return num;
              }


              %}

              %option case-insensitive

              INTEGER [0-9]+
              FLOATING [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?

              %%

              {INTEGER} {yylval=atoi(yytext); return TOK_NUMBER;}
              heat return TOK_HEAT;
              on|off {yylval=!strcmp(yytext,"on"); return TOK_STATE;}
              set return TOK_SET;
              temperature return TOK_TEMPERATURE;
              get return TOK_GET;
              \n return TOK_EOL; /* end of line */;
              [ \t]+ /* ignore whitespace */;

              %%


              main.y

              %{
              #include <stdio.h>
              #include <string.h>

              #define STR_SIZE 50
              char g_str[STR_SIZE];
              /*char* g_str;*/
              size_t g_str_size; /* taille de g_str */
              int g_str_read; /* nombre de caractère déjà envoyés à lex, initializé au départ à 0 */

              /*
              #define STR_SIZE 50
              char str[STR_SIZE];

              int myinput(char *buf, int max){
              memcpy(buf, str, STR_SIZE);
              }
              */
              /*
              int n = min(max, gStringLimit-gStringPtr);
              if(n>0){
              memcpy(buf, gStringPtr, n);
              gStringPtr +=n;
              }
              return n;
              }
              */


              int value; /* temperature value */

              enum _state {
              off,
              on
              };
              enum _state state;

              void show_heat_state() {
              if (state==off) {
              printf("\tHeat is off\n");
              } else {
              printf("\tHeat is on\n");
              }
              }

              %}


              %token TOK_NUMBER TOK_HEAT TOK_STATE TOK_SET TOK_TEMPERATURE TOK_GET TOK_EOL

              %%

              commands: /* empty */
              | commands command TOK_EOL
              ;

              command:
              heat_switch
              |
              heat_get
              |
              target_set
              |
              target_get
              ;

              heat_switch:
              TOK_HEAT TOK_STATE
              {
              if ($2==0) {
              state=off;
              } else if ($2==1) {
              state=on;
              }
              show_heat_state();
              }
              ;

              heat_get:
              TOK_GET TOK_HEAT
              {
              show_heat_state();
              }
              ;


              target_set:
              TOK_SET TOK_TEMPERATURE TOK_NUMBER
              {
              value=$3;
              printf("\tTemperature set to %d\n",value);
              }
              ;

              target_get:
              TOK_GET TOK_TEMPERATURE
              {
              printf("\tTemperature is %d\n",value);
              }
              ;
              %%


              void yyerror(const char *str) {
              fprintf(stderr,"yyerror: %s\n",str);
              }

              int yywrap() {
              return 1;
              }



              int main(void) {

              strcpy(g_str,"get temperature");
              /* printf(g_str); */

              value=0;
              state=off;

              yyparse();

              return 0;
              }


              Makefile

              all:
              flex main.l
              bison -d main.y
              gcc lex.yy.c main.tab.c -lfl -o main
              ./main
              • [^] # Re: YY_INPUT

                Posté par  . Évalué à 1.

                Voici un mini programme pour flex qui va simplement afficher le contenu de la chaine de caractère g_str :

                Sinon dans ton programme, j'ai l'impression que tu n'initialise jamais g_str_size et g_str_read, le mieux est sans doute d'avoir une fonction set_parse_string() qui va positionner correctement g_str, g_str_size et g_str_read.



                %{
                #include <string.h>

                #undef YY_INPUT
                #define YY_INPUT(b,r,ms) { r = my_yyinput(b,ms); }

                char g_str[] = "toto tata tutu\n";
                size_t g_str_size = sizeof(g_str);
                int g_str_read = 0;


                int my_yyinput(char* buf, int max_size)
                {
                    int reste = g_str_size-g_str_read;
                    int num = (max_size < reste ? max_size : reste);

                    if (num > 0)
                    {
                        memcpy(buf, g_str+g_str_read, num);
                        g_str_read += num;
                    }
                    return num;
                }
                %}

                %%
                %.|\n    ECHO;
                %%


                Étienne
              • [^] # Re: YY_INPUT

                Posté par  . Évalué à 1.

                En regardant vite fait, il semble que tu as 2 problème :
                - je ne pense pas que tu puisse faire un "extern char*" et déclarer un "char[]", il faut transformer dans main.y ton g_str en char*
                - tu ne calcule pas g_str_size et g_str_read
                - il me semble que ton fichier doit finir par une fin de ligne (cf commands dans main.y), et donc soit tu supprime le TOK_EOL de la fin de commands, soit tu rajoute un \n à la fin de ta chaine de caractères.


                Étienne
                • [^] # Re: YY_INPUT

                  Posté par  (site web personnel) . Évalué à 1.

                  si je mets un char* j'ai un bus error lorsque j'initialise ma chaine de caractère !

                  ok pour le \n .... je n'y avais vraiment pas pensé !!!!!

                  sinon je crois qu'il existe une fonction nommé yy_scan_string...
                  ne peut-elle pas servir à cet usage ?

                  http://flex.sourceforge.net/manual/Multiple-Input-Buffers.ht(...)

                  Function: YY_BUFFER_STATE yy_scan_buffer (char *base, yy_size_t size)

                  which scans in place the buffer starting at base, consisting of size bytes, the last two bytes of which must be YY_END_OF_BUFFER_CHAR (ASCII NUL). These last two bytes are not scanned; thus, scanning consists of base[0] through base[size-2], inclusive.

                  If you fail to set up base in this manner (i.e., forget the final two YY_END_OF_BUFFER_CHAR bytes), then yy_scan_buffer() returns a NULL pointer instead of creating a new input buffer.
                  • [^] # Re: YY_INPUT

                    Posté par  . Évalué à 1.


                    si je mets un char* j'ai un bus error lorsque j'initialise ma chaine de caractère !


                    Il faut quand même allouer ton buffer.

                    Je te propose de faire quelquechose du genre (dans main.y) :


                    char* g_str = NULL;
                    size_t g_str_size;
                    size_t g_str_read;

                    int set_parse_string(char* str)
                    {
                    size_t length = strlen(str);
                    if (g_str != NULL) free(g_str);

                    g_str = (char*) malloc(length+1);
                    if (g_str == NULL) return 0;

                    strcpy(g_str, str);
                    g_str_size = length;
                    g_str_read = 0;

                    return 1;
                    }


                    et dans ton main :

                    int main(void) {
                    value = 0;
                    state = off;

                    /* première suite de commandes à parser (ici il n'y en a qu'une) */
                    set_parse_string("set temperature 12");
                    yyparse();

                    /* deuxième suite de commandes à parser */
                    set_parse_string("get temperature heat");
                    yyparse();

                    return 0;
                    }
                    • [^] # Re: YY_INPUT

                      Posté par  (site web personnel) . Évalué à 1.

                      Merci beaucoup de passer du temps à résoudre mes problèmes !
                      Ah ce fichu malloc/free !!! je sais pourquoi j'avais abandonné le C pendant
                      quelques années pour me mettre à Python ! par contre Python sur un uC 8 bits ça doit pas le faire ;-)
                      • [^] # Re: YY_INPUT

                        Posté par  (site web personnel) . Évalué à 1.

                        ça y est c'est fait !... et ça marche nickel... merci...
                        Il faut juste que je gère le problème des températures en nombre à virgule flottante
                    • [^] # Re: YY_INPUT

                      Posté par  (site web personnel) . Évalué à 2.

                      En fait c'est bien plus simple !!!
                      Il y a juste besoin d'utiliser

                      yy_scan_string("*IDN?\n");
                      yyparse();

                      et il est nullement nécessaire de redéfinir la macro YY_INPUT !!!

                      main.l


                      %{
                      #include <string.h>
                      #include "main.tab.h"
                      %}

                      %option case-insensitive

                      INTEGER [0-9]+
                      FLOATING [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?

                      %%
                      {INTEGER} {yylval=atoi(yytext); return TOK_NUMBER;}
                      "*IDN?" return TOK_IDENT;
                      heat return TOK_HEAT;
                      on|off {yylval=!strcmp(yytext,"on"); return TOK_STATE;}
                      set return TOK_SET;
                      temperature return TOK_TEMPERATURE;
                      get return TOK_GET;
                      \n return TOK_EOL; /* end of line */;
                      [ \t]+ /* ignore whitespace */;

                      %%



                      main.y

                      %{
                      #include <stdio.h>
                      #include <string.h>
                      #include <stdlib.h>

                      int value; /* temperature value */

                      enum _state {
                      off,
                      on
                      };
                      enum _state state;

                      void show_heat_state() {
                      if (state==off) {
                      printf("\tHeat is off\n");
                      } else {
                      printf("\tHeat is on\n");
                      }
                      }


                      /* Si l'on compile avec l'option --verbose et yydebug mis a 1, permet
                      d'obtenir le fichier data.output, qui contient la liste des
                      differents states possibles pour la machine d'etat. */
                      int yydebug=1;

                      %}


                      %token TOK_IDENT TOK_NUMBER TOK_HEAT TOK_STATE TOK_SET TOK_TEMPERATURE TOK_GET TOK_EOL
                      /*
                      %union
                      {
                      double dbl;
                      int integer;
                      }
                      */

                      %%

                      commands: /* empty */
                      | commands command TOK_EOL
                      ;

                      command:
                      identification
                      | heat_switch
                      | heat_get
                      | target_set
                      | target_get
                      ;

                      identification:
                      TOK_IDENT
                      {
                      printf("\tMyDevice\n",value);
                      }
                      ;


                      heat_switch:
                      TOK_HEAT TOK_STATE
                      {
                      if ($2==0) {
                      state=off;
                      } else if ($2==1) {
                      state=on;
                      }
                      show_heat_state();
                      }
                      ;

                      heat_get:
                      TOK_GET TOK_HEAT
                      {
                      show_heat_state();
                      }
                      ;


                      target_set:
                      TOK_SET TOK_TEMPERATURE TOK_NUMBER
                      {
                      value=$3;
                      printf("\tTemperature set to %d\n",value);
                      }
                      ;

                      target_get:
                      TOK_GET TOK_TEMPERATURE
                      {
                      printf("\tTemperature is %d\n",value);
                      }
                      ;
                      %%


                      void yyerror(const char *str) {
                      fprintf(stderr,"yyerror: %s\n",str);
                      }

                      int yywrap() {
                      return 1;
                      }



                      int main(void) {
                      value=0;
                      state=off;

                      /* yyparse(); */ /* parser les commandes envoyées sur stdin */

                      /* suite de commandes à parser */
                      yy_scan_string("*idn?\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("*IDN?\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("heat on\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("get heat\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("heat off\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("get heat\n");
                      yyparse();

                      /* suite de commandes à parser */
                      yy_scan_string("set temperature 21\n");
                      yyparse();

                      /* suite commandes à parser */
                      yy_scan_string("get temperature\n");
                      yyparse();

                      return 0;
                      }



                      Makefile

                      all:
                      flex main.l
                      bison -d --verbose main.y
                      gcc lex.yy.c main.tab.c -lfl -o main
                      ./main

Suivre le flux des commentaires

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