Gestione degli errori con Flex (lex) e Bison (yacc)
-
07-07-2019 - |
Domanda
Dal manuale Bison:
In un semplice parser di comandi interattivo dove ogni input è una riga, può essere sufficiente per consentire a yyparse di restituisce 1 in caso di errore e dispone del chiamante ignora il resto della riga di input quando ciò accade (e quindi chiama yyparse di nuovo).
Questo è praticamente quello che voglio, ma ho difficoltà a mettermi al lavoro. Fondamentalmente, voglio rilevare ed errore in flex, e se viene rilevato un errore, Bison deve scartare l'intera linea. Quello che ho in questo momento, non funziona abbastanza bene perché i miei comandi vengono ancora eseguiti:
kbsh: ls '/home
Error: Unterminated Single Quote
admin kbrandt tempuser
syntax error
kbsh:
Nel mio file Bison:
commands:
/*Empty*/ { prompt(); } |
command { prompt(); }
;
command:
error {return 1; } |
chdir_command |
pwd_command |
exit_command |
WORD arg_list {
execute_command($1, $2);
//printf("%s, %s\n", $1, $2);
} |
WORD { execute_command($1, NULL); }
;
E nel mio Flex:
' {BEGIN inQuote; }
<inQuote>\n {printf("Error: Unterminated Single Quote\n"); BEGIN(0); return(ERROR);}
Soluzione
Non credo che troverai una soluzione semplice per gestire questi tipi di errori di analisi nel lexer.
Vorrei mantenere il lexer (flex / lex) il più stupido possibile, dovrebbe semplicemente fornire un flusso di token di base (identificatori, parole chiave, ecc ...) e fare in modo che il parser (yacc / bison) esegua il rilevamento degli errori . In realtà è impostato esattamente per quello che vuoi, con una piccola ristrutturazione del tuo approccio ...
Nel lexer (parser.l), mantienilo semplice (nessuna gestione eol / newline), qualcosa del tipo (non è cosa completa):
}%
/* I don't recall if the backslashify is required below */
SINGLE_QUOTE_STRING \'.*\'
DOUBLE_QUOTE_STRING \".*\"
%%
{SINGLE_QUOTE_STRING} {
yylval.charstr = copy_to_tmp_buffer(yytext); // implies a %union
return STRING;
}
{DOUBLE_QUOTE_STRING} {
yylval.charstr = copy_to_tmp_buffer(yytext); // implies a %union
return STRING;
}
\n return NEWLINE;
Quindi nel tuo file parser.y fai tutta la vera gestione (non è cosa completa):
command:
error NEWLINE
{ yyclearin; yyerrorok; print_the_next_command_prompt(); }
| chdir_command STRING NEWLINE
{ do_the_chdir($<charstr>2); print_the_next_command_prompt(); }
| ... and so on ...
Ci sono due cose da notare qui:
- Lo spostamento di cose come NEWLINE sul lato yacc in modo da poter determinare quando l'utente ha terminato il comando, quindi è possibile cancellare le cose e ricominciare (supponendo che si abbia "
int yywrap() {return 1;}
" da qualche parte). Se provi a rilevarlo troppo presto in flex, quando sai di generare un errore? - chdir non è un comando (a meno che non sia stato sottoposto a sub-ruling e tu non l'abbia semplicemente mostrato), ora ha chdir_command STRING (l'argomento di chdir). Questo fa sì che il parser possa capire cosa è andato storto, quindi puoi yyerror se quella directory non esiste, ecc ...
In questo modo dovresti ottenere qualcosa del genere (indovinando come potrebbe apparire chdir):
cd 'some_directory
errore di sintassi
cd 'directory_un_modulo'
sei nel tizio della some_directory!
Ed è tutto gestito dalla grammatica yacc, non dal tokenizer.
Ho scoperto che mantenere la flessibilità il più semplice possibile ti dà la massima flessibilità. :)