• Non ci sono risultati.

Gestione degli errori in Yacc

Capitolo 3: Parser generator YACC

3.7 Gestione degli errori in Yacc

La gestione degli errori è un campo estremamente difficile e molti dei problemi che si incontrano sono semantici. Quando si trova un errore, potrebbe essere necessario, per esempio, richiedere il contenuto dell’albero sintattico, cancellare o alterare simboli in entrata nella tabella e, di norma, regolare i cambiamenti per evitare di generare qualche altro errore in uscita. Quando si trova un errore raramente si interrompe l’elaborazione: è più utile, infatti, continuare la scansione dell’ingresso per trovare successivi errori di sintassi. Questo conduce al problema di far ripartire il parser dopo un errore richiedendo (attraverso algoritmi specifici) di scartare diversi tokens dalla stringa di ingresso e di tentare di aggiustare il parser così da poter continuare con l’ingresso.

In Yacc esiste un token di nome error riservato proprio alla gestione degli errori. Questo nome può essere usato anche nelle regole grammaticali. Definiamo quindi, in aggiunta ad un insieme di regole della grammatica, una produzione di errore definita come segue:

A → error α

dove A è un simbolo non terminale e α è una stringa di simboli grammaticali oppure è una stringa vuota. Yacc, quindi, genererà un parser dalle specifiche, trattando le produzioni di errore come produzioni ordinarie. Tuttavia un parser generator Yacc, quando incontra un errore, tratta in modo speciale gli stati contenenti item che producono errori. Quando si incontra un errore, Yacc estrae simboli dalla pila finché entra in uno stato dove è ammesso il token error della forma A → .error α, quindi si comporta come se il token error fosse il token di lookahead corrente (token che ha causato l’errore) ed esegue l’azione incontrata. Quindi il parser

Capitolo 3 - Parser generator Yacc

sposta un fittizio token error sulla pila, come se vedesse il token error sul relativo input. Quando α∈ε, occorre immediatamente una riduzione di A ed è invocata l’azione semantica associata alla produzione A → error ; il parser, allora, scarta i simboli in input finché trova un simbolo in input sul quale può effettuare la sua analisi. Se α∉ε, Yacc salta la sottostringa in input che può essere ridotta da α. Se α è composto da simboli terminali, allora Yacc guarda la stringa di terminali in input e la riduce effettuando l’operazione di shift della stringa sulla pila. A questo punto il parser avrà in cima alla pila error α. Il parser ridurrà error α ad A e riprenderà la normale analisi. Se non sono state specificate speciali regole per gli errori, allora l’elaborazione termina in uno stato di errore.

Per esempio, una regola generica di errore della forma

stmt → error

significherebbe, in effetti, che su un errore di sintassi il parser potrebbe tentare di saltare oltre le dichiarazioni in cui l’errore è stato visto. Più precisamente, il parser scansionerà oltre, cercando i tokens successivi che possono seguire un’istruzione, e farà ripartire il processo col primo di questi tokens. Se le parti iniziali delle istruzioni non sono sufficientemente distinte, si può produrre una falsa partenza a metà di un’istruzione e in conclusionen questo caso viene riportato un secondo errore, di fatto inesistente. Pertanto, per prevenire una cascata di messaggi di errore, il parser, una volta localizzato l’errore, rimane in uno stato di errore finché non legge un certo numero di tokens successivi e li sposta in cima alla pila. Se un errore è localizzato quando il parser è già in uno stato di errore, non è dato nessun messaggio e il token in ingresso è tranquillamente cancellato.

Capitolo 3 - Parser generator Yacc

Per gli errori si potrebbero usare, tuttavia, delle azioni con alcune speciali regole, più facili da trattare rispetto ad una regola generica di errore, che è difficilmente controllabile. Una regola più semplice e facile da controllare è, ad esempio, quella dalla forma:

stmt → error ‘ ; ‘

dove, in presenza di un errore, il parser tenta di saltare oltre l’istruzione, ma fa ciò soltanto fino al ‘;’ successivo. Tutti i tokens dopo l’errore e prima del ‘;’ successivo non possono essere spostati e vengono ignorati. Quando il ‘;’ viene visto, questa regola permetterà la riduzione e verrà eseguita ogni azione di pulizia associata ad essa.

Un altro tipo di regola di errore è utile nelle applicazioni interattive, dove sarebbe desiderabile poter reinserire una linea dopo un errore. Una possibile regola di errore potrebbe essere

lines : error ‘ \n’ { printf ( “ Reenter last line: ” ) ; } input { $$ = $4; }

che causa la sospensione di una normale analisi quando si verifica un errore sintattico in una linea di input. Quando si incontra l’errore, il parser estrae simboli dalla sua pila finché non si trova in uno stato che effettui l’azione di shift sul token error; tale stato, quindi, deve includere l’item della forma:

lines : . error ‘ \n’

Quindi il parser sposta il token error sulla pila e salta avanti nella stringa di input finché non trova il carattere che permette di inserire una nuova linea. A questo punto il parser sposta il carattere in testa alla pila, riduce error ‘\n’ in lines e scrive il messaggio diagnosticato. In Yacc esiste una procedura yyerrok() che fa tornare il parser nella modalità normale (cioè

Capitolo 3 - Parser generator Yacc

nel suo modo normale di operare). Pertanto l’ultimo esempio può essere scritto in un modo migliore:

lines : error ‘ \n’

{ yyerrok;

printf ( “ Reenter last line: ” ) ; } input

{ $$ = $4; } ;

Come abbiamo visto, il token posto immediatamente dopo il simbolo error è il token d’ingresso in cui l’errore è stato scoperto. A volte è inappropriato; per esempio, un’azione di recupero di un errore può incaricarsi di ritrovare il posto giusto per far ripartire il processo di analisi. In questo caso il precedente token di lookahead deve essere eliminato, e ciò viene fatto attraverso la dichiarazione

yyclearin ;

Questi meccanismi sono alquanto rigidi ma permettono al parser di funzionare anche in presenza di molti errori [1,17,18,19].

Documenti correlati