Che cosa ha a che fare l'analisi senza scanner con il "Problema Elang ciondolante"?


13

Non capisco questa frase dall'articolo di Wikipedia sul problema Dangling Else :

[The Dangling Else problem] è un problema che si presenta spesso nella costruzione di compilatori, in particolare analisi senza scanner.

Qualcuno può spiegarmi come le tecniche di analisi senza scanner potrebbero esacerbare questo problema? Mi sembra che il problema sia con la grammatica, poiché è ambigua, non con la scelta della tecnica di analisi. Cosa mi sto perdendo?


2
L'unica cosa che mi viene in mente è che un parser senza scanner necessita di una grammatica più complessa, rendendo più difficile fornire euristiche per risolvere l'ambiguità.
Giorgio,

3
@Robert Harvey: Il punto è che questo presupposto deve essere riflesso dall'albero della sintassi. Se una grammatica consente di derivare due diversi alberi di sintassi per la stringa if a then if b then s1 else s2, la grammatica è ambigua.
Giorgio,

1
@RobertHarvey un modo comune per definire le lingue è usare una grammatica senza contesto, oltre a un mucchio di regole che chiariscono la grammatica, se necessario.

2
Non tutti i parser scannerless creati uguali. Per esempio, PEG o GLR, un comportamento penzolante di altro è sempre prevedibile.
SK-logic,

1
[Il problema Dangling Else] non ha nulla a che fare con l'analisi senza scanner. [Il problema Dangling Else] è correlato alle operazioni di riduzione-spostamento dei parser LR (bottom-up). Per quanto ne so
Ddur

Risposte:


6

La mia ipotesi migliore è che la frase nell'articolo di Wikipedia derivi da un fraintendimento del lavoro di E. Visser.

Le grammatiche per parser senza scanner (ovvero grammatiche che descrivono un linguaggio come un insieme di sequenze di caratteri invece che come un insieme di sequenze di token con i token descritti separatamente come stringhe di caratteri) tendono ad avere molte ambiguità. E. Visser paper Filtri di disambiguazione per parser LR generalizzati senza scanner (*) propone diversi meccanismi per risolvere le ambiguità, uno dei quali è utile per risolvere il problema degli altri penzoloni. Ma il documento non afferma che la precisa ambiguità denominata "problema penzolante altro" è correlata ai parser senza scanner (né che il meccanismo sia particolarmente utile per i parser senza scanner).

Il fatto che proponga un meccanismo per risolverlo non è un'affermazione implicita in quanto un altro meccanismo di risoluzione dell'ambiguità (priorità e precedenza dell'operatore) sembra totalmente estraneo alla natura priva di scanner dei parser considerati (si consideri ad esempio che tali ambiguità non possono essere presente nelle grammatiche regolari come risultano dalla nidificazione, mentre quelli gestiti da una regola di corrispondenza più lunga possono).


(*) Che è probabilmente il documento che funge da base dell'articolo di Wikipedia sui parser scannerless anche se ne fanno riferimento ad un altro, anche di E. Visser, Scannerless Generalized-LR Parsing .


13

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 ifottiene 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 " pushcondizione A nello stack di valutazione e quindi chiama brfalseper 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 è " calldoFoo". 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 elsesignifica 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 breakcome 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. " calldoBar". E c'è un punto e virgola e non ho mai visto parentesi graffe. Quindi, l'incondizionato breakdovrebbe 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 elseva 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 ifall'istruzione esterna passa breakall'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 elseappartiene 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 ifmai 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 ifs elseche ha per un tempo più lungo, ma se la specifica della lingua dice che una singola elsedopo due ifs corrisponde alla prima if, ciò causa un problema con due ifs con elses 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' ifistruzione ha un else(entrambi comunemente visti in altri stili di linguaggio).

Questo è solo uno, un semplice esempio di un paio di ifaffermazioni 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.


1
Interessante ma non sono certo che sia quello che intendeva l'articolo di Wikipedia. Fa riferimento (attraverso la voce senza scanner) a un rapporto di Eelco Visser il cui contenuto a prima vista non è compatibile con la tua spiegazione.
AProgrammer,

3
Grazie per la risposta, ma in realtà non si occupa dell'OP. Non sono d'accordo con le ipotesi nel post su quale sia l'obiettivo di un parser scannerless e come sia implementato. Esistono molti modi per implementare i parser scannerless e questo post sembra occuparsi solo di un sottoinsieme limitato.
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.