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
Ci sono due cose poco carine:
1) 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".