Sto studiando CoffeeScript sul sito http://coffeescript.org/ e ha il testo
Il compilatore CoffeeScript è esso stesso scritto in CoffeeScript
Come può compilare un compilatore stesso o cosa significa questa affermazione?
Sto studiando CoffeeScript sul sito http://coffeescript.org/ e ha il testo
Il compilatore CoffeeScript è esso stesso scritto in CoffeeScript
Come può compilare un compilatore stesso o cosa significa questa affermazione?
Risposte:
La prima edizione di un compilatore non può essere generata automaticamente da un linguaggio di programmazione specifico; la tua confusione è comprensibile. Una versione successiva del compilatore con più funzionalità di linguaggio (con la fonte riscritta nella prima versione del nuovo linguaggio) potrebbe essere costruita dal primo compilatore. Quella versione potrebbe quindi compilare il compilatore successivo e così via. Ecco un esempio:
Nota: non sono sicuro di come siano numerate le versioni di CoffeeScript, questo è solo un esempio.
Questo processo è generalmente chiamato bootstrap . Un altro esempio di compilatore di bootstrap è rustc
il compilatore per il linguaggio Rust .
Nel documento Reflections on Trusting Trust , Ken Thompson, uno dei creatori di Unix, scrive un'affascinante (e facilmente leggibile) panoramica di come si compila il compilatore C. Concetti simili possono essere applicati a CoffeeScript o a qualsiasi altra lingua.
L'idea di un compilatore che compila il proprio codice è vagamente simile a un quine : codice sorgente che, quando eseguito, produce come output il codice sorgente originale. Ecco un esempio di quine CoffeeScript. Thompson ha dato questo esempio di C quine:
char s[] = {
'\t',
'0',
'\n',
'}',
';',
'\n',
'\n',
'/',
'*',
'\n',
… 213 lines omitted …
0
};
/*
* The string s is a representation of the body
* of this program from '0'
* to the end.
*/
main()
{
int i;
printf("char\ts[] = {\n");
for(i = 0; s[i]; i++)
printf("\t%d,\n", s[i]);
printf("%s", s);
}
Quindi, potresti chiederti come viene insegnato al compilatore che una sequenza di escape come '\n'
rappresenta il codice ASCII 10. La risposta è che da qualche parte nel compilatore C, c'è una routine che interpreta i caratteri letterali, contenente alcune condizioni come questa per riconoscere le sequenze di barra rovesciata:
…
c = next();
if (c != '\\') return c; /* A normal character */
c = next();
if (c == '\\') return '\\'; /* Two backslashes in the code means one backslash */
if (c == 'r') return '\r'; /* '\r' is a carriage return */
…
Quindi, possiamo aggiungere una condizione al codice sopra ...
if (c == 'n') return 10; /* '\n' is a newline */
... per produrre un compilatore che sappia che '\n'
rappresenta ASCII 10. È interessante notare che quel compilatore e tutti i successivi compilatori da esso compilati "conoscono" tale mappatura, quindi nella prossima generazione del codice sorgente, è possibile modificare quest'ultima riga in
if (c == 'n') return '\n';
... e farà la cosa giusta! La 10
deriva dal compilatore, e non ha più bisogno di essere esplicitamente definito nel codice sorgente del compilatore. 1
Questo è un esempio di una funzionalità del linguaggio C implementata nel codice C. Ora, ripeti questo processo per ogni singola lingua e hai un compilatore "self-hosting": un compilatore C scritto in C.
1 La svolta della trama descritta nel documento è che dal momento che al compilatore possono essere "insegnati" fatti come questo, può anche essere insegnato male a generare eseguibili trojan in un modo che è difficile da rilevare, e un tale atto di sabotaggio può persistere in tutti i compilatori prodotti dal compilatore contaminato.
Hai già ottenuto un'ottima risposta, tuttavia voglio offrirti una prospettiva diversa, che si spera ti sia illuminante. Stabiliamo innanzitutto due fatti su cui possiamo entrambi concordare:
Sono sicuro che puoi essere d'accordo sul fatto che sia il numero 1 che il numero 2 siano veri. Ora, guarda le due affermazioni. Vedete ora che è del tutto normale che il compilatore CoffeeScript sia in grado di compilare il compilatore CoffeeScript?
Al compilatore non importa cosa compila. Finché è un programma scritto in CoffeeScript, può compilarlo. E il compilatore CoffeeScript stesso sembra essere un tale programma. Al compilatore CoffeeScript non importa che sia il compilatore CoffeeScript stesso che sta compilando. Tutto ciò che vede è un po 'di codice CoffeeScript. Periodo.
Come può compilare un compilatore stesso o cosa significa questa affermazione?
Sì, questo è esattamente ciò che significa questa affermazione, e spero che tu possa vedere ora come questa affermazione è vera.
Come può compilare un compilatore stesso o cosa significa questa affermazione?
Significa esattamente questo. Prima di tutto, alcune cose da considerare. Ci sono quattro oggetti che dobbiamo guardare:
Ora, dovrebbe essere ovvio che è possibile utilizzare l'assembly generato - l'eseguibile - del compilatore CoffeScript per compilare qualsiasi programma CoffeScript arbitrario e generare l'assembly per quel programma.
Ora, il compilatore CoffeScript stesso è solo un programma CoffeScript arbitrario, e quindi può essere compilato dal compilatore CoffeScript.
Sembra che la tua confusione derivi dal fatto che quando crei la tua nuova lingua, non hai ancora un compilatore che puoi usare per compilare il tuo compilatore. Questo sembra sicuramente un problema con le uova di gallina , giusto?
Introdurre il processo chiamato bootstrap .
Ora devi aggiungere nuove funzionalità. Supponiamo che tu abbia implementato solo while
-loops, ma desideri anche for
-loops. Questo non è un problema, poiché puoi riscrivere qualsiasi for
-loop in modo tale che sia while
-loop. Questo significa che puoi usare solo while
-loops nel codice sorgente del tuo compilatore, poiché l'assembly che hai a portata di mano può solo compilare quelli. Ma puoi creare funzioni all'interno del tuo compilatore in grado di compilare e compilare for
-loops con esso. Quindi si utilizza l'assembly già in uso e si compila la nuova versione del compilatore. E ora hai un assembly di un compilatore che può anche analizzare e compilare for
-loops! Ora puoi tornare al file sorgente del tuo compilatore e riscrivere tutti i while
loop che non vuoi for
inserire in -loops.
Risciacquare e ripetere fino a quando tutte le funzionalità di lingua desiderate possono essere compilate con il compilatore.
while
e for
ovviamente erano solo esempi, ma questo funziona per qualsiasi nuova funzione linguistica che desideri. E poi ti trovi nella situazione in cui si trova CoffeScript: il compilatore si compila da solo.
C'è molta letteratura là fuori. Riflessioni sulla fiducia La fiducia è un classico che tutti gli interessati a quell'argomento dovrebbero leggere almeno una volta.
Qui il termine compilatore riflette sul fatto che ci sono due file coinvolti. Uno è un eseguibile che accetta come file di input scritti in CoffeScript e produce come file di output un altro eseguibile, un file oggetto collegabile o una libreria condivisa. L'altro è un file sorgente di CoffeeScript che descrive semplicemente la procedura per la compilazione di CoffeeScript.
Si applica il primo file al secondo, producendo un terzo che è in grado di eseguire lo stesso atto di compilazione del primo (possibilmente di più, se il secondo file definisce funzionalità non implementate dal primo), e quindi si può sostituire il primo se si così desiderio.
Poiché la versione Ruby del compilatore CoffeeScript esisteva già, è stata utilizzata per creare la versione CoffeeScript del compilatore CoffeeScript.
Questo è noto come compilatore self-hosting .
È estremamente comune e di solito deriva dal desiderio di un autore di usare la propria lingua per mantenere la crescita di quella lingua.
Non è una questione di compilatori qui, ma una questione di espressività del linguaggio, dal momento che un compilatore è solo un programma scritto in una lingua.
Quando diciamo che "una lingua è scritta / implementata" intendiamo effettivamente che un compilatore o un interprete per quella lingua è implementato. Esistono linguaggi di programmazione in cui è possibile scrivere programmi che implementano la lingua (sono compilatori / interpreti per la stessa lingua). Queste lingue sono chiamate lingue universali .
Per poterlo capire, pensa a un tornio di metallo. È uno strumento utilizzato per modellare il metallo. È possibile, usando solo quello strumento, creare un altro strumento identico, creando le sue parti. Pertanto, tale strumento è una macchina universale. Naturalmente, il primo è stato creato utilizzando altri mezzi (altri strumenti) ed era probabilmente di qualità inferiore. Ma il primo è stato usato per costruirne di nuovi con maggiore precisione.
Una stampante 3D è quasi una macchina universale. È possibile stampare l'intera stampante 3D utilizzando una stampante 3D (non è possibile creare la punta che scioglie la plastica).
La n + 1a versione del compilatore è scritta in X.
Quindi può essere compilato dall'ennesima versione del compilatore (anch'essa scritta in X).
Ma la prima versione del compilatore scritta in X deve essere compilata da un compilatore per X scritto in una lingua diversa da X. Questo passaggio è chiamato bootstrap del compilatore.
I compilatori prendono una specifica di alto livello e la trasformano in un'implementazione di basso livello, come può essere eseguita su hardware. Pertanto non esiste alcuna relazione tra il formato della specifica e l'esecuzione effettiva oltre alla semantica del linguaggio preso di mira.
I compilatori incrociati si spostano da un sistema a un altro, i compilatori multilingua compilano una specifica di lingua in un'altra specifica di lingua.
Fondamentalmente la compilazione è una traduzione giusta, e il livello è generalmente di livello più alto della lingua a un livello più basso della lingua, ma ci sono molte varianti.
I compilatori di bootstrap sono i più confusi, ovviamente, perché compilano la lingua in cui sono scritti. Non dimenticare il passaggio iniziale nel bootstrap che richiede almeno una versione minima esistente eseguibile. Molti compilatori con bootstrap lavorano prima sulle funzionalità minime di un linguaggio di programmazione e aggiungono funzionalità linguistiche complesse aggiuntive a condizione che la nuova funzionalità possa essere espressa utilizzando le funzionalità precedenti. Se così non fosse, richiederebbe che quella parte del "compilatore" sia stata precedentemente sviluppata in un'altra lingua.
self-hosting
compilatore. Vedi programmers.stackexchange.com/q/263651/6221