Come fa esattamente un compilatore a recuperare da un errore di tipo?


10

Ho letto diversi articoli, articoli e la sezione 4.1.4, capitolo 4 di Compilatori: Principi, tecniche e strumenti (2a edizione) (alias "The Dragon Book") che discutono tutti dell'argomento del recupero sintattico degli errori del compilatore. Tuttavia, dopo aver sperimentato diversi compilatori moderni, ho visto che si riprendono anche da errori semantici e da errori sintattici.

Comprendo abbastanza bene gli algoritmi e le tecniche alla base dei compilatori che si stanno riprendendo da errori sintatticamente correlati, tuttavia non capisco esattamente come un compilatore possa recuperare da un errore semantico.

Attualmente sto usando una leggera variazione del modello visitatore per generare codice dal mio albero di sintassi astratto. Considera il mio compilatore compilando le seguenti espressioni:

1 / (2 * (3 + "4"))

Il compilatore genererebbe il seguente albero di sintassi astratto:

      op(/)
        |
     -------
    /       \ 
 int(1)    op(*)
             |
          -------
         /       \
       int(2)   op(+)
                  |
               -------
              /       \
           int(3)   str(4)

La fase di generazione del codice utilizza quindi il modello visitatore per attraversare ricorsivamente l'albero di sintassi astratto ed eseguire il controllo del tipo. L'albero di sintassi astratto verrebbe attraversato fino a quando il compilatore non arrivasse alla parte più interna dell'espressione; (3 + "4"). Il compilatore quindi controlla ogni lato delle espressioni e vede che non sono semanticamente equivalenti. Il compilatore genera un errore di tipo. Qui è dove si trova il problema. Cosa dovrebbe fare ora il compilatore ?

Affinché il compilatore si riprenda da questo errore e continui il controllo del tipo controllando le parti esterne delle espressioni, dovrebbe restituire un tipo ( into str) dalla valutazione della parte più interna dell'espressione alla successiva parte più interna dell'espressione. Ma semplicemente non ha un tipo da restituire . Poiché si è verificato un errore di tipo, non è stato dedotto alcun tipo.

Una possibile soluzione che ho postulato è che se si verifica un errore di tipo, dovrebbe essere generato un errore e un valore speciale che indica che si è verificato un errore di tipo dovrebbe essere restituito alle precedenti chiamate di attraversamento dell'albero di sintassi astratte. Se le chiamate di attraversamento precedenti incontrano questo valore, sanno che si è verificato un errore di tipo più profondo nella struttura della sintassi astratta e dovrebbero evitare di provare a dedurne un tipo. Mentre questo metodo sembra funzionare, sembra essere molto inefficiente. Se la parte più interna di un'espressione è in profondità nell'albero della sintassi astratta, il compilatore dovrà effettuare molte chiamate ricorsive solo per rendersi conto che non è possibile eseguire alcun lavoro reale e tornare semplicemente da ognuna.

È usato il metodo che ho descritto sopra (ne dubito). In tal caso, non è efficace? In caso contrario, quali sono esattamente i metodi utilizzati quando i compilatori si riprendono da errori semantici?


3
Abbastanza sicuro che è quello che viene utilizzato e perché non pensi che sia abbastanza efficiente? Per fare il controllo di tipo, il compilatore deve camminare l'intero albero in ogni modo . Un errore semantico è più efficiente poiché consente al compilatore di eliminare un ramo una volta rilevato l'errore.
Telastyn,

Risposte:


8

L'idea proposta è sostanzialmente corretta.

La chiave è che il tipo di un nodo AST viene calcolato solo una volta e quindi memorizzato. Ogni volta che il tipo è di nuovo necessario, recupera semplicemente il tipo memorizzato. Se la risoluzione termina in un errore, viene invece memorizzato un tipo di errore.


3

Un approccio interessante è quello di avere un tipo speciale per gli errori. Quando si verifica per la prima volta un errore del genere, viene registrata una diagnostica e il tipo di errore viene restituito come tipo dell'espressione. Questo tipo di errore ha alcune proprietà interessanti:

  • Qualsiasi operazione eseguita su di essa ha esito positivo (al fine di evitare una cascata di messaggi di errore tutti causati dallo stesso errore originale)
  • Il risultato di qualsiasi operazione eseguita su un oggetto con tipo di errore ha anche un tipo di errore
  • Se un tipo di errore arriva fino alla generazione del codice, il generatore di codice individua l'uso e genera codice che non riesce (ad esempio genera un'eccezione, interrompe o tutto ciò che è appropriato per la tua lingua)

Con questa combinazione, puoi effettivamente compilare correttamente il codice che contiene errori di tipo e fino a quando quel codice non viene effettivamente utilizzato, non si verificherà alcun errore di runtime. Ciò può essere utile, ad esempio, per consentire all'utente di eseguire unit test per parti del codice che non sono interessate.


Grazie per la risposta Jules. Abbastanza divertente, questo è il metodo esatto che ho finito per usare. Le grandi menti pensano allo stesso modo, eh? ;-)
Christian Dean,

2

Se si verifica un errore semantico, viene inviato all'utente un messaggio di errore di compilazione che indica tale errore.

Una volta fatto, è ok interrompere la compilazione poiché il programma di input è in errore - non è un programma legale nella lingua, quindi può semplicemente essere rifiutato.

È piuttosto duro, però, quindi ci sono alternative più morbide. Annullare qualsiasi generazione di codice e generazione di file di output, ma continuare a cercare altri errori.

Ad esempio, può semplicemente interrompere qualsiasi ulteriore analisi del tipo per l'albero delle espressioni corrente e continuare a elaborare le espressioni dalle istruzioni successive.


2

Supponiamo che la tua lingua consenta di aggiungere numeri interi e di concatenare stringhe con l' +operatore.

Dal momento che int + stringnon è consentito, la valutazione del +comporterà la segnalazione di un errore. Il compilatore potrebbe semplicemente restituire errorcome tipo. Oppure potrebbe essere più intelligente, poiché int + int -> inte string + string -> stringsono consentiti, potrebbe restituire "errore, potrebbe essere int o stringa".

Poi arriva l' *operatore e supponiamo che int + intsia consentito solo . Il compilatore può quindi decidere che +effettivamente dovrebbe essere restituito int, e il tipo restituito per *sarebbe quindi int, senza alcun messaggio di errore.


Penso di seguirti, @gnasher, ma cosa intendi esattamente con l' operatore "" ? È stato un errore di battitura?
Christian Dean,

@ChristianDean c'è un asterisco tra virgolette che viene interpretato come markup Markdown invece di essere reso.
JakeRobb,

Ho inviato una modifica alla risposta che risolverà il problema non appena la mia modifica sarà sottoposta a peer review.
JakeRobb,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.