it-swarm.dev

Wie erstelle ich meine eigene Programmiersprache und einen Compiler dafür?

Ich bin gründlich mit Programmierung vertraut und bin auf Sprachen gestoßen, darunter BASIC, FORTRAN, COBOL, LISP, LOGO, Java, C++, C, MATLAB, Mathematica, Python, Ruby, Perl, JavaScript, Assembly und so weiter. Ich kann nicht verstehen, wie Leute Programmiersprachen erstellen und Compiler dafür entwickeln. Ich konnte auch nicht verstehen, wie Leute Betriebssysteme wie Windows, Mac, UNIX, DOS und so weiter erstellen. Das andere, was mir rätselhaft ist, ist, wie Leute Bibliotheken wie OpenGL, OpenCL, OpenCV, Cocoa, MFC usw. erstellen. Das Letzte, was ich nicht herausfinden kann, ist, wie Wissenschaftler eine Assemblersprache und einen Assembler für einen Mikroprozessor entwickeln. Ich würde all diese Dinge wirklich gerne lernen und bin 15 Jahre alt. Ich wollte immer ein Informatiker sein wie Babbage, Turing, Shannon oder Dennis Ritchie.


Ich habe bereits Ahos Compiler Design und Tanenbaums OS-Konzeptbuch gelesen und alle diskutieren Konzepte und Code nur auf hohem Niveau. Sie gehen nicht auf Details und Nuancen ein und darauf, wie man einen Compiler oder ein Betriebssystem entwickelt. Ich möchte ein konkretes Verständnis, damit ich selbst eines erstellen kann und nicht nur ein Verständnis dafür, was ein Thread, ein Semaphor, ein Prozess oder eine Analyse ist. Ich habe meinen Bruder danach gefragt. Er ist ein SB-Student in EECS bei MIT und hat keine Ahnung, wie man all diese Dinge in der realen Welt erstellt. Alles, was er weiß, ist nur ein Verständnis von Compiler-Design und Betriebssystem Konzepte wie die, die ihr erwähnt habt (z. B. Thread, Synchronisation, Parallelität, Speicherverwaltung, Lexikalische Analyse, Zwischencodegenerierung usw.)

427
Dave

Grundsätzlich lautet Ihre Frage: "Wie werden Computerchips, Befehlssätze, Betriebssysteme, Sprachen, Bibliotheken und Anwendungen entworfen und implementiert?" Das ist eine weltweite Milliarden-Dollar-Industrie, in der Millionen von Menschen beschäftigt sind, von denen viele Spezialisten sind. Vielleicht möchten Sie Ihre Frage etwas mehr fokussieren.

Das heißt, ich kann einen Sprung machen bei:

Ich kann nicht verstehen, wie Leute Programmiersprachen erstellen und Compiler dafür entwickeln.

Es ist überraschend für mich, aber viele Leute betrachten Programmiersprachen als magisch. Wenn ich Leute auf Partys oder was auch immer treffe, wenn sie mich fragen, was ich mache, sage ich ihnen, dass ich Programmiersprachen entwerfe und die Compiler und Tools implementiere, und es ist überraschend, wie oft Leute - wohlgemerkt professionelle Programmierer - sagen "Wow, ich habe nie darüber nachgedacht, aber ja, jemand muss diese Dinge entwerfen." Es ist, als hätten sie gedacht, dass Sprachen nur vollständig mit Werkzeuginfrastrukturen um sie herum entstehen.

Sie erscheinen nicht nur. Sprachen werden wie jedes andere Produkt entworfen: indem sorgfältig eine Reihe von Kompromissen zwischen konkurrierenden Möglichkeiten geschlossen werden. Die Compiler und Tools sind wie jedes andere professionelle Softwareprodukt aufgebaut: indem Sie das Problem auflösen, jeweils eine Codezeile schreiben und dann das resultierende Programm auf den Prüfstand stellen.

Sprachdesign ist ein großes Thema. Wenn Sie an der Gestaltung einer Sprache interessiert sind, sollten Sie zunächst überlegen, welche Mängel in einer Sprache bestehen, die Sie bereits kennen. Konstruktionsentscheidungen ergeben sich häufig aus der Berücksichtigung eines Konstruktionsfehlers in einem anderen Produkt.

Alternativ können Sie eine Domäne in Betracht ziehen, an der Sie interessiert sind, und dann eine domänenspezifische Sprache (DSL) entwerfen, die Lösungen für Probleme in dieser Domäne angibt. Sie haben LOGO erwähnt. Das ist ein großartiges Beispiel für ein DSL für die Domäne "Strichzeichnung". Reguläre Ausdrücke sind DSL für die Domäne "Finde ein Muster in einer Zeichenfolge". LINQ in C #/VB ist ein DSL für die Domäne "Daten filtern, verbinden, sortieren und projektieren". HTML ist ein DSL für die Domäne "Beschreiben des Layouts von Text auf einer Seite" usw. Es gibt viele Domänen, die für sprachbasierte Lösungen geeignet sind. Einer meiner Favoriten ist Inform7, ein DSL für die Domäne "Textbasiertes Abenteuerspiel". Es ist wahrscheinlich die seriöseste Programmiersprache auf höchstem Niveau, die ich je gesehen habe. Wählen Sie eine Domain aus, über die Sie etwas wissen, und überlegen Sie, wie Sie mithilfe der Sprache Probleme und Lösungen in dieser Domain beschreiben können.

Wenn Sie skizziert haben, wie Ihre Sprache aussehen soll, versuchen Sie, genau die Regeln für die Bestimmung eines legalen und illegalen Programms aufzuschreiben. Normalerweise möchten Sie dies auf drei Ebenen tun:

  1. lexikalisch: Wie lauten die Regeln für Wörter in der Sprache, welche Zeichen sind legal, wie sehen Zahlen aus und so weiter.
  2. syntaktisch: Wie verbinden sich Wörter der Sprache zu größeren Einheiten? In C # sind größere Einheiten Dinge wie Ausdrücke, Anweisungen, Methoden, Klassen usw.
  3. semantisch: Wie können Sie bei einem syntaktisch legalen Programm herausfinden, was das Programm macht?

Schreiben Sie diese Regeln auf so genau wie möglich. Wenn Sie das gut machen, können Sie dies als Grundlage für das Schreiben eines Compilers oder Interpreters verwenden. Schauen Sie sich die C # -Spezifikation oder die ECMAScript-Spezifikation an, um zu sehen, was ich meine. Sie sind voll von sehr präzisen Regeln, die beschreiben, was ein Rechtsprogramm ausmacht und wie man herausfindet, was man tut.

Eine der besten Möglichkeiten, um mit dem Schreiben eines Compilers zu beginnen, besteht darin, einen Hochsprachen-zu-Hochsprachen-Compiler zu schreiben. Schreiben Sie einen Compiler, der Zeichenfolgen in Ihrer Sprache aufnimmt und Zeichenfolgen in C # oder JavaScript oder einer anderen Sprache ausspuckt, die Sie gerade kennen. Lassen Sie den Compiler für diese Sprache sich dann darum kümmern, sie in ausführbaren Code umzuwandeln.

Ich schreibe einen Blog über das Design von C #, VB, VBScript, JavaScript und anderen Sprachen und Tools. Wenn Sie dieses Thema interessiert, probieren Sie es aus. http://blogs.msdn.com/ericlippert (historisch) und http://ericlippert.com (aktuell)

Insbesondere könnte Sie diesen Beitrag interessant finden; Hier liste ich die meisten Aufgaben auf, die der C # -Compiler während seiner semantischen Analyse für Sie ausführt. Wie Sie sehen können, gibt es viele Schritte. Wir teilen das große Analyseproblem in eine Reihe von Problemen auf, die wir individuell lösen können.

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

Wenn Sie auf der Suche nach einem Job sind, der diese Dinge erledigt, wenn Sie älter sind, sollten Sie als College-Praktikant zu Microsoft kommen und versuchen, in die Entwicklerabteilung einzusteigen. So bin ich heute zu meinem Job gekommen!

407
Eric Lippert

Vielleicht finden Sie Lets Build a Compiler von Jack Crenshaw eine interessante Einführung in das Schreiben von Compilern und die Assemblersprache.

Der Autor hielt es sehr einfach und konzentrierte sich auf den Aufbau der tatsächlichen Funktionalität.

127
user1249

"Ich würde wirklich dieses Zeug gerne lernen". Wenn Sie es langfristig ernst meinen:

  • Gehen Sie aufs College und spezialisieren Sie sich auf Software-Engineering. Nehmen Sie jede Compiler-Klasse, die Sie bekommen können. Die Leute, die den Unterricht anbieten, sind besser ausgebildet und erfahrener als Sie. Es ist gut, wenn die Expertenperspektiven verwendet werden, um Ihnen die Informationen auf eine Weise zu präsentieren, die Sie beim Lesen von Code niemals erhalten.

  • Bleiben Sie während der High School beim Mathematikunterricht und bleiben Sie alle 4 Jahre auf dem College. Fokus auf nicht standardisierte Mathematik: Logik, Gruppentheorie, Metamathematik. Dies wird Sie zwingen, abstrakt zu denken. Auf diese Weise können Sie die fortgeschrittenen theoretischen Arbeiten zum Zusammenstellen lesen und verstehen, warum diese Theorien interessant und nützlich sind. Sie können diese fortgeschrittenen Theorien ignorieren, wenn Sie für immer hinter dem Stand der Technik stehen wollen.

  • Sammeln/lesen Sie die Standard-Compiler-Texte: Aho/Ullman usw. Sie enthalten das, was die Community im Allgemeinen als grundlegend einstuft. Möglicherweise verwenden Sie nicht alles aus diesen Büchern, aber Sie sollten wissen, dass es existiert, und Sie sollten wissen, warum Sie es nicht verwenden. Ich fand Muchnick großartig, aber es ist für ziemlich fortgeschrittene Themen.

  • Erstellen Sie einen Compiler. Beginnen Sie JETZT, indem Sie einen faulen bauen. Dies wird Ihnen einige Probleme beibringen. Baue einen zweiten. Wiederholen. Diese Erfahrung schafft enorme Synergien mit Ihrem Buchlernen.

  • Ein guter Anfang ist, sich mit BNF (Backus Naur Form), Parsern und Parser-Generatoren vertraut zu machen. BNF wird im Compiler-Land effektiv universell eingesetzt, und Sie können nicht realistisch mit Ihren Compilerkollegen sprechen, wenn Sie es nicht wissen.

Wenn Sie eine gute erste Einführung in das Kompilieren und den direkten Wert von BNF nicht nur für die Dokumentation, sondern auch als werkzeugverarbeitbare Metasprache wünschen, lesen Sie dieses Tutorial (nicht meins) zum Erstellen von "Meta" -Compilern (Compilern) die Compiler bauen) basierend auf einem Artikel aus 1964 (ja, Sie haben das richtig gelesen) ["META II eine syntaxorientierte Compiler-Schreibsprache" von Val Schorre. (http://doi.acm.org/10.1145/800257.808896)] Dieses IMHO ist eines der besten Comp-Sci-Papiere, die jemals geschrieben wurden: Es lehrt Sie, Compiler-Compiler auf 10 Seiten zu erstellen. Ich habe anfangs aus dieser Arbeit gelernt.

Was ich oben geschrieben habe, ist viel aus persönlicher Erfahrung, und ich denke, es hat mir ziemlich gute Dienste geleistet. YMMV, aber meiner Meinung nach nicht viel.

72
Ira Baxter

Hier ist ein Online-Buch/Kurs, dem Sie folgen können: Die Elemente von Computersystemen: Aufbau eines modernen Computers nach ersten Prinzipien .

Mit Simulatoren bauen Sie tatsächlich ein komplettes Computersystem von Grund auf auf. Während viele Kommentatoren angegeben haben, dass Ihre Frage zu weit gefasst ist, beantwortet dieses Buch sie tatsächlich und bleibt dabei sehr überschaubar. Wenn Sie fertig sind, haben Sie ein Spiel in einer Hochsprache (die Sie entworfen haben) geschrieben, die die Funktionalität Ihres eigenen Betriebssystems verwendet und in eine VM Sprache (das) kompiliert wird Sie haben) von Ihrem Compiler entworfen, der von Ihrem VM Übersetzer) in eine Assemblersprache (die Sie entworfen haben) übersetzt wird, die von Ihrem Assembler, der ausgeführt wird, in Maschinencode (den Sie entworfen haben) zusammengesetzt wird auf Ihrem Computersystem, das Sie aus Chips zusammengesetzt haben, die Sie mithilfe von Boolescher Logik und einer einfachen Hardwarebeschreibungssprache entworfen haben.

Die Kapitel:

  1. Kursüberblick
  2. Boolesche Logik
  3. Kombinatorische Chips
  4. Sequentielle Chips
  5. Maschinensprache
  6. Rechnerarchitektur
  7. Assembler
  8. Virtuelle Maschine I: Arithmetik
  9. Virtuelle Maschine II: Steuerung
  10. Programmiersprache
  11. Compiler I: Syntaxanalyse
  12. Compiler II: Codegenerierung
  13. Betriebssystem
  14. Listenpunkt

Mehr Spaß zu gehen

46
Joe Internet

Geh einen Schritt zurück. Ein Compiler ist einfach ein Programm, das ein Dokument in einer Sprache in ein Dokument in einer anderen Sprache übersetzt. Beide Sprachen sollten klar definiert und spezifisch sein.

Die Sprachen müssen keine Programmiersprachen sein. Sie können jede Sprache sein, deren Regeln aufgeschrieben werden können. Sie haben wahrscheinlich gesehen Google Translate ; Das ist ein Compiler, weil er eine Sprache (z. B. Deutsch) in eine andere (vielleicht Japanisch) übersetzen kann.

Ein weiteres Beispiel für einen Compiler ist eine HTML-Rendering-Engine. Die Eingabe ist eine HTML-Datei und die Ausgabe besteht aus einer Reihe von Anweisungen zum Zeichnen der Pixel auf dem Bildschirm.

Wenn die meisten Leute über einen Compiler sprechen, beziehen sie sich normalerweise auf ein Programm, das eine Programmiersprache auf hoher Ebene (wie Java, C, Prolog) in eine Programmiersprache auf niedriger Ebene (Assembly- oder Maschinencode) übersetzt. Das kann entmutigend sein. Aber es ist nicht so schlimm, wenn man die Ansicht eines Generalisten vertritt, dass ein Compiler ein Programm ist, das eine Sprache in eine andere übersetzt.

Können Sie ein Programm schreiben, das jedes Wort in einer Zeichenfolge umkehrt? Zum Beispiel:

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

wird

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

Das ist kein schwer zu schreibendes Programm, aber Sie müssen über einige Dinge nachdenken:

  • Was ist ein "Wort"? Können Sie definieren, aus welchen Zeichen ein Wort besteht?
  • Wo beginnen und enden Wörter?
  • Sind Wörter nur durch ein Leerzeichen getrennt oder kann es mehr oder weniger geben?
  • Muss die Interpunktion auch umgekehrt werden?
  • Was ist mit Interpunktion innerhalb eines Wortes?
  • Was passiert mit Großbuchstaben?

Die Antworten auf diese Fragen helfen dabei, die Sprache klar zu definieren. Schreiben Sie nun das Programm. Herzlichen Glückwunsch, Sie haben gerade einen Compiler geschrieben.

Wie wäre es damit: Können Sie ein Programm schreiben, das eine Reihe von Zeichenanweisungen verwendet und eine PNG- (oder JPEG-) Datei ausgibt? Vielleicht so etwas:

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

Auch hier müssen Sie einige Überlegungen anstellen, um die Sprache zu definieren:

  • Was sind die primitiven Anweisungen?
  • Was kommt nach dem Wort "Zeile"? Was kommt nach "Farbe"? Ebenso für "Hintergrund", "Box" usw.
  • Was ist eine Nummer?
  • Ist eine leere Eingabedatei erlaubt?
  • Ist es in Ordnung, die Wörter groß zu schreiben?
  • Sind negative Zahlen erlaubt?
  • Was passiert, wenn Sie die Direktive "image" nicht angeben?
  • Ist es in Ordnung, keine Farbe anzugeben?

Natürlich gibt es noch mehr Fragen zu beantworten, aber wenn Sie sie festnageln können, haben Sie eine Sprache definiert. Das Programm, das Sie für die Übersetzung schreiben, ist vermutlich ein Compiler.

Sie sehen, das Schreiben eines Compilers ist nicht so schwierig. Die Compiler, die Sie in Java oder C verwendet haben, sind nur größere Versionen dieser beiden Beispiele. Also machen Sie es! Definieren Sie eine einfache Sprache und schreiben Sie ein Programm, damit diese Sprache etwas tut. Früher oder später Später möchten Sie Ihre Sprache erweitern. Beispielsweise möchten Sie möglicherweise Variablen oder arithmetische Ausdrücke hinzufügen. Ihr Compiler wird komplexer, aber Sie werden alles verstehen, weil Sie es selbst geschrieben haben. So entstehen Sprachen und Compiler.

46
Barry Brown

Wenn Sie sich für das Compiler-Design interessieren, lesen Sie das Dragon Book (offizieller Titel: Compiler: Prinzipien, Techniken und Tools). Es wird allgemein als klassisches Buch zu diesem Thema angesehen.

21
Brian Agnew

Glauben Sie nicht, dass ein Compiler oder ein Betriebssystem etwas Magisches hat: Es gibt nichts Magisches. Erinnern Sie sich an die Programme, die Sie geschrieben haben, um alle Vokale in einer Zeichenfolge zu zählen oder die Zahlen in einem Array zu addieren? Ein Compiler unterscheidet sich im Konzept nicht. es ist nur viel größer.

Jedes Programm besteht aus drei Phasen:

  1. lese ein paar Sachen
  2. verarbeiten Sie das Zeug: Übersetzen Sie die Eingabedaten in die Ausgabedaten
  3. schreibe ein paar andere Sachen - die Ausgabedaten

Denken Sie darüber nach: Was wird in den Compiler eingegeben? Eine Zeichenfolge aus einer Quelldatei.

Was wird vom Compiler ausgegeben? Eine Folge von Bytes, die Maschinenanweisungen an den Zielcomputer darstellen.

Was ist also die "Prozess" -Phase des Compilers? Was macht diese Phase?

Wenn Sie bedenken, dass der Compiler - wie jedes andere Programm - diese drei Phasen enthält, haben Sie eine gute Vorstellung davon, wie ein Compiler aufgebaut ist.

10
Pete Wilson

"Lassen Sie uns einen Compiler bauen" wurde bereits vorgeschlagen. Es gibt eine "modernisierte" Version, die Haskell anstelle von Turbo Pascal verwendet: http://alephnullplex.appspot.com/blog/view/2010/01/12/lbach-1-introduction

In Anlehnung an Haskell gibt es einen sehr lehrreichen Scheme-Interpreter, der weitere Ideen geben könnte: Schreiben Sie sich ein Schema in 48 Stunden

10
Landei

Ich bin kein Experte, aber hier ist mein Stich:

Sie scheinen nicht nach einem Compiler zu fragen, sondern nur nach einem Assembler. Das ist nicht wirklich magisch.

Jemand anderes zu stehlen, antwortet von SO ( https://stackoverflow.com/questions/3826692/how-do-i-translate-Assembly-to-binary ), Die Montage sieht folgendermaßen aus:

label:  LDA #$00
        JMP label

Dann führen Sie es durch einen Assembler und verwandeln sich in so etwas:

$A9 $00
$4C $10 $00

Nur ist alles so zusammengedrückt:

$A9 $00 $4C $10 $00

Es ist wirklich keine Magie.

Sie können das nicht in den Editor schreiben, da der Editor ASCII (nicht hex)) verwendet. Sie würden einen Hex-Editor verwenden oder die Bytes einfach programmgesteuert ausschreiben. Sie schreiben dieses Hex in eine Datei Nennen Sie es "a.exe" oder "a.out" und weisen Sie das Betriebssystem an, es auszuführen.

Natürlich sind moderne CPUs und Betriebssysteme sehr kompliziert, aber das ist die Grundidee.

Wenn Sie einen neuen Compiler schreiben möchten, gehen Sie wie folgt vor:

1) Schreiben Sie eine interpretierte Sprache mit dem Taschenrechner-Beispiel in Pyparsing (oder einem anderen guten Parsing-Framework). Damit sind Sie mit den Grundlagen des Parsens vertraut.

2) Schreiben Sie einen Übersetzer. Übersetzen Sie Ihre Sprache beispielsweise in Javascript. Jetzt wird Ihre Sprache in einem Browser ausgeführt.

3) Schreiben Sie einen Übersetzer auf eine niedrigere Ebene wie LLVM, C oder Assembly.

Sie können hier aufhören, dies ist ein Compiler. Es ist kein optimierender Compiler, aber das war nicht die Frage. Möglicherweise müssen Sie auch einen Linker und Assembler schreiben, aber möchten Sie das wirklich?

4) (Wahnsinnig) Schreiben Sie einen Optimierer. Daran arbeiten seit Jahrzehnten große Teams.

4) (Gesund) Beteiligen Sie sich an einer bestehenden Community. GCC, LLVM, PyPy, das Kernteam, das an jedem Dolmetscher arbeitet.

8
wisty

Einige andere haben ausgezeichnete Antworten gegeben. Ich werde nur noch ein paar Vorschläge hinzufügen. Ein gutes Buch für das, was Sie versuchen, sind zunächst Appels Modern Compiler-Implementierungstexte (wählen Sie C , Java = oder Standard ML ). Dieses Buch führt Sie durch eine vollständige Implementierung eines Compilers für eine einfache Sprache, Tiger, zu MIPS Assembly, der in einem Emulator ausgeführt werden kann, zusammen mit einer minimalen Laufzeitunterstützungsbibliothek. Für einen einzigen Durchgang durch alles, was notwendig ist, damit eine kompilierte Sprache funktioniert, ist es ein ziemlich gutes Buch1.

Appel führt Sie durch das Kompilieren einer vorgefertigten Sprache, verbringt jedoch nicht viel Zeit damit, was verschiedene Sprachfunktionen bedeuten oder wie Sie sie in Bezug auf ihre relativen Vorzüge für das Entwerfen Ihrer eigenen Sprache betrachten. Für diesen Aspekt ist Programmiersprachen: Konzepte & Konstrukte anständig. Konzepte, Techniken und Modelle der Computerprogrammierung ist auch ein gutes Buch, um tief über Sprachdesign nachzudenken, obwohl dies im Kontext einer einzelnen Sprache geschieht ( Oz ).

Schließlich erwähnte ich, dass Appel seinen Text in C, Java und Standard ML hat. Wenn Sie es mit Compiler-Konstruktions- und Programmiersprachen ernst meinen, empfehle ich, ML zu lernen und diese Version von Appel zu verwenden. Die Sprachen der ML-Familie haben starke Typensysteme, die vorwiegend funktionsfähig sind - Funktionen, die sich von vielen anderen Sprachen unterscheiden. Wenn Sie sie also nicht kennen, wenn Sie noch keine funktionale Sprache kennen, wird dies Ihr Sprachhandwerk verbessern. Außerdem eignen sich ihre Musteranpassungs- und funktionalen Denkweisen hervorragend für die Arten von Manipulationen, die Sie häufig in einem Compiler ausführen müssen. Daher sind in ML-basierten Sprachen geschriebene Compiler in der Regel viel kürzer und leichter zu verstehen als in C geschriebene Compiler. Java oder ähnliche Sprachen. Harpers Buch on Standard ML ist eine ziemlich gute Anleitung, um Ihnen den Einstieg zu erleichtern. Wenn Sie dies durcharbeiten, sollten Sie sich darauf vorbereiten, das Implementierungsbuch für den Standard ML-Compiler von Appel zu übernehmen. Wenn Sie Standard ML lernen, ist es auch ziemlich einfach, OCaml für spätere Arbeiten zu erwerben. IMO, es hat bessere Tools für den arbeitenden Programmierer (lässt sich sauberer in die umgebende Betriebssystemumgebung integrieren, erstellt problemlos ausführbare Programme und verfügt über einige spektakuläre Tools zum Erstellen von Compilern wie ulex und Menhir).


1Zum langfristigen Nachschlagen bevorzuge ich das Drachenbuch, da es mehr Details zu den Dingen enthält, auf die ich mich wahrscheinlich beziehe, wie zum Beispiel das Innenleben von Parser-Algorithmen, und eine breitere Abdeckung verschiedener Ansätze bietet, aber Appels Buch ist sehr gut für ein erster Durchgang. Grundsätzlich lehrt Sie Appel einen Weg, Dinge den ganzen Weg durch den Compiler zu erledigen, und führt Sie durch diesen. Das Drachenbuch behandelt verschiedene Designalternativen ausführlicher, bietet jedoch weitaus weniger Anleitungen, wie etwas zum Laufen gebracht werden kann.


Bearbeitet : Ersetzen Sie die falsche Aho-Referenz durch Sethi und erwähnen Sie CTMCP.

8

Ich musste einen Compiler für den Unterricht im College erstellen.

Die Grundlagen dafür sind nicht so kompliziert, wie Sie denken würden. Der erste Schritt besteht darin, Ihre Grammatik zu erstellen. Denken Sie an die Grammatik der englischen Sprache. Auf die gleiche Weise können Sie einen Satz analysieren, wenn er ein Thema und ein Prädikat enthält. Weitere Informationen hierzu finden Sie unter Context Free Grammars .

Sobald Sie die Grammatik (die Regeln Ihrer Sprache) festgelegt haben, ist das Schreiben eines Compilers so einfach wie das Befolgen dieser Regeln. Compiler werden normalerweise in den Maschinencode übersetzt. Wenn Sie jedoch nicht x86 lernen möchten, sollten Sie sich MIPS ansehen oder Ihre eigene virtuelle Maschine erstellen.

Compiler bestehen normalerweise aus zwei Teilen, einem Scanner und einem Parser. Grundsätzlich liest der Scanner den Code ein und teilt ihn in Token auf. Der Parser untersucht die Struktur dieser Token. Dann geht der Compiler durch und folgt einigen ziemlich einfachen Regeln, um ihn in den Code zu konvertieren, in dem er sein soll (Assembly, Zwischencode wie Bytecode usw.). Wenn Sie es in immer kleinere Teile zerlegen, ist dies letztendlich überhaupt nicht entmutigend.

Viel Glück!

6
Jerr

Petzolds Buch Code ist eine großartige Einführung in Nicht-Techniker und Technikfreaks, beginnend mit den ersten Prinzipien. Es ist gut lesbar und umfangreich, ohne zu sehr ins Stocken zu geraten.

Nachdem ich das geschrieben habe, muss ich es noch einmal lesen.

6
Kevin Won

Es gibt ausgezeichnete Antworten in diesem Thread, aber ich wollte nur meine hinzufügen, da auch ich einmal die gleiche Frage hatte. (Außerdem möchte ich darauf hinweisen, dass das von Joe-Internet vorgeschlagene Buch eine hervorragende Ressource ist.)

Zunächst stellt sich die Frage, wie ein Computer funktioniert. So geht's: Eingabe -> Berechnen -> Ausgabe.

Betrachten Sie zunächst den Teil „Berechnen“. Wir werden uns später ansehen, wie Eingabe und Ausgabe funktionieren.

Ein Computer besteht im Wesentlichen aus einem Prozessor (oder einer CPU) und einem Speicher (oder RAM). Der Speicher ist eine Sammlung von Speicherorten, von denen jeder eine endliche Anzahl von Bits speichern kann, und jeder dieser Speicherorte kann selbst durch eine Zahl referenziert werden, die als Adresse des Speicherorts bezeichnet wird. Der Prozessor ist ein Gadget, das Daten abrufen kann Führen Sie aus dem Speicher einige Operationen basierend auf den Daten aus und schreiben Sie einige Daten zurück in den Speicher. Wie findet der Prozessor heraus, was zu lesen ist und was zu tun ist, nachdem die Daten aus dem Speicher gelesen wurden?

Um dies zu beantworten, müssen wir die Struktur eines Prozessors verstehen. Das Folgende ist eine ziemlich einfache Ansicht. Ein Prozessor besteht im Wesentlichen aus zwei Teilen. Eine davon ist eine Reihe von Speicherplätzen im Prozessor, die als Arbeitsspeicher dienen. Diese werden als "Register" bezeichnet. Die zweite ist eine Reihe von elektronischen Maschinen, die gebaut wurden, um bestimmte Operationen unter Verwendung der Daten in den Registern auszuführen. Es gibt zwei spezielle Register, die als "Programmzähler" oder "PC" und "Befehlsregister" oder "ir" bezeichnet werden. Der Prozessor betrachtet den Speicher als in drei Teile unterteilt. Der erste Teil ist der „Programmspeicher“, in dem das ausgeführte Computerprogramm gespeichert ist. Der zweite ist der "Datenspeicher". Der dritte wird für einige spezielle Zwecke verwendet, wir werden später darüber sprechen. Der Programmzähler enthält die Position der nächsten Anweisung, die aus dem Programmspeicher gelesen werden soll. Der Anweisungszähler enthält eine Nummer, die sich auf die aktuell ausgeführte Operation bezieht. Auf jede Operation, die ein Prozessor ausführen kann, wird durch eine Nummer verwiesen, die als Opcode der Operation bezeichnet wird. Ein Computer arbeitet im Wesentlichen so, dass er den vom Programmzähler referenzierten Speicherplatz in das Befehlsregister liest (und den Programmzähler so erhöht, dass er auf den Speicherplatz des nächsten Befehls zeigt). Als nächstes liest es das Befehlsregister und führt die gewünschte Operation aus. Zum Beispiel könnte der Befehl sein, einen bestimmten Speicherort in ein Register zu lesen oder in ein Register zu schreiben oder eine Operation unter Verwendung der Werte von zwei Registern durchzuführen und die Ausgabe in ein drittes Register zu schreiben.

Wie führt der Computer nun die Eingabe/Ausgabe durch? Ich werde eine sehr vereinfachte Antwort geben. Siehe http://en.wikipedia.org/wiki/Input/output und http://en.wikipedia.org/wiki/Interrupt . für mehr. Es werden zwei Dinge verwendet, der dritte Teil des Speichers und etwas, das Interrupts genannt wird. Jedes an einen Computer angeschlossene Gerät muss Daten mit dem Prozessor austauschen können. Dies geschieht unter Verwendung des dritten Teils des zuvor erwähnten Speichers. Der Prozessor weist jedem Gerät eine Speicherscheibe zu, und das Gerät und der Prozessor kommunizieren über diese Speicherscheibe. Aber woher weiß der Prozessor, welcher Standort sich auf welches Gerät bezieht und wann ein Gerät Daten austauschen muss? Hier kommen Interrupts ins Spiel. Ein Interrupt ist im Wesentlichen ein Signal an den Prozessor, den aktuellen Stand anzuhalten, alle Register an einem bekannten Ort zu speichern und dann etwas anderes zu tun. Es gibt viele Interrupts, die jeweils durch eine eindeutige Nummer gekennzeichnet sind. Für jeden Interrupt ist ein spezielles Programm zugeordnet. Wenn der Interrupt auftritt, führt der Prozessor das dem Interrupt entsprechende Programm aus. Abhängig vom BIOS und der Art und Weise, wie die Hardwaregeräte mit dem Computer-Motherboard verbunden sind, erhält jedes Gerät einen eindeutigen Interrupt und einen Speicherbereich. Während des Startvorgangs ermittelt das Betriebssystem mithilfe des BIOS den Interrupt und den Speicherort jedes Geräts und richtet die speziellen Programme für den Interrupt ein, um die Geräte ordnungsgemäß zu handhaben. Wenn ein Gerät Daten benötigt oder Daten senden möchte, signalisiert es einen Interrupt. Der Prozessor hält an, was er tut, behandelt den Interrupt und kehrt dann zu dem zurück, was er tut. Es gibt viele Arten von Interrupts, z. B. für die Festplatte, die Tastatur usw. Ein wichtiger ist der System-Timer, der in regelmäßigen Abständen einen Interrupt aufruft. Es gibt auch Opcodes, die Interrupts auslösen können, sogenannte Software-Interrupts.

Jetzt können wir fast verstehen, wie ein Betriebssystem funktioniert. Wenn es hochfährt, richtet das Betriebssystem einen Timer-Interrupt ein, so dass das Betriebssystem in regelmäßigen Abständen gesteuert wird. Es werden auch andere Interrupts eingerichtet, um andere Geräte usw. zu handhaben. Wenn der Computer nun eine Reihe von Programmen ausführt und der Timer-Interrupt auftritt, erhält das Betriebssystem die Kontrolle und führt wichtige Aufgaben wie Prozessverwaltung, Speicherverwaltung usw. aus Eine abstrakte Möglichkeit für die Programme, auf die Hardwaregeräte zuzugreifen, anstatt sie direkt auf Geräte zugreifen zu lassen. Wenn ein Programm auf ein Gerät zugreifen möchte, ruft es einen vom Betriebssystem bereitgestellten Code auf, der dann mit dem Gerät kommuniziert. Es gibt eine Menge Theorie, die sich mit Parallelität, Threads, Sperren, Speicherverwaltung usw. befasst.

Nun kann man theoretisch ein Programm direkt mit Opcodes schreiben. Dies wird als Maschinencode bezeichnet. Das ist offensichtlich sehr schmerzhaft. Jetzt ist eine Assemblersprache für den Prozessor nichts anderes als eine Mnemonik für diese Opcodes, was das Schreiben von Programmen erleichtert. Ein einfacher Assembler ist ein Programm, das ein in Assembly geschriebenes Programm verwendet und die Mnemonik durch die entsprechenden Opcodes ersetzt.

Wie entwirft man einen Prozessor und eine Assemblersprache? Um zu wissen, dass Sie einige Bücher über Computerarchitektur lesen müssen. (siehe Kapitel 1-7 des Buches, auf das sich Joe-Internet bezieht). Dies beinhaltet das Erlernen der Booleschen Algebra, das Erstellen einfacher kombinatorischer Schaltkreise zum Hinzufügen, Multiplizieren usw., das Erstellen von Speicher- und sequentiellen Schaltkreisen, das Erstellen eines Mikroprozessors usw.

Wie schreibt man nun Computer-Sprachen? Man könnte damit beginnen, einen einfachen Assembler in Maschinencode zu schreiben. Verwenden Sie dann diesen Assembler, um einen Compiler für eine einfache Teilmenge von C zu schreiben. Verwenden Sie dann diese Teilmenge von C, um eine vollständigere Version von C zu schreiben. Verwenden Sie schließlich C, um eine kompliziertere Sprache wie python = oder C++. Um eine Sprache zu schreiben, müssen Sie sie natürlich zuerst entwerfen (genauso wie Sie einen Prozessor abwägen). Schauen Sie sich noch einmal einige Lehrbücher dazu an.

Und wie schreibt man ein Betriebssystem? Zuerst zielen Sie auf eine Plattform wie x86. Dann finden Sie heraus, wie es startet und wann Ihr Betriebssystem aufgerufen wird. Ein typischer PC bootet auf diese Weise. Es startet und BIOS führt einige Tests durch. Dann liest das BIOS den ersten Sektor der Festplatte und lädt den Inhalt an eine bestimmte Stelle im Speicher. Anschließend wird die CPU so eingerichtet, dass diese geladenen Daten ausgeführt werden. Dies ist der Punkt, an dem Sie aufgerufen werden. Ein typisches Betriebssystem lädt zu diesem Zeitpunkt den Rest seines Speichers. Dann werden die Geräte initialisiert und andere Dinge eingerichtet, und schließlich werden Sie mit dem Anmeldebildschirm begrüßt.

Um ein Betriebssystem zu schreiben, müssen Sie den "Bootloader" schreiben. Dann müssen Sie Code schreiben, um die Interrupts und Geräte zu behandeln. Dann müssen Sie den gesamten Code für die Prozessverwaltung, Geräteverwaltung usw. schreiben. Anschließend müssen Sie eine API schreiben, mit der die in Ihrem Betriebssystem ausgeführten Programme auf Geräte und andere Ressourcen zugreifen können. Und schließlich müssen Sie Code schreiben, der ein Programm von der Festplatte liest, es als Prozess einrichtet und mit der Ausführung beginnt.

Natürlich ist meine Antwort offen vereinfacht und wahrscheinlich von geringem praktischem Nutzen. Zu meiner Verteidigung bin ich jetzt ein Doktorand in Theorie, daher habe ich viele dieser Dinge vergessen. Aber Sie können viele dieser Dinge googeln und mehr herausfinden.

5
dubyaman

Vielleicht möchten Sie diese hervorragende Frage (und Antworten) auf StackOverflow überprüfen: Lernen, einen Compiler zu schreiben . Es enthält eine breite Liste von Ressourcen.

5
Angry Lettuce

Ich kann mich an einen Punkt in meiner Programmierkarriere erinnern, als ich in einem ähnlichen Zustand der Verwirrung war wie Sie: Ich hatte die Theorie ziemlich viel gelesen, das Drachenbuch, das Tigerbuch (rot), aber immer noch nicht viel davon ein Hinweis, wie man alles zusammensetzt.

Was es zusammenbrachte, war, ein konkretes Projekt zu do zu finden (und dann herauszufinden, dass ich nur eine kleine Teilmenge der gesamten Theorie brauchte).

Das Java VM hat mir einen guten Ausgangspunkt gegeben: Es ist konzeptionell ein "Prozessor", aber es ist stark von den chaotischen Details der tatsächlichen CPUs abstrahiert. Es bietet auch Ein wichtiger und oft übersehener Teil des Lernprozesses: Dinge auseinander nehmen, bevor sie wieder zusammengesetzt werden (wie Kinder früher mit Funkgeräten gearbeitet haben).

Spielen Sie mit einem Dekompiler und der Hello, World-Klasse in Java. Lesen Sie die JVM-Spezifikation und versuchen Sie zu verstehen, was los ist. Dies gibt Ihnen einen fundierten Einblick in das, was der Compiler ist tun.

Spielen Sie dann mit Code herum, der erstellt die Hallo, Weltklasse. (Tatsächlich erstellen Sie einen anwendungsspezifischen Compiler für eine hochspezialisierte Sprache, in der Sie nur Hallo, Welt sagen können.)

Versuchen Sie, Code zu schreiben, der in Hello, World in einer anderen Sprache gelesen werden kann, und geben Sie dieselbe Klasse aus. Machen Sie es so, dass Sie die Zeichenfolge von "Hallo Welt" in etwas anderes ändern können.

Versuchen Sie nun, (in Java) eine Klasse zu kompilieren, die einen arithmetischen Ausdruck berechnet, z. B. "2 * (3 + 4)". Nehmen Sie diese Klasse auseinander, schreiben Sie einen "Spielzeug-Compiler", der sie wieder zusammensetzen kann.

4
Morendil

1) Großartige Videovorträge von der University of Washington:

CSE P 501 Compilerkonstruktion - Herbst 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/ Und das gleichnamige Buch. Dies ist eigentlich eine Pflicht für jeden Softwareentwickler da draußen.

3) Auch über funktionale Programmierung, Haskell, Lambda-Kalkül, Semantik (einschließlich Denotational) und Compiler-Implementierung für funktionale Sprachen. Sie können von 2005-SS-FP.V10.2005-05-24.HDV beginnen, wenn Sie Haskell bereits kennen. Uxx Videos sind Antworten. Bitte folgen Sie zuerst Vxx Videos.

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

(Videos sind auf Englisch, andere Kurse sind auf Deutsch.)

  • neue Benutzer können maximal zwei Hyperlinks posten.
3
Zura

ANTLR ist ein guter Ausgangspunkt. Es ist ein sprachgenerierendes Framework, ähnlich wie Lex und Yacc. Es gibt eine GUI namens ANTLRWorks , die den Prozess vereinfacht.

In der .NET-Welt gibt es die Dynamic Language Runtime , mit der Code in der .NET-Welt generiert werden kann. Ich habe eine Ausdruckssprache namens Zentrum geschrieben, die Code über das DLR generiert. Es zeigt Ihnen, wie Sie statisch und dynamisch typisierte Ausdrücke analysieren und ausführen.

3
Sean

Wenn alles, was Sie sagen, wahr ist, haben Sie das Profil eines vielversprechenden Forschers, und ein konkretes Verständnis kann nur auf eine Weise erreicht werden: studieren. Und ich sage nicht " Lies all diese hochrangigen Informatikbücher (speziell diese ) geschrieben von diesem Genie !"; Ich meine: Sie müssen mit hochrangigen Leuten zusammen sein, um Informatiker wie Charles Babbage, Alan Turing, Claude Shannon oder Dennis Ritchie zu sein. Ich verachte keine Autodidakten (ich bin einer von ihnen), aber es gibt nicht viele Leute wie Sie da draußen. Ich empfehle ernsthaft Symbolic Systems Program (SSP) at Stanford University . Wie ihre Website sagt:

Das Symbolic Systems Program (SSP) an der Stanford University konzentriert sich auf Computer und Geist: künstliche und natürliche Systeme, die Symbole zur Darstellung von Informationen verwenden. SSP bringt Studenten und Lehrkräfte zusammen, die an verschiedenen Aspekten der Mensch-Computer-Beziehung interessiert sind, darunter ...

  • Kognitionswissenschaft : Studium der menschlichen Intelligenz, der natürlichen Sprachen und des Gehirns als Rechenprozesse;
  • Künstliche Intelligenz : Computer mit menschlichem Verhalten und Verständnis ausstatten; und
  • Mensch-Computer-Interaktion : Entwerfen von Computersoftware und Schnittstellen, die gut mit menschlichen Benutzern zusammenarbeiten.
2
quantme

Ich werde etwas außerhalb des linken Feldes vorschlagen: learn Python (oder vielleicht Ruby, aber ich habe viel mehr Erfahrung in Python das ist es also) was ich besprechen werde). Und nicht nur darin herumspielen, sondern es wirklich auf einer tiefen Ebene kennenlernen.

Es gibt mehrere Gründe, warum ich dies vorschlage:

  1. Python ist eine außergewöhnlich gut gestaltete Sprache. Während es ein paar Warzen hat, hat es meiner Meinung nach weniger als viele andere Sprachen. Wenn Sie ein angehender Sprachdesigner sind, ist es gut, sich so vielen guten Sprachen wie möglich auszusetzen.

  2. Die Standardimplementierung von Python (CPython) ist Open Source und gut dokumentiert, sodass Sie leichter verstehen können, wie die Sprache unter der Haube funktioniert.

  3. Python wird zu einem einfachen Bytecode kompiliert, der einfacher zu verstehen ist als Assembly und auf allen Plattformen gleich funktioniert Python läuft weiter. Sie lernen also die Kompilierung kennen (seit Python kompiliert Ihren Quellcode zu Bytecode) und Interpretation (da dieser Bytecode in der virtuellen Maschine Python) interpretiert wird).

  4. Python bietet viele neue Funktionen, die in nummerierten PEPs (Python Enhancement Proposals) dokumentiert sind. PEPs, die interessant zu lesen sind, um zu sehen, wie die Sprachdesigner die Implementierung eines Features in Betracht gezogen haben, bevor sie die Art und Weise gewählt haben, wie sie es tatsächlich getan haben. (PEPs, die noch geprüft werden, sind in dieser Hinsicht besonders interessant.)

  5. Python bietet eine Mischung aus Funktionen aus verschiedenen Programmierparadigmen, sodass Sie verschiedene Möglichkeiten zur Problemlösung kennenlernen und eine größere Auswahl an Tools in Betracht ziehen können, die auch in Ihrer eigenen Sprache verfügbar sind.

  6. Python macht es ziemlich einfach, die Sprache mit Dekoratoren, Metaklassen, Import-Hooks usw. auf verschiedene Arten zu erweitern, sodass Sie in gewissem Umfang mit neuen Sprachfunktionen spielen können, ohne die Sprache tatsächlich zu verlassen. (Nebenbei bemerkt: Codeblöcke sind in Ruby erstklassige Objekte, sodass Sie tatsächlich neue Kontrollstrukturen wie Schleifen schreiben können! Ich habe den Eindruck, dass Ruby Programmierer dies nicht unbedingt berücksichtigen Das ist zwar eine Erweiterung der Sprache, aber genau so programmiert man in Ruby. Aber es ist ziemlich cool.)

  7. In Python können Sie den vom Compiler generierten Bytecode tatsächlich zerlegen oder sogar Ihren eigenen von Grund auf neu schreiben und vom Interpreter ausführen lassen (ich habe das selbst gemacht, und es war umwerfend, aber lustig).

  8. Python hat gute Bibliotheken zum Parsen. Sie können Python Code in einen abstrakten Syntaxbaum analysieren und ihn dann mit dem Modul AST) bearbeiten. Das PyParsing-Modul ist nützlich, um beliebige Sprachen zu analysieren, z. B. solche Sie können theoretisch Ihren ersten Sprachcompiler in Python, wenn Sie möchten) schreiben (und er könnte C, Assembly oder sogar Python Ausgabe) erzeugen.

Dieser Untersuchungsansatz könnte gut zu einem formelleren Ansatz passen, da Sie Konzepte erkennen, die Sie in der Sprache gelernt haben, mit der Sie arbeiten, und umgekehrt.

Habe Spaß!

2
kindall

Für eine einfache Einführung in die Funktionsweise von Compilern und das Erstellen einer eigenen Programmiersprache würde ich das neue Buch http://createyourproglang.com empfehlen, das sich mehr auf die Sprache konzentriert Designtheorie, ohne über OS/CPU-Interna, dh Lexer, Parser, Interpreter usw., Bescheid wissen zu müssen.

Es werden dieselben Tools verwendet, mit denen die kürzlich beliebten Programmiersprachen Coffee Script und Fancy erstellt wurden.

2
mythz

Siehe Kenneth Loudens Buch "Compiler Construction"

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

Es bietet einen besseren praktischen Ansatz für die Compilerentwicklung.

Menschen lernen dabei. Nur eine kleine Anzahl kann Symbole auf der Tafel erkennen und sofort von der Theorie zur Praxis springen. Leider sind diese Leute oft dogmatisch, fundamentalistisch und am lautesten.

1
Jarvis Jones

Nun, ich denke, Ihre Frage könnte so umgeschrieben werden, dass sie lautet: "Was sind die praktischen Kernkonzepte eines Informatik-Abschlusses?", Und die vollständige Antwort lautet natürlich, einen eigenen Bachelor in Informatik zu erhalten.

Grundsätzlich erstellen Sie Ihren eigenen Programmiersprachen-Compiler, indem Sie eine Textdatei lesen, Informationen daraus extrahieren und Transformationen für den Text basierend auf den Informationen durchführen, die Sie daraus gelesen haben, bis Sie ihn in Bytes umgewandelt haben, die von gelesen werden können der Lader (vgl. Linker und Lader von Levine). Ein trivialer Compiler ist beim ersten Mal ein ziemlich strenges Projekt.

Das Herz eines Betriebssystems ist der Kernel, der Ressourcen verwaltet (z. B. Speicherzuweisung/Freigabe) und zwischen Aufgaben/Prozessen/Programmen wechselt.

Ein Assembler ist eine Text-> Byte-Transformation.

Wenn Sie an diesem Material interessiert sind, würde ich vorschlagen, einen X86-Assembler unter Linux zu schreiben, der eine Teilmenge der Standard-X86-Assembly unterstützt. Dies ist ein ziemlich einfacher Einstiegspunkt und führt Sie in diese Themen ein. Es ist kein Babyprojekt und wird Ihnen viele Dinge beibringen.

Ich würde empfehlen, es in C zu schreiben; C ist die Verkehrssprache für dieses Arbeitsniveau.

1
Paul Nathan

Ich war gesegnet, dem PDP-8 als meiner ersten Assemblersprache ausgesetzt zu sein. Der PDP-8 hatte nur sechs Anweisungen, die so einfach waren, dass man sich leicht vorstellen konnte, dass sie von einigen diskreten Komponenten implementiert wurden, die es tatsächlich waren. Es hat wirklich die "Magie" von Computern entfernt.

Ein weiteres Tor zu derselben Offenbarung ist die Assemblersprache "mix", die Knuth in seinen Beispielen verwendet. "Mix" wirkt heute archaisch, hat aber immer noch den DE-mystifizierenden Effekt.

1
ddyer

Compiler und Programmiersprachen (und alles, auch beim Erstellen einer Sprache - wie das Definieren einer endlichen Grammatik und die Konvertierung in Assembly) sind eine sehr komplexe Aufgabe, die ein hohes Verständnis der Systeme als Ganzes erfordert. Diese Art von Kurs wird normalerweise als Comp Sci-Kurs im 3./4. Jahr an der Universität angeboten.

Ich würde Ihnen wärmstens empfehlen, zunächst ein besseres Verständnis der Betriebssysteme im Allgemeinen und der Kompilierung/Ausführung vorhandener Sprachen (dh nativ (C/C++), in a VM (Java) oder by) zu erlangen ein Interpreter (Python/Javascript)).

Ich glaube, wir haben das Buch Betriebssystemkonzepte von Abraham Silberschatz, Peter B. Galvin und Greg Gagne in meinem Betriebssystemkurs (im 2. Jahr) verwendet. Dies war ein ausgezeichnetes Buch, das einen gründlichen Überblick über jede Komponente eines Betriebssystems gab - ein bisschen teuer, aber es lohnt sich und ältere/gebrauchte Kopien sollten herumschweben.

0
plafond

Es ist ein großes Thema, aber anstatt dich mit einem pompösen "Geh, lies ein Buch, Kind" abzuwischen, gebe ich dir gerne Hinweise, die dir helfen, deinen Kopf darum zu wickeln.

Die meisten Compiler und/oder Interpreter arbeiten folgendermaßen:

Tokenize : Scannen Sie den Codetext und teilen Sie ihn in eine Liste von Token auf.

Dieser Schritt kann schwierig sein, da Sie die Zeichenfolge nicht einfach in Leerzeichen aufteilen können. Sie müssen erkennen, dass if (bar) foo += "a string"; eine Liste von 8 Token ist: Word, OPEN_PAREN, Word, CLOSE_PAREN, Word, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR. Wie Sie sehen können, funktioniert das Aufteilen des Quellcodes auf Leerzeichen nicht. Sie müssen jedes Zeichen als Sequenz lesen. Wenn Sie also auf ein alphanumerisches Zeichen stoßen, lesen Sie die Zeichen so lange, bis Sie ein nicht-alphanumerisches Zeichen und diese Zeichenfolge treffen gerade gelesen ist ein Wort, das später weiter klassifiziert werden soll. Sie können selbst entscheiden, wie detailliert Ihr Tokenizer ist: ob er "a string" Als ein Token namens STRING_LITERAL verschluckt, um später weiter analysiert zu werden, oder ob "a string" Als OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE oder was auch immer angezeigt wird Dies ist nur eine von vielen Möglichkeiten, die Sie beim Codieren selbst entscheiden müssen.

Lex : Jetzt haben Sie eine Liste von Token. Sie haben wahrscheinlich einige Token mit einer mehrdeutigen Klassifizierung wie Word versehen, da Sie beim ersten Durchgang nicht zu viel Aufwand betreiben, um den Kontext jeder Zeichenfolge herauszufinden. Lesen Sie nun Ihre Liste der Quell-Token erneut und klassifizieren Sie jedes der mehrdeutigen Token anhand der Schlüsselwörter in Ihrer Sprache mit einem spezifischeren Token-Typ neu. Sie haben also ein Wort wie "if" und "if" in Ihrer Liste der speziellen Schlüsselwörter, die als Symbol IF bezeichnet werden. Sie ändern also den Symboltyp dieses Tokens von Word in IF und jedes Wort, das nicht in Ihrer Liste der speziellen Schlüsselwörter enthalten ist , wie Word foo, ist ein IDENTIFIER.

Parse : Jetzt haben Sie if (bar) foo += "a string"; eine Liste von lexierten Token erstellt, die folgendermaßen aussieht: IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. Der Schritt besteht darin, Folgen von Token als Anweisungen zu erkennen. Das ist Parsen. Sie tun dies mit einer Grammatik wie:

STATEMENT: = ASIGN_EXPRESSION | IF_STATEMENT

IF_STATEMENT: = IF, PAREN_EXPRESSION, STATEMENT

ASIGN_EXPRESSION: = IDENTIFIER, ASIGN_OP, VALUE

PAREN_EXPRESSSION: = OPEN_PAREN, VALUE, CLOSE_PAREN

WERT: = IDENTIFIER | STRING_LITERAL | PAREN_EXPRESSION

ASIGN_OP: = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT

Die Produktionen, die "|" verwenden zwischen Begriffen bedeutet "mit einem dieser Begriffe übereinstimmen". Wenn zwischen Begriffen Kommas stehen, bedeutet dies "mit dieser Folge von Begriffen übereinstimmen".

Wie benutzt du das? Versuchen Sie ab dem ersten Token, Ihre Token-Sequenz mit diesen Produktionen abzugleichen. Zuerst versuchen Sie, Ihre Token-Liste mit STATEMENT abzugleichen. Lesen Sie also die Regel für STATEMENT und sagen Sie "eine STATEMENT ist entweder eine ASIGN_EXPRESSION oder eine IF_STATEMENT". Versuchen Sie also zuerst, ASIGN_EXPRESSION abzugleichen, und suchen Sie die Grammatikregel für ASIGN_EXPRESSION und es heißt "ASIGN_EXPRESSION ist ein IDENTIFIER, gefolgt von einem ASIGN_OP gefolgt von einem VALUE. Sie suchen also nach der Grammatikregel für IDENTIFIER und sehen, dass es keinen Grammatik-Ruke für IDENTIFIER gibt, was bedeutet, dass IDENTIFIER ein" Terminal "ist, was bedeutet, dass es nicht weiter erforderlich ist Parsing, um es abzugleichen, damit Sie versuchen können, es direkt mit Ihrem Token abzugleichen. Ihr erstes Quell-Token ist jedoch eine IF, und IF ist nicht dasselbe wie ein IDENTIFIER, sodass die Übereinstimmung fehlgeschlagen ist. Was jetzt? Sie kehren zur STATEMENT-Regel zurück und versuchen es Um mit dem nächsten Begriff übereinzustimmen: IF_STATEMENT. Sie suchen IF_STATEMENT, es beginnt mit IF, Lookup IF, IF ist ein Terminal, vergleichen Sie das Terminal mit Ihrem ersten Token, IF-Token-Übereinstimmungen, fantastisch, machen Sie weiter, der nächste Begriff ist PAREN_EXPRESSION, Lookup PAREN_EXPRESSION, es ist nicht ein Terminal, was ist der erste Begriff, PAREN_EXPRESSION beginnt mit OPEN_PAREN, Lookup OPEN_PAREN, es ist ein Terminal, ordne OPEN_PAREN deinem nächsten Token zu, es stimmt überein, ... und so weiter.

Der einfachste Weg, sich diesem Schritt zu nähern, besteht darin, dass Sie eine Funktion namens parse () haben, der Sie das Quellcode-Token übergeben, mit dem Sie übereinstimmen möchten, und den Grammatikbegriff, mit dem Sie ihn abgleichen möchten. Wenn der Grammatikbegriff kein Terminal ist, wiederholen Sie: Sie rufen parse () erneut auf und übergeben ihm das gleiche Quell-Token und den ersten Begriff dieser Grammatikregel. Aus diesem Grund wird es als "Parser für rekursiven Abstieg" bezeichnet. Die Funktion parse () gibt Ihre aktuelle Position beim Lesen der Quell-Token zurück (oder ändert sie), gibt im Wesentlichen das letzte Token in der übereinstimmenden Sequenz zurück und Sie setzen den nächsten Aufruf an fort parse () von dort.

Jedes Mal, wenn parse () mit einer Produktion wie ASIGN_EXPRESSION übereinstimmt, erstellen Sie eine Struktur, die diesen Code darstellt. Diese Struktur enthält Verweise auf die ursprünglichen Quell-Token. Sie beginnen mit der Erstellung einer Liste dieser Strukturen. Wir nennen diese gesamte Struktur den Abstract Syntax Tree (AST).

Kompilieren und/oder Ausführen : Für bestimmte Produktionen in Ihrer Grammatik haben Sie Handlerfunktionen erstellt, die bei einer AST -Struktur kompiliert werden oder führen Sie diesen Teil von AST aus.

Schauen wir uns also das Stück Ihres AST an, das den Typ ASIGN_ADD hat. Als Interpreter haben Sie also eine ASIGN_ADD_execute () -Funktion. Diese Funktion wird als Teil des AST übergeben, der dem Analysebaum für foo += "a string" Entspricht. Diese Funktion betrachtet also diese Struktur und weiß, dass der erste Term in der Struktur ein IDENTIFIER sein muss. und der zweite Term ist der VALUE, also übergibt ASIGN_ADD_execute () den VALUE-Term an eine VALUE_eval () -Funktion, die ein Objekt zurückgibt, das den ausgewerteten Wert im Speicher darstellt. Dann sucht ASIGN_ADD_execute () nach "foo" in Ihrer Variablentabelle und speichert einen Verweis auf alles, was von der Funktion eval_value () zurückgegeben wurde.

Das ist ein Dolmetscher. Ein Compiler würde stattdessen Handlerfunktionen haben, die das AST in Bytecode oder Maschinencode übersetzen, anstatt es auszuführen.

Die Schritte 1 bis 3 und einige 4 können mit Tools wie Flex und Bison vereinfacht werden. (auch bekannt als Lex und Yacc), aber selbst einen Dolmetscher von Grund auf neu zu schreiben, ist wahrscheinlich die stärkste Übung, die ein Programmierer erreichen kann. Alle anderen Programmierherausforderungen scheinen nach dem Gipfeltreffen trivial zu sein.

Mein Rat ist, klein anzufangen: eine winzige Sprache mit einer winzigen Grammatik, und versuchen Sie, ein paar einfache Anweisungen zu analysieren und auszuführen, und wachsen Sie dann von dort aus.

Lesen Sie diese und viel Glück!

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

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

0
snorkel

Das Computerfeld ist nur deshalb kompliziert, weil es Zeit hatte, sich in viele Richtungen zu entwickeln. Im Kern geht es nur um Maschinen, die rechnen.

Mein sehr einfacher Lieblingscomputer ist Harry Porters Relay Computer . Es gibt einen Eindruck davon, wie ein Computer auf der Basisebene funktioniert. Dann können Sie verstehen, warum Dinge wie Sprachen und Betriebssysteme benötigt werden.

Die Sache ist, es ist schwer etwas zu verstehen, ohne zu verstehen , was es braucht . Viel Glück und lese nicht nur Sachen. Mach Sachen.

0
Mike Dunlavey