cerca
Sintassi Bison - YACC
modifica cronologia stampa login logout

Wiki

UniCrema


Materie per semestre

Materie per anno

Materie per laurea


Help

Sintassi Bison - YACC

Torna alla pagina di Traduttori

Sintassi Bison - YACC

La sintassi per segnare commenti etc. è simile a quella di flex:

 definizioni
 
 regole
 
 subroutine

Qui, avremo regole diverse e sub obbligatorie diverse:

  • main()
  • yyerror()

Al solito, o le definiamo noi, oppure linkiamo con -ly, anche se qui in laboratorio non si riesce.

Regole

L'idea è simile: si scrive la regola e poi segue l'azione.

Ad esempio, vogliamo scrivere le regole di una grammatica così:

 A-> αBγ | δμγ

Si traduce in questo modo:

 A: α B γ { action }
    | δ μ γ
    ;

Generare un parser

Non è obbligatorio scrivere un programma da Flex-are per poter poi usarlo col parser: sono due cose indipendenti, anche se poi sarà più comodo usare uno scanner generato da flex piuttosto che scriverlo noi.

In input ho:

 bim bum bam

e tradurlo in

 qui quo qua

La prima sezione è scritta così:

 %start frase

Il simbolo % viene usato per dare comandi a Bison, e in questo caso il comando%start dice qual'è il simbolo iniziale della grammatica. Non è necessario scriverlo: Bison sceglie automaticamente la prima regola.

Vogliamo riconoscere solo bim bum bam e NON bam bum bim o altre combinazioni. Certo, banalmente posso fare un riconoscitore che riconosca direttamente la stringa (frase : bim bum bam), ma sarebbe stata una regola lessicale e non sintattica. Invece noi vogliamo usare i token.

La frase ha quindi un inizio ed una fine, separati da uno spazio vuoto. Andando avanti, definisco come sono fatti sti token.

 
 frase    : inizio ' ' ultimo {printf("\nAssioma riconosciuto:
                                         la frase fa parte del linguaggio\n");}

 inizio   : primo ' ' medio
          ;

 primo    : bim
          ;

 medio    : bum
          ;

 ultimo   : bam
          ;

 bim      : 'b' 'i' 'm' {printf("qui");}

 bum      : 'b' 'u' 'm' {printf("quo");}

 bam      : 'b' 'a' 'm' {printf("qua");}

 

 int yylex()
 {
   char ch = getchar();
   return (int) ch;
 }

 int yyerror (char*s)
 {
   printf("\n %s \n", s);
   return 1;
 }

 int main()
 {
   yyparse();
   return 0;
 }

Le ultime funzioni serve per far sì che il programma funzioni anche senza tirare in ballo i files generati da Flex. yylex viene chiamata automaticamente da yyparse, la quale è chiamata automaticamente dalle librerie di Bison. La yyerror server per quando viene trovato un errore sintattico. Le viene passato il carattere reo.

Per compilare:

 bison file.y

Poi si compila con gcc.

Quando viene eseguito il parser, prima riconosce (o no) il tutto, e alla fine te lo dice.

Eserczio

Vogliamo che riconosca anche stringhe con più spazi tra una parola e l'altra. Senza usare la sintassi di Flex, scrivo una regola ricorsiva che possa riconoscere più spazi.

 frase : inizio spazio ultimo
 ...
 spazi : ' '
       | spazi ' '

Ocio: qui ho definito il token spazi. Se per caso in frase non avessi scritto quello che ho scritto sopra, ma avessi scritto questo:

 frase : inizio ' ' ultimo

avrei avuto la regola spazi irraggiungibile. Bison però è un bravo ragazzo: compilando, mi direbbe che c'è un non terminale inutile, e due regole inutili (infatti spazi ha una | in mezzo).

Se poi a Bison do l'opzione -v, diventa verboso e mi dice tutto. Crea un file con estensione .output che contiene tutto quello che mi serve, compreso l'automa che ha generato.

Per evitare di scrivere i simboli terminali nelle regole, li posso far leggere al mio scanner. In questo caso, si tratta della funzione yylex.
Ad esempio, una regola sarebbe:

 primo : BIM

con BIM definito nelle dichiarazioni:

 %token BIM BUM BAM

Nella yylex, avrei cose come:

 int yylex()
 {
  char str[255];
  int t;
  t = scanf("%s", str);

  if (t != 1) return 0;

  if (strcmp(str, "bim") == 0)
    return BIM

  else if (strcmp(str, "bum") == 0)
    return BUM

  else if (strcmp(str, "bam") == 0)
    return BAM
 }

La dichiarazione %token crea dei #define automatici, che associano dei numeri a BIM, BUM e BAM, e li mette in un .h.

Per compilare, stavolta utilizzo l'opzione -d. Quando compilo con gcc, viene usato anche il .h in cui i token sono stati definiti.

Combinare Flex e Bison

Nel file di Flex, devo semplicemente mettere, nelle dichiarazioni, l'#include necessario:

 %{
 #include "parserblabla.tab.h"
 }%
 
 "bim"   { return BIM; }
 
 int yywrap()
 {
   return 1;
 }

Ovviamente, il main dei due files deve essere unico, e sarà quello del parser. Infatti è il parser che chiama il lexer, e non viceversa. Inoltre, nel file di Bison non occorre più mettere la yylex, perché viene generata automaticamente da Flex.

Per riconoscere spazi multipli, adesso posso ricorrere a Flex. Flex di default quando trova un match lo stampa nell'output. Ma posso scrivere una regola così:

 [ ]+ ;

che vuol dire che non ritorno nulla a Bison.

Riconoscere un linguaggio

Vogliamo riconoscere un linguaggio semplice, con poche parole chiave, che rappresenta il controller di un termostato. Accetta solo ste robe:

 heat on
 heat off
 target temperature value

dove valure è il valore numerico della temperatura.

Distinguiamo regole lessicali da regole sintattiche, usando Flex e Bison e combinandoli.

Regole lessicali

Le parole heat, on, off, target, temperature, numero sono gli unici lessemi validi. L'analisi lessicale dovrà riconoscere solo queste parole.

Per questo scopo, utilizzo Flex. Ogni volta che trovo una parola valida, devo ritornare un token.

 {%
 #include "header di bison.h"
 %}
 

 [0-9]+      return NUMBER;
 heat        return TOKHEAT;
 on|off      return STATE;
 target      return TOKTARGET;
 temperature return TOKTEMPERATURE;
 \n       ;
 [ \t]+   ;

 
 int yywrap()
 {
   return 1;
 }

Regole sintattiche

Si occupa di come le robe lette siano ben combinate secondo le regole sintattiche del mio linguaggio.

 %{
 #include "stdio.h"
 #include "string.h"
 }%

 %token NUMBER TOKHEAT TOKSTATE TOKTARGET TOKTEMPERATURE
 

 commands : /* vuota! */
          | commands command
          ;

 command  : heat_switch
          | target_set
          ;

 heat_switch : TOKHEAT STATE { printf("Heat turned on or off\n"); }
          ;

 target_set : TOKTARGET TOKTEMPERATURE NUMBER
            {
               printf("Temperature set\n");
            }
            ;

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

 int main()
 {
    yyparse();
    return 0;
 }

Compilare il tutto

Do in input a Flex il suo .l. A Bison do il .y, aggiungendo l'opzione -d così che mi generi il .h necessario. Poi compilo con gcc:

 gcc termostato.tab.c lex.yy.c -o termostato.exe

Esercizio

Se scrivo heat on, voglio che mi dica "hai ACCESO", se scrivo heat off deve dirmi "hai SMORSATO" Devo avere come minimo due token, TOKON e TOKOFF, e quindi modificare l'analizzatore lessicale, e poi anche quello sintattico. In Flex avrò:

 on return STATEON;
 off return STATEOFF;

e sintatticamente avrò:

 % token ... TOKON TOKOFF ...

 
 ...
 heat_switch : TOKHEAT TOKON { ... } | TOKHEAT TOKOFF { ... };

Ma c'è un'altra via: alla fine, al parser non interessa sapere l'esatta sequenza di comandi, ma solo l'istruzione "accendi" o "spegni". Posso sfruttare il fatto che Flex e Bison possono comunicare tramite la variabile yylval.

In Flex: on|off yylval = strcmp(yytext, "off"); return STATE;

Infatti, la strcmp ritorna 0 se le stringhe sono uguali.

In Bison:

 heat_switch : TOKHEAT STATE 
             {
               if ($2) printf ("Heat ON\n");
               else printf("Heat OFF\n");
             };

Perché $2? Perché il valore che mi interessa è quello che viene ritornato dal SECONDO token, che è appunto STATE.

Allo stesso modo, posso recuperare, nel parser, la temperatura:

 target_set : TOKTARGET TOKTEMPERATURE NUMBER
            {

               printf("Impostata la temperatura a %d\n", $3):
            };

avendo avuto cura, in Flex, di avere una regola così:

 [0-9]+   { return atoi(yytext); }

che appunto converte la variabile yytext, che ricordiamo essere la stringa matchata, in numero.

Attenzione la notazione $i conta, nella parte destra della regola, sia i terminali che i non terminali! Ad esempio:

 (TOKEN)

vuol dire:

  1. $1 = (
  2. $2 = TOKEN
  3. $3 = )

Esercizio

 Frase = Soggetto  Verbo  Complemento
 Soggetto = articolo [nome comune | nome proprio]

Torna alla pagina di Traduttori