Come faccio a creare il mio linguaggio di programmazione e un compilatore per esso [chiuso]


427

Sono molto attento alla programmazione e ho incontrato lingue tra cui BASIC, FORTRAN, COBOL, LISP, LOGO, Java, C ++, C, MATLAB, Mathematica, Python, Ruby, Perl, JavaScript, Assembly e così via. Non riesco a capire come le persone creano linguaggi di programmazione e escogitano compilatori per questo. Inoltre non riuscivo a capire come le persone creavano sistemi operativi come Windows, Mac, UNIX, DOS e così via. L'altra cosa che è misteriosa per me è come le persone creano librerie come OpenGL, OpenCL, OpenCV, Cocoa, MFC e così via. L'ultima cosa che non riesco a capire è come gli scienziati escogitano un linguaggio assembly e un assemblatore per un microprocessore. Mi piacerebbe davvero imparare tutte queste cose e ho 15 anni. Ho sempre voluto essere uno scienziato informatico qualcuno come Babbage, Turing, Shannon o Dennis Ritchie.


Ho già letto il Compiler Design di Aho e il libro dei concetti del sistema operativo di Tanenbaum e tutti discutono solo concetti e codice di alto livello. Non entrano nei dettagli e nelle sfumature e come escogitare un compilatore o un sistema operativo. Voglio una comprensione concreta in modo da poterne creare una me stessa e non solo una comprensione di cosa sia un thread, un semaforo, un processo o un'analisi. Ho chiesto a mio fratello di tutto questo. È uno studente SB in EECS presso il MIT e non ha idea di come creare effettivamente tutte queste cose nel mondo reale. Tutto quello che sa è solo una comprensione dei concetti di progettazione del compilatore e del sistema operativo come quelli che avete menzionato (ad es. Thread, sincronizzazione, concorrenza, gestione della memoria, analisi lessicale, generazione di codice intermedio e così via)


Se siete su Unix / Linux, è possibile ottenere informazioni sugli strumenti dedicati: lex, yacce bison.
mouviciel,

Il mio primo suggerimento sarebbe quello di leggere il libro del drago di Aho. amazon.com/Compilers-Principles-Techniques-Alfred-Aho/dp/…
Julian

1
Forse non troppo utile, ma vi consiglio di andare attraverso sites.google.com/site/steveyegge2/blog-rants (il blog di Steve Yegge), e steve-yegge.blogspot.com/ (altri blog di Steve Yegge).
KK.

3
Impara quanti più linguaggi di programmazione puoi. In questo modo imparerai dai loro concetti e dai loro errori. Perché accontentarsi dei nani, quando puoi stare sulla spalla dei giganti?
sabato

1
suggerimento: un interprete è più semplice di un compilatore; è solo una classe che "fa qualcosa" in base al testo di input che legge riga per riga. un altro suggerimento: legalo alla riflessione e puoi controllare oggetti arbitrari con la tua sceneggiatura.
Dave Cousineau,

Risposte:


407

Fondamentalmente, la tua domanda è "come vengono progettati e implementati chip per computer, set di istruzioni, sistemi operativi, lingue, librerie e applicazioni?" Si tratta di un'industria multimiliardaria che impiega milioni di persone, molte delle quali specializzate. Potresti voler concentrare un po 'di più la tua domanda.

Detto questo, posso fare una pausa a:

Non riesco a capire come le persone creano linguaggi di programmazione e escogitano compilatori per questo.

È sorprendente per me, ma molte persone considerano i linguaggi di programmazione magici. Quando incontro persone alle feste o altro, se mi chiedono cosa faccio dico loro che progetto linguaggi di programmazione e implemento compilatori e strumenti, ed è sorprendente il numero di volte in cui le persone - programmatori professionisti, intendiamoci - dicono "wow, non ci ho mai pensato, ma sì, qualcuno deve progettare quelle cose". È come se pensassero che le lingue si siano appena formate interamente con le infrastrutture degli strumenti intorno a loro.

Non appaiono solo. Le lingue sono progettate come qualsiasi altro prodotto: facendo attentamente una serie di compromessi tra possibilità concorrenti. I compilatori e gli strumenti sono costruiti come qualsiasi altro prodotto software professionale: abbattendo il problema, scrivendo una riga di codice alla volta e quindi testando il controllo del programma risultante.

Il design della lingua è un argomento enorme. Se sei interessato a progettare una lingua, un buon punto di partenza è pensare a quali sono le carenze in una lingua che già conosci. Le decisioni di progettazione spesso derivano dalla considerazione di un difetto di progettazione in un altro prodotto.

In alternativa, prendere in considerazione un dominio a cui si è interessati e quindi progettare un linguaggio specifico del dominio (DSL) che specifica le soluzioni ai problemi in quel dominio. Hai citato LOGO; questo è un ottimo esempio di DSL per il dominio "disegno a tratteggio". Le espressioni regolari sono un DSL per il dominio "trova uno schema in una stringa". LINQ in C # / VB è un DSL per il dominio "filtro, join, ordinamento e dati di progetto". HTML è un DSL per il dominio "descrivi il layout del testo in una pagina" e così via. Esistono molti domini che sono suscettibili di soluzioni basate sulla lingua. Uno dei miei preferiti è Inform7, che è un DSL per il dominio "gioco di avventura basato su testo"; è probabilmente il linguaggio di programmazione serio di più alto livello che abbia mai visto.

Dopo aver delineato l'aspetto della tua lingua, prova a scrivere esattamente quali sono le regole per determinare che cos'è un programma legale e illegale. In genere ti consigliamo di farlo a tre livelli:

  1. lessicale : quali sono le regole per le parole nella lingua, quali caratteri sono legali, che aspetto hanno i numeri e così via.
  2. sintattica : come si uniscono le parole della lingua in unità più grandi? In C # le unità più grandi sono cose come espressioni, istruzioni, metodi, classi e così via.
  3. semantica : dato un programma sintatticamente legale, come si fa a capire che cosa il programma fa ?

Scrivi queste regole nel modo più preciso possibile . Se fai un buon lavoro, puoi usarlo come base per scrivere un compilatore o un interprete. Dai un'occhiata alla specifica C # o alla specifica ECMAScript per capire cosa intendo; sono pieni di regole molto precise che descrivono cosa rende un programma legale e come capire cosa si fa.

Uno dei modi migliori per iniziare a scrivere un compilatore è scrivere un compilatore di linguaggio di alto livello in linguaggio di alto livello . Scrivi un compilatore che includa le stringhe nella tua lingua e ne esponga le stringhe in C # o JavaScript o in qualsiasi altra lingua tu conosca; lascia che il compilatore di quella lingua si occupi del pesante sollevamento di trasformarlo in codice eseguibile.

Scrivo un blog sulla progettazione di C #, VB, VBScript, JavaScript e altri linguaggi e strumenti; se questo argomento ti interessa, dai un'occhiata. http://blogs.msdn.com/ericlippert (historical) e http://ericlippert.com (corrente)

In particolare potresti trovare questo post interessante; qui elenchiamo la maggior parte delle attività che il compilatore C # esegue per te durante la sua analisi semantica. Come puoi vedere, ci sono molti passaggi. Suddividiamo il grande problema di analisi in una serie di problemi che possiamo risolvere individualmente.

http://blogs.msdn.com/b/ericlippert/archive/2010/02/04/how-many-passes.aspx

Infine, se stai cercando un lavoro facendo queste cose quando sei più grande, considera di venire a Microsoft come stagista universitario e provare a entrare nella divisione sviluppatori. È così che ho finito il mio lavoro oggi!


Hai scritto fino a che punto le ottimizzazioni del compilatore non vengono più eseguite in quanto il CLR può eseguirle automaticamente?

6
@ Thorbjørn: chiariamo la terminologia. Un "compilatore" è qualsiasi dispositivo che traduce da un linguaggio di programmazione a un altro. Una delle cose belle di avere un compilatore C # che trasforma C # in IL e un compilatore IL (il "jitter") che trasforma IL in codice macchina, è che puoi scrivere il compilatore C # su IL (facile!), E mettere le ottimizzazioni specifiche del processore nel jitter. Non è che le ottimizzazioni del compilatore "non vengano fatte", è che il team del compilatore jit le fa per noi. Vedi blogs.msdn.com/b/ericlippert/archive/2009/06/11/…
Eric Lippert

6
@ Cyclotis04: Inform6 viene compilato in codice Z, che è un famoso esempio estremamente precoce di una macchina virtuale basata su bytecode. Ecco come tutti questi giochi Infocom negli anni '80 potrebbero essere sia più grandi della memoria sia portatili per più architetture; i giochi sono stati compilati in z-code e quindi gli interpreti z-code con il paging della memoria del codice sono stati implementati per più macchine. Al giorno d'oggi, naturalmente, puoi eseguire un interprete zcode su un orologio da polso, se necessario, ma ai tempi della tecnologia avanzata . Vedi en.wikipedia.org/wiki/Z-machine per i dettagli.
Eric Lippert,

Il compilatore @EricLippert non è un dispositivo, il dispositivo è qualcosa che contiene hardware. Possiamo dire un programma predefinito che ha una serie di regole per convertire i dati di input in codice macchina
dharam,

2
@dhams: un dispositivo è qualsiasi cosa fatta per uno scopo particolare. Ogni compilatore che abbia mai scritto è stato eseguito su hardware appositamente progettato per consentire l'esistenza di compilatori.
Eric Lippert,

127

Potresti trovare Lets Build a Compiler di Jack Crenshaw un'interessante introduzione alla scrittura di compilatori e linguaggio assembly.

L'autore è stato molto semplice e si è concentrato sulla costruzione di funzionalità reali.


2
La cosa interessante dell'intro di Crenshaw è che finisce (spoiler: è incompleto) proprio nel momento in cui ti imbatteresti nei problemi che ti farebbero capire, ehi, avrei davvero dovuto progettare il mio linguaggio completamente prima di iniziare a implementarlo. E poi dici, ehi, se devo scrivere una specifica del linguaggio completo, perché non farlo in una notazione formale che posso quindi inserire in uno strumento per generare un parser? E poi lo stai facendo come tutti gli altri.
kindall

3
@kindall, devi averlo fatto a mano per capire che c'è un motivo per usare gli strumenti.

72

"Mi piacerebbe davvero imparare queste cose". Se sei serio a lungo termine:

  • Vai al college, specializzato in ingegneria del software. Prendi ogni classe di compilatore che puoi ottenere. Quelle persone che offrono le lezioni sono più istruite e più esperte di te; è bello avere le loro prospettive esperte usate per presentarti le informazioni in modi che non otterrai mai dalla lettura del codice.

  • Segui le lezioni di matematica al liceo e continua al college per tutti i 4 anni. Focus su matematica non standard: logica, teoria dei gruppi, meta-matematica. Questo ti costringerà a pensare in modo astratto. Ti consentirà di leggere gli articoli di teoria avanzata sulla compilazione e capire perché quelle teorie sono interessanti e utili. Puoi ignorare quelle teorie avanzate, se vuoi essere per sempre dietro lo stato dell'arte.

  • Raccogli / leggi i testi standard del compilatore: Aho / Ullman, ecc. Contengono ciò che la comunità generalmente concorda è roba fondamentale. Potresti non usare tutto di quei libri, ma dovresti sapere che esiste e dovresti sapere perché non lo stai usando. Ho pensato che Muchnick fosse eccezionale, ma è per argomenti piuttosto avanzati.

  • Costruisci un compilatore. Inizia ORA costruendone uno marcio. Questo ti insegnerà alcuni problemi. Costruisci un secondo. Ripetere. Questa esperienza crea un'enorme sinergia con l'apprendimento del tuo libro.

  • Un ottimo punto di partenza è conoscere BNF (Backus Naur Form), parser e generatori di parser. BNF è effettivamente utilizzato universalmente nella terra del compilatore e non puoi parlare realisticamente con i tuoi colleghi tipi di compilatore se non lo conosci.

Se si desidera un'ottima prima introduzione alla compilazione e il valore diretto di BNF non solo per la documentazione ma come metalinguaggio elaborabile con strumenti, vedere questo tutorial (non mio) sulla costruzione di compilatori "meta" (compilatori che creano compilatori) basati su un articolo del 1964 (sì, hai letto bene) ["META II un linguaggio di scrittura per compilatore orientato alla sintassi" di Val Schorre. (http://doi.acm.org/10.1145/800257.808896)] Questo IMHO è uno dei migliori singoli documenti comp-sci mai scritti: ti insegna a costruire compilatori-compilatori in 10 pagine. Inizialmente ho imparato da questo documento.

Quello di cui ho scritto sopra è molto per esperienza personale e penso che mi sia servito abbastanza bene. YMMV, ma IMHO, non di molto.


54
-1 Nessuna delle precedenti è necessaria.
Neil Butterworth,

77
@nbt Non è necessario quanto sopra. Ma tutto quanto sopra aiuta. Davvero molto.
Konrad Rudolph,

1
Non sono particolarmente d'accordo con "Impara la matematica a pensare in modo astratto!" suggerimento. Anche se pensi che "imparare a pensare in modo astratto" sia particolarmente utile nel creare il tuo linguaggio di programmazione e il tuo compilatore (non lo trovo - trovo molto più utile imparare facendo, piuttosto che seguire queste rotonde, percorsi incredibilmente indiretti) , la matematica non è l'unico campo con pensiero astratto! (Sono un matematico a proposito, quindi non sto negando l'uso della matematica in generale, ma solo la sua applicabilità in questo caso particolare ...)
Grautur,

26
Se vuoi leggere gli articoli tecnici avanzati sulla teoria dei compilatori, è meglio essere matematicamente competenti. Puoi decidere di ignorare quella letteratura, e la tua teoria e quindi i compilatori saranno più poveri per questo. Gli oppositori qui sottolineano che puoi costruire un compilatore senza molta educazione formale, e sono d'accordo. Sembrano implicare che puoi costruire compilatori davvero buoni senza di essa. Non è una scommessa che mi piacerebbe prendere.
Ira Baxter,

7
CS è una disciplina che è veramente utile per la progettazione e l'implementazione del linguaggio. Naturalmente non è obbligatorio, ma ci sono stati decenni di ricerche che possono e devono essere sfruttate, e non vi è alcun motivo per ripetere altri errori.
Donal Fellows,

46

Ecco un libro / corso online che puoi seguire chiamato The Elements of Computing Systems: Building a Modern Computer from First Principles .

Usando i simulatori, in realtà costruisci un sistema informatico completo da zero. Mentre molti commentatori hanno affermato che la tua domanda è troppo ampia, questo libro risponde effettivamente rimanendo molto gestibile. Al termine, avrai scritto un gioco in un linguaggio di alto livello (che hai progettato), che utilizza la funzionalità del tuo sistema operativo, che viene compilato in un linguaggio VM (che hai progettato) dal tuo compilatore, che ottiene tradotto in un linguaggio assembly (che hai progettato) dal tuo traduttore VM, che viene assemblato in codice macchina (che hai progettato) dal tuo assemblatore, che gira sul tuo sistema informatico che componi da chip che hai progettato usando la logica booleana e un semplice linguaggio di descrizione hardware.

I capitoli:

  1. Panoramica del corso
  2. Logica booleana
  3. Chip Combinatori
  4. Chip sequenziali
  5. Linguaggio macchina
  6. Architettura del computer
  7. assembler
  8. Macchina virtuale I: aritmetica
  9. Macchina virtuale II: controllo
  10. Linguaggio di programmazione
  11. Compilatore I: analisi della sintassi
  12. Compilatore II: generazione di codice
  13. Sistema operativo
  14. Voce di elenco

Più divertimento da fare


Grazie per le modifiche, persona sconosciuta. Ci ho provato un paio di volte ma non sono riuscito a concentrare i miei pensieri abbastanza per la descrizione ... ma non volevo non menzionare il libro. Il libro è ora online al link del piano di studio: www1.idc.ac.il/tecs/plan.html . Ha anche un prezzo molto ragionevole online. Buon divertimento a tutti.
Joe Internet,

Lo stavo per suggerire io stesso ... per i più pigri, dai un'occhiata all'introduzione di 10 minuti: dalla NAND a Tetris in 12 passaggi @ youtube.com/watch?v=JtXvUoPx4Qs
Richard Anthony Hein

46

Fai un passo indietro. Un compilatore è semplicemente un programma che traduce un documento in una lingua in un documento in un'altra lingua. Entrambe le lingue dovrebbero essere ben definite e specifiche.

Le lingue non devono essere linguaggi di programmazione. Possono essere qualsiasi lingua le cui regole possono essere scritte. Probabilmente hai visto Google Translate ; è un compilatore perché può tradurre una lingua (diciamo, tedesco) in un'altra (giapponese, forse).

Un altro esempio di compilatore è un motore di rendering HTML. Il suo input è un file HTML e l'output è una serie di istruzioni per disegnare i pixel sullo schermo.

Quando la maggior parte delle persone parla di un compilatore, di solito si riferiscono a un programma che traduce un linguaggio di programmazione di alto livello (come Java, C, Prolog) in uno di basso livello (assembly o codice macchina). Questo può essere scoraggiante. Ma non è poi così male quando si considera l'opinione di un generalista che un compilatore è un programma che traduce una lingua in un'altra.

Sai scrivere un programma che inverte ogni parola in una stringa? Per esempio:

When the cat's away, the mice will play.

diventa

nehW eht s'tac yawa, eht ecim lliw yalp.

Non è un programma difficile da scrivere, ma devi pensare ad alcune cose:

  • Che cos'è una "parola"? Puoi definire quali personaggi compongono una parola?
  • Dove iniziano e finiscono le parole?
  • Le parole sono separate da un solo spazio o possono esserci più o meno?
  • Anche la punteggiatura deve essere invertita?
  • Che dire della punteggiatura all'interno di una parola?
  • Cosa succede alle lettere maiuscole?

Le risposte a queste domande aiutano a definire bene la lingua. Ora vai avanti e scrivi il programma. Congratulazioni, hai appena scritto un compilatore.

Che ne dici di questo: puoi scrivere un programma che prende una serie di istruzioni di disegno e genera un file PNG (o JPEG)? Forse qualcosa del genere:

image 100 100
background black
color red
line 20 55 93 105
color green
box 0 0 99 99

Ancora una volta, dovrai pensare un po 'per definire la lingua:

  • Quali sono le istruzioni primitive?
  • Cosa viene dopo la parola "linea"? Cosa viene dopo "color"? Allo stesso modo per "sfondo", "scatola", ecc.
  • Cos'è un numero?
  • È consentito un file di input vuoto?
  • Va bene scrivere in maiuscolo le parole?
  • Sono ammessi numeri negativi?
  • Cosa succede se non dai la direttiva "immagine"?
  • Va bene non specificare un colore?

Naturalmente, ci sono più domande a cui rispondere, ma se riesci a inchiodarle, hai definito una lingua. Il programma che scrivi per fare la traduzione è, indovina, un compilatore.

Vedi, scrivere un compilatore non è poi così difficile. I compilatori che hai usato in Java o C sono solo versioni più grandi di questi due esempi. Quindi provaci! Definisci una lingua semplice e scrivi un programma per fare in modo che quella lingua faccia qualcosa. Prima o poi vorrai estendere la tua lingua. Ad esempio, potresti voler aggiungere variabili o espressioni aritmetiche. Il tuo compilatore diventerà più complesso ma capirai ogni cosa perché l'hai scritto tu stesso. Ecco come nascono le lingue e i compilatori.


7
myFirstCompiler = (str) -> ("" + (str || "")). split (''). reverse (). join (''); jsfiddle.net/L7qSr
Larry Battle,

21

Se sei interessato alla progettazione del compilatore, dai un'occhiata al Dragon Book (titolo ufficiale: compilatori: principi, tecniche e strumenti). È ampiamente considerato come un libro classico su questo argomento.


4
Nota, potresti aver bisogno di un'esperienza un po 'più attuale per ottenere il massimo da questo libro. Grande riferimento, però.

13
-1 Solo qualcuno che non l'ha letto può pensare che il libro dei draghi sia utile. e in particolare non affronta la questione.
Neil Butterworth,

33
The Dragon Book? Per un entusiasta quindicenne? Preferirei che mantenga il suo entusiasmo ancora per un po '.
David Thornley,

1
Un'alternativa più accessibile: "Programming Pragmatics" 3e .
Willjcroz,

@DavidThornley Non contarlo completamente (Sì, mi rendo conto che questo è un post molto vecchio). Ho iniziato a ricercare come funzionano le lingue all'età di 15 anni e mi sono concentrato in particolare sulle macchine virtuali. Ora ho 16 anni e dopo mesi di ricerca, scrittura e riscrittura ho un interprete e un compilatore di lavoro di cui sono contento.
David,


10

Non credere che ci sia qualcosa di magico in un compilatore o in un sistema operativo: non esiste. Ricordi i programmi che hai scritto per contare tutte le vocali in una stringa o sommare i numeri in un array? Un compilatore non è diverso nel concetto; è solo molto più grande.

Ogni programma ha tre fasi:

  1. leggi alcune cose
  2. elaborare quel materiale: tradurre i dati di input in dati di output
  3. scrivere alcune altre cose: i dati di output

Pensaci: cos'è l'input per il compilatore? Una stringa di caratteri da un file di origine.

Cosa viene generato dal compilatore? Una stringa di byte che rappresentano le istruzioni della macchina per il computer di destinazione.

Qual è la fase di "processo" del compilatore? Cosa fa quella fase?

Se consideri che il compilatore - come qualsiasi altro programma - deve includere queste tre fasi, avrai una buona idea di come è costruito un compilatore.


3
Come diceva Neil, vero ma non utile. Gli aspetti fondamentali del compilatore come una grammatica ricorsiva e le tabelle dei simboli non sono intuitivamente ovvi.
Mason Wheeler,

1
@Mason Wheeler: Penso che chiunque aspiri realisticamente a scrivere un compilatore (e progettare la lingua di destinazione?) Molto probabilmente penserebbe che le tabelle di grammatica e simboli ricorsive fossero concetti piuttosto basilari.
FumbleFingers

8

Non sono un esperto, ma ecco la mia pugnalata:

Non sembri chiedere di scrivere un compilatore, solo un assemblatore. Questo non è davvero magico.

Rubando la risposta di qualcun altro da SO ( https://stackoverflow.com/questions/3826692/how-do-i-translate-assembly-to-binary ), il montaggio è simile al seguente:

label:  LDA #$00
        JMP label

Quindi lo esegui attraverso un assemblatore e lo trasformi in qualcosa del genere:

$A9 $00
$4C $10 $00

Solo è tutto schiacciato, in questo modo:

$A9 $00 $4C $10 $00

Non è davvero magico.

Non puoi scriverlo nel blocco note, perché il blocco note utilizza ASCII (non esadecimale). Utilizzeresti un editor esadecimale, o semplicemente scriveresti i byte in modo programmatico. Scrivi esadecimale in un file, chiamalo "a.exe" o "a.out", quindi dici al sistema operativo di eseguirlo.

Certo, le moderne CPU e sistemi operativi sono davvero piuttosto complicati, ma questa è l'idea di base.

Se vuoi scrivere un nuovo compilatore, ecco come è fatto:

1) Scrivi una lingua interpretata usando qualcosa come l'esempio della calcolatrice in pyparsing (o qualsiasi altro buon framework di analisi). Ciò ti consentirà di accelerare le basi dell'analisi.

2) Scrivi un traduttore. Traduci la tua lingua in, per esempio, Javascript. Ora la tua lingua verrà eseguita in un browser.

3) Scrivi un traduttore a qualcosa di livello inferiore, come LLVM, C o Assembly.

Puoi fermarti qui, questo è un compilatore. Non è un compilatore ottimizzante, ma non era questa la domanda. Potrebbe anche essere necessario prendere in considerazione la scrittura di un linker e un assemblatore, ma vuoi davvero?

4) (Insane) Scrivi un ottimizzatore. Grandi team lavorano per decenni su questo.

4) (Sane) Fatti coinvolgere in una comunità esistente. GCC, LLVM, PyPy, il team principale che lavora su qualsiasi interprete.


8

Molti altri hanno dato risposte eccellenti. Aggiungerò solo qualche altro suggerimento. Innanzitutto, un buon libro per quello che stai cercando di fare sono i testi di implementazione del compilatore moderno di Appel (scegli tra C , Java o ML standard ). Questo libro illustra l'implementazione completa di un compilatore per un linguaggio semplice, Tiger, all'assemblaggio MIPS che può essere eseguito in un emulatore, insieme a una libreria di supporto runtime minima. Per un singolo passaggio attraverso tutto il necessario per far funzionare un linguaggio compilato, è un bel libro 1 .

Appel ti spiegherà come compilare un linguaggio che viene pre-progettato, ma non dedica molto tempo a ciò che significano le varie caratteristiche del linguaggio o a come pensarci in termini di meriti relativi per progettare il tuo. Per questo aspetto, Linguaggi di programmazione: concetti e costrutti è decente. Concetti, tecniche e modelli di programmazione per computer è anche un buon libro per riflettere profondamente sulla progettazione linguistica, sebbene lo faccia nel contesto di una sola lingua ( Oz ).

Infine, ho menzionato che Appel ha il suo testo in C, Java e ML standard - se sei serio sulla costruzione di compilatori e sui linguaggi di programmazione, ti consiglio di imparare ML e usare quella versione di Appel. Le lingue della famiglia ML hanno sistemi di tipo forte che sono prevalentemente funzionali - caratteristiche che saranno diverse da molte altre lingue, quindi impararle se non si conosce già una lingua funzionale affina il proprio linguaggio. Inoltre, le loro mentalità di adattamento dei modelli e funzionali sono estremamente adatte ai tipi di manipolazioni che devi fare spesso in un compilatore, quindi i compilatori scritti in linguaggi basati su ML sono in genere molto più brevi e più facili da comprendere rispetto ai compilatori scritti in C, Java o lingue simili. Libro di Harpersu Standard ML è una guida abbastanza buona per iniziare; lavorandoci sopra dovrebbe prepararti ad affrontare il libro di implementazione del compilatore ML standard di Appel. Se impari ML standard, sarà anche abbastanza facile prendere OCaml per i lavori successivi; IMO, ha strumenti migliori per il programmatore che lavora (si integra in modo più pulito con l'ambiente del sistema operativo circostante, produce facilmente programmi eseguibili e ha alcuni spettacolari strumenti di compilazione come ulex e Menhir).


1 Per riferimento a lungo termine, preferisco il Dragon Book, in quanto contiene maggiori dettagli su cose a cui probabilmente farò riferimento come il funzionamento interno degli algoritmi di analisi e ha una più ampia copertura di approcci diversi, ma il libro di Appel è molto buono per un primo passaggio. Fondamentalmente, Appel ti insegna un modo per fare le cose per tutto il compilatore e ti guida attraverso di esso. Dragon Book tratta diverse alternative di design in modo più dettagliato, ma fornisce molte meno indicazioni su come far funzionare qualcosa.


Modificato : sostituire il riferimento Aho errato con Sethi, menzionare CTMCP.


Ho avuto Essentials Of Programming Languages ​​per la mia classe di interpreti del college. È stato terribile. Mi piace persino lo schema personalmente e non mi preoccupo della sintassi, sono stati gli autori a spiegare male i concetti che mi hanno rovinato.
Greg Guida,

Mi piace la compilazione di Appel con continuazioni, ma ho scoperto che i suoi libri hanno assunto molte conoscenze precedenti.
Jon Harrop

6

Ho dovuto creare un compilatore per la lezione al college.

Le basi per fare questo non sono così complicate come si potrebbe pensare. Il primo passo è creare la tua grammatica. Pensa alla grammatica della lingua inglese. Allo stesso modo puoi analizzare una frase se ha un soggetto e un predicato. Per ulteriori informazioni su questo leggi su Grammatiche senza contesto .

Una volta che hai la grammatica giù (le regole della tua lingua), scrivere un compilatore è semplice come seguire queste regole. I compilatori di solito si traducono nel codice macchina, ma a meno che tu non voglia imparare x86, ti suggerisco di guardare MIPS o creare la tua macchina virtuale.

I compilatori in genere hanno due parti, uno scanner e un parser. Fondamentalmente, lo scanner legge il codice e lo separa in token. Il parser esamina la struttura di quei token. Quindi il compilatore passa attraverso e segue alcune regole piuttosto semplici per convertirlo in qualunque codice sia necessario (assembly, codice intermedio come bytecode, ecc.). Se lo scomponi in pezzi sempre più piccoli, questo alla fine non è affatto scoraggiante.

In bocca al lupo!


8
Concettualmente semplice? Sì. Davvero semplice? No.
Neil Butterworth

7
Uhm. Il compilatore, dopo la scansione / analisi, deve eseguire il controllo del tipo / inferenza, ottimizzazione, allocazione dei registri, ecc. Questi passaggi sono tutt'altro che semplici. (Quando si utilizza il codice interpretato, è sufficiente rinviare queste parti alla fase di runtime.)
Macke

Nessun voto da parte mia: mentre i compilatori hanno due parti di base, una è costruire una descrizione astratta del programma (che in genere è suddivisa in scansione e analisi) e l'altra per scrivere di nuovo una versione di quella descrizione astratta in alcuni altra forma (ad es. codice macchina). (Nota a margine : l' ottimizzazione dei compilatori in genere cerca di migliorare la descrizione astratta prima di scriverla, ma è un perfezionamento.)
Donal Fellows

6

Il libro Code di Petzold è un'ottima introduzione ai non tecnici e ai tecnici a partire dai primi principi. È altamente leggibile e vasto nel suo ambito senza impantanarsi troppo.

Ora che ho scritto questo, dovrò rileggerlo.



5

Ci sono risposte eccellenti in questo thread, ma volevo solo aggiungere le mie perché anch'io una volta avevo la stessa domanda. (Inoltre, vorrei sottolineare che il libro suggerito da Joe-Internet è una risorsa eccellente.)

La prima è la domanda su come funziona un computer? Ecco come: Input -> Calcola -> Output.

Prima considera la parte "Calcola". Vedremo in seguito come funzionano Input e Output.

Un computer è essenzialmente costituito da un processore (o CPU) e da un po 'di memoria (o RAM). La memoria è una raccolta di posizioni ciascuna delle quali può memorizzare un numero finito di bit e ciascuna di tali posizioni di memoria può essere referenziata da un numero, questo è chiamato l'indirizzo della posizione di memoria. Il processore è un gadget in grado di recuperare dati dalla memoria, eseguire alcune operazioni in base ai dati e riscrivere alcuni dati nella memoria. Come fa il processore a capire cosa leggere e cosa fare dopo aver letto i dati dalla memoria?

Per rispondere a questo, dobbiamo capire la struttura di un processore. Quella che segue è una vista abbastanza semplice. Un processore è essenzialmente costituito da due parti. Uno è un insieme di posizioni di memoria costruite all'interno del processore che fungono da memoria di lavoro. Questi sono chiamati "registri". Il secondo è un gruppo di macchine elettroniche costruite per eseguire determinate operazioni utilizzando i dati nei registri. Esistono due registri speciali chiamati "Contatore di programmi" o PC e "Registro di istruzioni" o ir. Il processore considera la memoria suddivisa in tre parti. La prima parte è la "memoria del programma", che memorizza il programma del computer in esecuzione. Il secondo è la "memoria di dati". Il terzo è usato per alcuni scopi speciali, ne parleremo più avanti. Il contatore del programma contiene la posizione dell'istruzione successiva da leggere dalla memoria del programma. Il contatore di istruzioni contiene un numero che si riferisce all'operazione corrente eseguita. Ogni operazione che può essere eseguita da un processore è indicata da un numero chiamato codice operativo dell'operazione. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Il contatore di istruzioni contiene un numero che si riferisce all'operazione corrente eseguita. Ogni operazione che può essere eseguita da un processore è indicata da un numero chiamato codice operativo dell'operazione. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Il contatore di istruzioni contiene un numero che si riferisce all'operazione corrente eseguita. Ogni operazione che può essere eseguita da un processore è indicata da un numero chiamato codice operativo dell'operazione. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Ogni operazione che può essere eseguita da un processore è indicata da un numero chiamato codice operativo dell'operazione. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Ogni operazione che può essere eseguita da un processore è indicata da un numero chiamato codice operativo dell'operazione. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro. Come funziona essenzialmente un computer è che legge la posizione della memoria a cui fa riferimento il contatore del programma nel registro delle istruzioni (e incrementa il contatore del programma in modo che punti alla posizione della memoria dell'istruzione successiva). Successivamente, legge il registro delle istruzioni ed esegue l'operazione desiderata. Ad esempio, l'istruzione potrebbe essere quella di leggere una posizione di memoria specifica in un registro, o di scrivere in un registro o di eseguire alcune operazioni usando i valori di due registri e scrivere l'output in un terzo registro.

Ora come fa il computer a eseguire Input / Output? Fornirò una risposta molto semplificata. Vedi http://en.wikipedia.org/wiki/Input/output e http://en.wikipedia.org/wiki/Interrupt. per più. Usa due cose, quella terza parte della memoria e qualcosa chiamato Interrupts. Ogni dispositivo collegato a un computer deve essere in grado di scambiare dati con il processore. Lo fa usando la terza parte della memoria menzionata in precedenza. Il processore alloca una porzione di memoria a ciascun dispositivo e il dispositivo e il processore comunicano tramite quella fetta di memoria. Ma come fa il processore a sapere quale posizione si riferisce a quale dispositivo e quando un dispositivo deve scambiare dati? È qui che arrivano gli interrupt. Un interrupt è essenzialmente un segnale al processore per mettere in pausa ciò che è attualmente e salvare tutti i suoi registri in una posizione nota e quindi iniziare a fare qualcos'altro. Ci sono molti interrupt, ognuno è identificato da un numero univoco. Ad ogni interruzione è associato un programma speciale. Quando si verifica l'interrupt, il processore esegue il programma corrispondente all'interruzione. Ora, a seconda del BIOS e di come i dispositivi hardware sono collegati alla scheda madre del computer, ogni dispositivo riceve un interrupt unico e una porzione di memoria. Durante l'avvio del sistema operativo con l'aiuto del BIOS determina la posizione dell'interrupt e della memoria di ciascun dispositivo e imposta i programmi speciali per l'interrupt per gestire correttamente i dispositivi. Pertanto, quando un dispositivo necessita di alcuni dati o desidera inviare alcuni dati, segnala un interrupt. Il processore mette in pausa ciò che sta facendo, gestisce l'interrupt e quindi torna a quello che sta facendo. Esistono molti tipi di interrupt, come ad esempio per l'hdd, la tastiera, ecc. Uno importante è il timer di sistema, che invoca un interrupt a intervalli regolari. Inoltre ci sono codici operativi che possono attivare interrupt, chiamati interrupt software.

Ora possiamo quasi capire come funziona un sistema operativo. Quando si avvia, il sistema operativo imposta un interrupt del timer, in modo da dare il controllo del sistema operativo a intervalli regolari. Configura anche altri interrupt per gestire altri dispositivi ecc. Ora, quando il computer esegue un sacco di programmi e si verifica l'interruzione del timer, il sistema operativo acquisisce il controllo ed esegue compiti importanti come la gestione dei processi, la gestione della memoria, ecc. un modo astratto per i programmi di accedere ai dispositivi hardware, piuttosto che consentire loro di accedere direttamente ai dispositivi. Quando un programma vuole accedere a un dispositivo, chiama un codice fornito dal sistema operativo che comunica con il dispositivo. C'è molta teoria coinvolta in questi che si occupa di concorrenza, thread, blocchi, gestione della memoria ecc.

Ora, in teoria, si può scrivere un programma usando direttamente gli opcode. Questo è ciò che viene chiamato codice macchina. Questo è ovviamente molto doloroso. Ora un linguaggio assembly per il processore non è altro che mnemonica per questi codici operativi, che semplifica la scrittura di programmi. Un semplice assemblatore è un programma che prende un programma scritto in assembly e sostituisce i mnemonici con i codici operativi appropriati.

Come si fa a progettare un processore e un linguaggio assembly. Per sapere che devi leggere alcuni libri sull'architettura del computer. (vedere i capitoli 1-7 del libro a cui fa riferimento joe-internet). Ciò comporta l'apprendimento dell'algebra booleana, come costruire semplici circuiti combinatori da aggiungere, moltiplicare ecc., Come costruire memoria e circuiti sequenziali, come costruire un microprocessore e così via.

Ora come si scrivono lingue di computer. Si potrebbe iniziare scrivendo un semplice assemblatore nel codice macchina. Quindi usa quell'assemblatore per scrivere un compilatore per un semplice sottoinsieme di C. Quindi usa quel sottoinsieme di C per scrivere una versione più completa di C. Infine usa C per scrivere un linguaggio più complicato come Python o C ++. Ovviamente per scrivere una lingua devi prima progettarla (allo stesso modo in cui desideri un processore). Guarda ancora alcuni libri di testo su questo.

E come si scrive un SO. Per prima cosa scegli come target una piattaforma come x86. Quindi capisci come si avvia e quando verrà invocato il tuo sistema operativo. Un tipico PC si avvia in questo modo. Si avvia e il BIOS esegue alcuni test. Quindi il BIOS legge il primo settore dell'hdd e carica il contenuto in una posizione specifica nella memoria. Quindi imposta la CPU per iniziare l'esecuzione di questi dati caricati. Questo è il punto in cui il sistema operativo viene invocato. Un tipico sistema operativo a questo punto carica la memoria rimanente. Quindi inizializza i dispositivi e imposta altre cose e infine ti saluta con la schermata di accesso.

Quindi per scrivere un sistema operativo è necessario scrivere il "boot-loader". Quindi è necessario scrivere il codice per gestire gli interrupt e i dispositivi. Quindi è necessario scrivere tutto il codice per la gestione dei processi, la gestione dei dispositivi, ecc. Quindi è necessario scrivere un API che consenta ai programmi in esecuzione nel sistema operativo di accedere ai dispositivi e ad altre risorse. E infine devi scrivere il codice che legge un programma dal disco, lo imposta come processo e inizia a eseguirlo.

Naturalmente la mia risposta è chiaramente semplificata e probabilmente di scarsa utilità pratica. A mia difesa, ora sono uno studente laureato in teoria, quindi ho dimenticato molte di queste cose. Ma puoi google un sacco di queste cose e saperne di più.


4

Ricordo un punto della mia carriera di programmatore quando ero in uno stato di confusione simile al tuo: avevo letto un po 'di teoria, il libro del Drago, il libro della Tigre (rosso), ma non avevo ancora molto un indizio su come mettere tutto insieme.

Ciò che lo ha legato è stato trovare un progetto concreto da fare (e poi scoprire che avevo solo bisogno di un piccolo sottoinsieme di tutta la teoria).

La VM Java mi ha fornito un buon punto di partenza: concettualmente è un "processore" ma è fortemente astratto dai dettagli disordinati delle CPU reali. Offre anche una parte importante e spesso trascurata del processo di apprendimento: smontare le cose prima di rimetterle insieme (come ai vecchi tempi dei bambini con i set radio).

Gioca con un decompilatore e Hello, classe mondiale in Java. Leggi le specifiche JVM e prova a capire cosa sta succedendo. Questo ti darà una visione approfondita di ciò che sta facendo il compilatore .

Quindi gioca con il codice che crea la classe Hello, World. (In effetti stai creando un compilatore specifico per l'applicazione, per un linguaggio altamente specializzato in cui puoi solo dire Ciao, Mondo.)

Prova a scrivere codice che sarà in grado di leggere in Hello, World scritto in un'altra lingua e di generare la stessa classe. Fallo in modo da poter cambiare la stringa da "Hello, World" a qualcos'altro.

Ora prova a compilare (in Java) una classe che calcola alcune espressioni aritmetiche, come "2 * (3 + 4)". Smonta questa classe, scrivi un "compilatore di giocattoli" che può rimetterlo insieme.


3

1) Grandi lezioni video dell'Università di Washington:

Costruzione del compilatore CSE P 501 - Autunno 2009 www.cs.washington.edu/education/courses/csep501/09au/lectures/video.html *

2) SICP http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/ E il libro con lo stesso nome. Questo è in realtà un obbligo per qualsiasi ingegnere del software là fuori.

3) Inoltre, sulla programmazione funzionale, Haskell, calcolo lambda, semantica (incluso denotazionale) e implementazione del compilatore per linguaggi funzionali. Puoi iniziare da 2005-SS-FP.V10.2005-05-24.HDV se conosci già Haskell. I video di Uxx sono risposte. Si prega di seguire prima i video Vxx .

http://video.s-inf.de/#FP.2005-SS-Giesl.(COt).HD_Videoaufzeichnung

(i video sono in inglese, ma altri corsi sono in tedesco).

  • i nuovi utenti possono pubblicare solo un massimo di due collegamenti ipertestuali.

3

ANTLR è un buon punto di partenza. È un framework che genera linguaggio, simile a Lex e Yacc. C'è una GUI chiamata ANTLRWorks che semplifica il processo.

Nel mondo .NET esiste il Dynamic Language Runtime che può essere utilizzato per generare codice nel mondo .NET. Ho scritto un linguaggio di espressioni chiamato Zentrum che genera codice usando il DLR. Ti mostrerà come analizzare ed eseguire espressioni tipizzate staticamente e dinamicamente.


2

Per una semplice introduzione sul funzionamento dei compilatori e su come creare il proprio linguaggio di programmazione, consiglierei il nuovo libro http://createyourproglang.com che si concentra maggiormente sulla teoria della progettazione del linguaggio senza dover conoscere gli interni del sistema operativo / CPU, ad esempio lexer, parser , interpreti, ecc.

Utilizza gli stessi strumenti utilizzati per creare i più recenti linguaggi di programmazione Coffee Script e Fancy .


2

Se tutto ciò che dici è vero, hai il profilo di un promettente ricercatore e una comprensione concreta può essere ottenuta solo in un modo: studiare. E non sto dicendo " Leggi tutti questi libri di informatica di alto livello (specialmente questi ) scritti da questo genio !"; Voglio dire: devi essere con persone di alto livello per essere uno scienziato informatico come Charles Babbage, Alan Turing, Claude Shannon o Dennis Ritchie. Non sto disprezzando le persone autodidatta (sono una di loro) ma non ci sono molte persone come te là fuori. Consiglio vivamente il Symbolic Systems Program (SSP) presso la Stanford University . Come dice il loro sito Web:

Il Symbolic Systems Program (SSP) dell'Università di Stanford si concentra su computer e menti: sistemi artificiali e naturali che usano simboli per rappresentare le informazioni. SSP riunisce studenti e docenti interessati a diversi aspetti della relazione uomo-computer, tra cui ...

  • scienze cognitive : studiare l'intelligenza umana, i linguaggi naturali e il cervello come processi computazionali;
  • intelligenza artificiale : dotare i computer di comportamenti e comprensione simili all'uomo; e
  • interazione uomo-computer : progettazione di software e interfacce che funzionano bene con gli utenti umani.

2

Suggerirò qualcosa di un po 'fuori dal campo di sinistra: impara Python (o forse Ruby, ma ho molta più esperienza in Python, quindi è di questo che parlerò). E non solo dilettarsi in esso, ma davvero conoscerlo a un livello profondo.

Ci sono diversi motivi per cui suggerisco questo:

  1. Python è un linguaggio eccezionalmente ben progettato. Mentre ha alcune verruche, ha meno IMHO di molte altre lingue. Se sei un designer linguistico in erba, è bene esporsi a quante più lingue possibili.

  2. L'implementazione standard di Python (CPython) è open source e ben documentata, rendendo più semplice la comprensione di come funziona il linguaggio.

  3. Python è compilato in un semplice codice byte che è più facile da capire dell'assemblaggio e che funziona allo stesso modo su tutte le piattaforme su cui Python è in esecuzione. Quindi imparerai a conoscere la compilazione (dal momento che Python compila il tuo codice sorgente in codice byte) e l'interpretazione (poiché questo codice byte viene interpretato nella macchina virtuale Python).

  4. Python ha molte nuove funzionalità proposte, documentate in PEP numerate (Python Enhancement Proposals). PEP interessanti da leggere per vedere in che modo i progettisti del linguaggio hanno considerato l'implementazione di una funzione prima di scegliere il modo in cui l'hanno effettivamente fatta. (I PEP ancora in esame sono particolarmente interessanti in questo senso.)

  5. Python ha un mix di funzionalità da vari paradigmi di programmazione, quindi imparerai a conoscere vari modi per affrontare i problemi di risoluzione e avrai una gamma più ampia di strumenti da considerare anche nella tua lingua.

  6. Python rende abbastanza facile estendere la lingua in vari modi con decoratori, metaclassi, ganci di importazione, ecc. In modo da poter giocare con le nuove funzionalità della lingua in una misura senza effettivamente uscire dalla lingua. (A parte questo: i blocchi di codice sono oggetti di prima classe in Ruby, quindi puoi effettivamente scrivere nuove strutture di controllo come i loop! Ho l'impressione che i programmatori di Ruby non considerino necessariamente l'estensione del linguaggio, è solo come programmi in Ruby. Ma è piuttosto bello.)

  7. In Python, puoi effettivamente disassemblare il bytecode generato dal compilatore, o persino scrivere il tuo da zero e far eseguire l'interprete (l'ho fatto da solo, ed è stato strabiliante ma divertente).

  8. Python ha buone librerie per l'analisi. È possibile analizzare il codice Python in un albero di sintassi astratto e quindi manipolarlo utilizzando il modulo AST. Il modulo PyParsing è utile per l'analisi di linguaggi arbitrari, come quelli che si progettano. In teoria potresti scrivere il tuo primo compilatore di lingue in Python se lo desideri (e potrebbe generare output C, assembly o persino Python).

Questo approccio investigativo potrebbe andare bene con un approccio più formale, poiché inizierai a riconoscere i concetti che hai studiato nella lingua con cui stai lavorando e viceversa.

Divertiti!


Non scavare su Python, ma non è questo il punto. Il bambino ha già N lingue per N grande; l'incremento di N non farà molta differenza. Prendi C, per esempio. È standard. Ha molte librerie. È multipiattaforma (quando ti attieni allo standard). È possibile smontare l'output. Puoi scrivere CFront. Ecc. Quindi.
Ian,

1

Bene, penso che la tua domanda potrebbe essere riscritta per essere "Quali sono i concetti pratici fondamentali di una laurea in informatica", e la risposta totale, ovviamente, è quella di ottenere la tua laurea in Informatica.

Fondamentalmente, crei il tuo compilatore del linguaggio di programmazione leggendo un file di testo, estraendo informazioni da esso ed eseguendo trasformazioni sul testo in base alle informazioni che hai letto da esso, fino a quando non lo hai trasformato in byte che possono essere letti da il caricatore (cfr. Linker e Loader di Levine). Un banale compilatore è un progetto abbastanza rigoroso quando fatto per la prima volta.

Il cuore di un sistema operativo è il kernel, che gestisce le risorse (ad es. Allocazione / deallocazione della memoria) e passa da attività / processi / programmi.

Un assemblatore è una trasformazione text-> byte.

Se siete interessati a queste cose, suggerirei di scrivere un assemblatore X86, in Linux, che supporti alcuni sottogruppi dell'assembly X86 standard. Sarà un punto di ingresso abbastanza semplice e ti introdurrà a questi problemi. Non è un progetto per bambini e ti insegnerà molte cose.

Consiglierei di scriverlo in C; C è la lingua franca per quel livello di lavoro.


1
D'altra parte, questo è un posto eccellente per un linguaggio di altissimo livello. Finché è possibile dettare i singoli byte in un file, è possibile creare un compilatore / assemblatore (che è più semplice) in qualsiasi lingua. Dì, perl. O VBA. Cieli, le possibilità!
Ian,

1

Vedi il libro di Kenneth Louden, "Compiler Construction"

http://www.cs.sjsu.edu/~louden/cmptext/

Fornisce un approccio pratico migliore allo sviluppo del compilatore.

Le persone imparano facendo. Solo un piccolo numero può vedere i simboli scarabocchiati sul tabellone e saltare immediatamente dalla teoria alla pratica. Sfortunatamente, queste persone sono spesso dogmatiche, fondamentaliste e le più rumorose al riguardo.


1

Sono stato benedetto per essere stato esposto al PDP-8 come mio primo linguaggio di assemblaggio. Il PDP-8 aveva solo sei istruzioni, che erano così semplici che era facile immaginarle implementate da alcuni componenti discreti, che in realtà erano. Ha davvero rimosso la "magia" dai computer.

Un altro gateway per la stessa rivelazione è il linguaggio di assemblaggio "mix" che Knuth usa nei suoi esempi. "Mix" sembra arcaico oggi, ma ha ancora quell'effetto mistificante.


0

Compilatori e linguaggi di programmazione (e tutto ciò che comprende la costruzione di uno, come la definizione di una grammatica finita e la conversione in assembly) è un compito molto complesso che richiede una grande comprensione dei sistemi nel loro insieme. Questo tipo di corso è in genere offerto come classe Comp Sci 3 ° / 4 ° anno all'università.

Consiglio vivamente di comprendere prima i sistemi operativi in ​​generale e come vengono compilati / eseguiti i linguaggi esistenti (ad es. Nativamente (C / C ++), in una VM (Java) o da un interprete (Python / Javascript)).

Credo che abbiamo usato il libro Concetti sul sistema operativo di Abraham Silberschatz, Peter B. Galvin, Greg Gagne nel mio corso di Sistemi operativi (nel 2 ° anno). Questo è stato un libro eccellente che ha fornito una panoramica dettagliata di ogni componente di un sistema operativo: un po 'caro ma ne vale la pena e le copie vecchie / usate dovrebbero fluttuare intorno.


Concetti di sistema operativo? Basta poco per costruire un compilatore. Ciò che serve è la comprensione delle architetture software: indirizzi spazi, stack, thread (se vuole imparare i compilatori, meglio conoscere il parallelismo, è il suo futuro).
Ira Baxter,

Immediatamente dopo aver detto che voleva imparare la progettazione del linguaggio e i compilatori, ha detto che voleva conoscere i sistemi operativi.
David Thornley,

@Ira - d'accordo. Non ho mai affermato che per costruire un compilatore / linguaggio sia necessaria la comprensione del sistema operativo, ho semplicemente spiegato che potrebbe essere un punto di partenza più semplice. Tutti si stanno concentrando sull'aspetto "compilatore" della sua domanda, ma ha anche detto che vuole una migliore comprensione del sistema operativo e delle librerie. Per un quindicenne che sta ancora imparando a conoscere le architetture, sarebbe molto più utile comprendere la gestione della memoria, il threading, il blocco, i / o, ecc. Piuttosto che imparare a definire una grammatica con yacc (IMHO)
plafond

Scusate ... ho perso il punto di voler conoscere (costruire?) Sistemi operativi. Il mio punto è: non ha bisogno di molta conoscenza del sistema operativo per i compilatori. In effetti, è praticamente un argomento completamente diverso, tranne dove il compilatore e il sistema operativo interagiscono per raggiungere uno scopo collettivo. (Multics ha richiesto ai suoi compilatori PL / 1 di creare chiamate di funzione in determinati modi per abilitare una VM globale, per esempio).
Ira Baxter,

0

È un argomento importante, ma piuttosto che spazzarti via con un pomposo "vai a leggere un libro, ragazzo", invece ti darò volentieri suggerimenti per aiutarti a avvolgerti la testa.

La maggior parte dei compilatori e / o interpreti lavorano in questo modo:

Tokenize : scansiona il testo del codice e lo divide in un elenco di token.

Questo passaggio può essere complicato perché non puoi semplicemente dividere la stringa su spazi, devi riconoscere che if (bar) foo += "a string";è un elenco di 8 token: WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR. Come puoi vedere, semplicemente dividere il codice sorgente sugli spazi non funzionerà, devi leggere ogni carattere come una sequenza, quindi se incontri un carattere alfanumerico continui a leggere i caratteri fino a quando non colpisci un carattere non alfanico e quella stringa che appena letto è una WORD che verrà ulteriormente classificata in seguito. Puoi decidere tu stesso quanto granulare è il tuo tokenizer: se ingoia "a string"come un token chiamato STRING_LITERAL per essere ulteriormente analizzato in seguito, o se vede"a string" come OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE o qualsiasi altra cosa, questa è solo una delle tante scelte che devi decidere tu stesso mentre lo stai codificando.

Lex : Quindi ora hai un elenco di token. Probabilmente hai taggato alcuni token con una classificazione ambigua come WORD perché durante il primo passaggio non fai troppi sforzi cercando di capire il contesto di ogni stringa di caratteri. Quindi ora leggi di nuovo il tuo elenco di token di origine e riclassifica ciascuno dei token ambigui con un tipo di token più specifico basato sulle parole chiave nella tua lingua. Quindi hai una WORD come "if", e "if" è nel tuo elenco di parole chiave speciali chiamato simbolo IF, quindi cambi il tipo di simbolo di quel token da WORD a IF e qualsiasi WORD che non è nell'elenco delle parole chiave speciali , come WORD foo, è un IDENTIFICATORE.

Parse : ora hai trasformato if (bar) foo += "a string";un elenco di token lexed che assomiglia a questo: IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. Il passaggio consiste nel riconoscere sequenze di token come istruzioni. Questo sta analizzando. Puoi farlo usando una grammatica come:

STATEMENT: = ASIGN_EXPRESSION | IF_STATEMENT

IF_STATEMENT: = IF, PAREN_EXPRESSION, STATEMENT

ASIGN_EXPRESSION: = IDENTIFICATORE, ASIGN_OP, VALUE

PAREN_EXPRESSSION: = OPEN_PAREN, VALUE, CLOSE_PAREN

VALORE: = IDENTIFICATORE | STRING_LITERAL | PAREN_EXPRESSION

ASIGN_OP: = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT

Le produzioni che usano "|" tra termini significa "corrisponde a uno di questi", se sono presenti virgole tra termini significa "corrisponde a questa sequenza di termini"

Come lo usi? A partire dal primo token, prova ad abbinare la sequenza di token a queste produzioni. Quindi, prima provi a far corrispondere la tua lista di token con STATEMENT, quindi leggi la regola per STATEMENT e dice "uno STATEMENT è un ASIGN_EXPRESSION o un IF_STATEMENT", quindi provi prima ad abbinare ASIGN_EXPRESSION, quindi cerchi la regola grammaticale per ASIGN_EXPRESSION e dice "ASIGN_EXPRESSION è un IDENTIFICATORE seguito da un ASIGN_OP seguito da un VALORE, quindi cerchi la regola grammaticale per IDENTIFIER e vedi che non esiste un ruke grammaticale per IDENTIFIER in modo che significhi che IDENTIFIER sia un" terminale ", il che significa che non richiede ulteriori analizzando per abbinarlo in modo da poter provare ad abbinarlo direttamente con il token, ma il primo token di origine è un IF e IF non è lo stesso di un IDENTIFICATORE, pertanto la corrispondenza non è riuscita. E adesso? Torna alla regola STATEMENT e prova a trovare il termine successivo: IF_STATEMENT. Cerca IF_STATEMENT, inizia con IF, cerca IF, IF è un terminale, confronta il terminale con il tuo primo token, corrispondenze token IF, fantastico continua, il prossimo termine è PAREN_EXPRESSION, cerca PAREN_EXPRESSION, non è un terminale, qual è il primo termine, PAREN_EXPRESSION inizia con OPEN_PAREN, cerca OPEN_PAREN, è un terminale, abbina OPEN_PAREN al tuo prossimo token, corrisponde, ... e così via.

Il modo più semplice per avvicinarti a questo passaggio è avere una funzione chiamata parse () che gli passi il token del codice sorgente che stai cercando di abbinare e il termine grammaticale con cui stai cercando di abbinarlo. Se il termine grammaticale non è un terminale, allora si ricorre: si chiama di nuovo parse () passandogli lo stesso token sorgente e il primo termine di questa regola grammaticale. Questo è il motivo per cui è chiamato un "parser di discendenza ricorsiva" La funzione parse () restituisce (o modifica) la posizione corrente nella lettura dei token di origine, in sostanza restituisce l'ultimo token nella sequenza abbinata e si continua la chiamata successiva a parse () da lì.

Ogni volta che parse () corrisponde a una produzione come ASIGN_EXPRESSION crei una struttura che rappresenta quel pezzo di codice. Questa struttura contiene riferimenti ai token di origine originali. Inizi a costruire un elenco di queste strutture. Chiameremo questa intera struttura l'albero astratto di sintassi (AST)

Compila e / o Esegui : per determinate produzioni nella tua grammatica hai creato funzioni di gestione che, se assegnate una struttura AST, compilerebbero o eseguiranno quel blocco di AST.

Quindi diamo un'occhiata al pezzo del tuo AST che ha il tipo ASIGN_ADD. Quindi come interprete hai una funzione ASIGN_ADD_execute (). Questa funzione viene passata come parte dell'AST che corrisponde all'albero di analisi per foo += "a string", quindi questa funzione esamina quella struttura e sa che il primo termine nella struttura deve essere un IDENTIFICATORE, e il secondo termine è il VALORE, quindi ASIGN_ADD_execute () passa il termine VALUE a una funzione VALUE_eval () che restituisce un oggetto che rappresenta il valore valutato in memoria, quindi ASIGN_ADD_execute () esegue una ricerca di "pippo" nella tabella delle variabili e memorizza un riferimento a tutto ciò che è stato restituito da eval_value () funzione.

Questo è un interprete. Un compilatore avrebbe invece le funzioni del gestore che traducono l'AST in codice byte o codice macchina anziché eseguirlo.

I passaggi da 1 a 3 e alcuni 4 possono essere semplificati utilizzando strumenti come Flex e Bison. (alias Lex e Yacc), ma scrivere da soli un interprete è probabilmente l'esercizio più potente che un programmatore possa realizzare. Tutte le altre sfide di programmazione sembrano insignificanti dopo il summit di questo.

Il mio consiglio è di iniziare in piccolo: un linguaggio minuscolo, con una grammatica minuscola, e provare ad analizzare ed eseguire alcune semplici affermazioni, quindi crescere da lì.

Leggi questi e buona fortuna!

http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c

http://en.wikipedia.org/wiki/Recursive_descent_parser


2
Fai quello che io considero un classico errore quando la gente pensa alla compilazione: questo è credere che il problema riguardi l'analisi. IL PARSING È TECNICAMENTE FACILE; ci sono grandi tecnologie per farlo. La parte difficile della compilazione è l'analisi semantica, l'ottimizzazione a livelli alti e bassi di rappresentazione del programma e la generazione di codice, con crescente enfasi in questi giorni sul codice PARALLEL. Lo banalizzi completamente nella tua risposta: "un compilatore avrebbe funzioni di gestione per tradurre l'AST in codice byte". Ci sono 50 anni trascorsi di teoria dei compilatori e ingegneria nascosti lì dentro.
Ira Baxter,

0

Il campo informatico è complicato solo perché ha avuto il tempo di evolversi in molte direzioni. Al suo centro si tratta solo di macchine che calcolano.

Il mio computer preferito di base è il Relay Computer di Harry Porter . Dà un'idea di come funziona un computer al livello base. Quindi puoi iniziare ad apprezzare perché sono necessarie cose come lingue e sistemi operativi.

Il fatto è che è difficile capire qualcosa senza capire di cosa ha bisogno . Buona fortuna e non solo leggere cose. Fai cose.



-1

Un altro buon libro introduttivo è "Compilerbau" di N. Wirth del 1986 (costruzione del compilatore) che è lungo circa 100 pagine e spiega un codice conciso e ben progettato per il linguaggio giocattolo PL / 0, incluso parser, generatore di codice e macchina virtuale. Mostra anche come scrivere un parser che legge in grammatica per analizzare la notazione EBNF. Il libro è in tedesco ma ho scritto un riassunto e tradotto il codice in Python come esercizio, vedi http://www.d12k.org/cmplr/w86/intro.html .


-1

Se sei interessato a comprendere l'essenza dei linguaggi di programmazione, ti suggerirei di lavorare attraverso il libro PLAI (http://www.cs.brown.edu/~sk/Publications/Books/ProgLangs/) per comprendere i concetti e la loro attuazione. Ti aiuterà anche con il design della tua lingua.


-1

Se hai davvero interessi nel compilatore e non l'hai mai fatto prima, potresti iniziare progettando una calcolatrice per il calcolo di formule aritmetiche (una sorta di DSL come menzionato da Eric). Ci sono molti aspetti che dovresti considerare per questo tipo di compilatore:

  • Numeri consentiti
  • Operatori ammessi
  • Le priorità dell'operatore
  • Convalida della sintassi
  • Meccanismo di ricerca variabile
  • Rilevazione del ciclo
  • Ottimizzazione

Ad esempio, hai le seguenti formule, la tua calcolatrice dovrebbe essere in grado di calcolare il valore di x:

a = 1
b = 2
c = a + b
d = (3 + b) * c
x = a - d / b

Non è un compilatore estremamente difficile per cominciare, ma potrebbe farti pensare più ad alcune idee di base su cosa sia un compilatore, e anche aiutarti a migliorare le tue capacità di programmazione e controllare la qualità del tuo codice (questo in realtà è un problema perfetto che Test Driven Development TDD potrebbe applicarsi per migliorare la qualità del software).

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.