Giusto per affermare il problema, il problema di Dangling Else è un'ambiguità nella specifica della sintassi del codice in cui potrebbe non essere chiaro, in caso di if e elect successivi, a cui appartiene altrimenti a quale if.
L'esempio più semplice e classico:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Non è chiaro, per coloro che non conoscono a memoria le specifiche della specifica della lingua, che if
ottiene il else
(e questo particolare frammento di codice è valido in mezza dozzina di lingue, ma può funzionare diversamente in ciascuna).
Il costrutto Dangling Else rappresenta un potenziale problema per le implementazioni del parser senza scanner, perché la strategia è di snellire il flusso di file di un carattere alla volta, fino a quando il parser vede che ha abbastanza tokenize (digita nell'assembly o nel linguaggio intermedio che sta compilando) . Ciò consente al parser di mantenere uno stato minimo; non appena pensa di avere abbastanza informazioni per scrivere i token che viene analizzato nel file, lo farà. Questo è l'obiettivo finale di un parser senza scanner; compilation veloce, semplice e leggera.
Supponendo che le nuove righe e gli spazi bianchi prima o dopo la punteggiatura non abbiano senso (come nella maggior parte dei linguaggi in stile C), questa affermazione sembrerebbe al compilatore come:
if(conditionA)if(conditionB)doFoo();else doBar;
Perfettamente analizzabile su un computer, quindi vediamo. Ricevo un personaggio alla volta finché non ho:
if(conditionA)
Oh, so cosa significa (in C #), significa " push
condizione A nello stack di valutazione e quindi chiama brfalse
per saltare all'istruzione dopo il punto e virgola successivo se non è vero". In questo momento non vedo un punto e virgola, quindi per ora imposterò il mio salto di salto nello spazio successivo dopo questa istruzione e aumenterò tale offset quando inserirò più istruzioni fino a quando vedrò un punto e virgola. Continuando ad analizzare ...
if(conditionB)
OK, questo analizza una coppia simile di operazioni IL e va immediatamente dopo le istruzioni che ho appena analizzato. Non vedo un punto e virgola, quindi aumenterò l'offset del salto della mia precedente istruzione della lunghezza dei miei due comandi (uno per il push e uno per il break) e continuerò a cercare.
doFoo();
Ok, è facile. Questo è " call
doFoo". Ed è un punto e virgola che vedo? Bene, è fantastico, questa è la fine della linea. Incrementerò entrambi gli offset dei salti dei miei blocchi della lunghezza di questi due comandi e dimenticherò che mi è mai importato. OK, andare avanti ...
else
... Uh Oh. Questo non è così semplice come sembrava. OK, ho dimenticato quello che stavo solo facendo, ma else
significa che c'è una frase di interruzione condizionale da qualche parte che ho già visto, quindi lasciami guardare indietro ... sì, eccolo brfalse
, subito dopo aver premuto "condizione B" la pila, qualunque cosa fosse. OK, ora ho bisogno di un incondizionato break
come la prossima affermazione. L'affermazione che seguirà ora è sicuramente l'obiettivo della mia pausa condizionale, quindi mi assicurerò di averla giusta e aumenterò l'interruzione incondizionata che ho inserito. Passando ...
doBar();
Questo è facile. " call
doBar". E c'è un punto e virgola e non ho mai visto parentesi graffe. Quindi, l'incondizionato break
dovrebbe passare alla frase successiva, qualunque essa sia, e posso dimenticare di essermi mai preoccupato.
Quindi, cosa abbiamo ... (nota: sono le 22:00 e non ho voglia di convertire offset bit in esadecimali o compilare l'intera shell IL di una funzione con questi comandi, quindi questo è solo pseudo-IL usando i numeri di riga dove normalmente ci sarebbero offset di byte):
ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>
Bene, che in realtà viene eseguito correttamente, SE la regola (come nella maggior parte dei linguaggi in stile C) è che la else
va con il più vicino if
. Indentato per seguire l'annidamento dell'esecuzione, verrebbe eseguito in questo modo, se condizioneA è falsa, l'intero resto dello snippet viene ignorato:
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
... ma lo fa per caso, perché l'interruzione associata if
all'istruzione esterna passa break
all'istruzione alla fine dell'interiore if
, che porta il puntatore dell'esecuzione oltre l'intera istruzione. È un salto extra non necessario e, se questo esempio fosse più complesso, potrebbe non funzionare più se analizzato e tokenizzato in questo modo.
Inoltre, cosa accadrebbe se la specifica del linguaggio dicesse che un penzolante else
appartiene al primo if
, e se la condizione A è falsa, allora viene eseguita la doBar, mentre se la condizione A è vera ma non la condizione B, allora non accade nulla del genere?
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
Il parser aveva dimenticato il primo if
mai esistito, e quindi questo semplice algoritmo di parser non avrebbe prodotto il codice corretto, per non parlare di un codice efficiente.
Ora, il parser potrebbe essere abbastanza intelligente da ricordare le if
s else
che ha per un tempo più lungo, ma se la specifica della lingua dice che una singola else
dopo due if
s corrisponde alla prima if
, ciò causa un problema con due if
s con else
s corrispondenti :
if(conditionA)
if(conditionB)
doFoo();
else
doBar();
else
doBaz();
Il parser vedrà il primo else
, corrisponderà al primo if
, quindi vedrà il secondo e andrà nel panico modalità "cosa diavolo stavo facendo di nuovo". A questo punto, il parser ha ottenuto un sacco di codice in uno stato mutevole che avrebbe preferito piuttosto inviare al filestream di output.
Esistono soluzioni a tutti questi problemi e what-ifs. Ma, o il codice che deve essere così intelligente aumenta la complessità dell'algoritmo del parser, o le specifiche del linguaggio che consentono al parser di essere così stupido aumentano la verbosità del codice sorgente del linguaggio, come richiedendo istruzioni di terminazione come end if
, o parentesi che indicano nidificate si blocca se l' if
istruzione ha un else
(entrambi comunemente visti in altri stili di linguaggio).
Questo è solo uno, un semplice esempio di un paio di if
affermazioni e guarda tutte le decisioni che il compilatore ha dovuto prendere, e dove avrebbe potuto facilmente incasinare comunque. Questo è il dettaglio dietro quell'innocua dichiarazione di Wikipedia nella tua domanda.