Ich arbeite an einer Spielzeugsprache, die in C ++ auf Basis von Lisp (sehr kleine Teilmenge des Schemas) kompiliert, ich versuche herauszufinden, wie man Ausdruck,
darstellen kann %Vor%Zuerst dachte ich, führe alle excrs aus und dann die letzte zurück, aber die Rückkehr wird meine Fähigkeit töten zu verschweigen, lass Ausdrücke, was wäre der Weg für die Repräsentation von let?
Außerdem werden alle Ressourcen auf Source-zu-Source-Transformation geschätzt, ich habe gegoogelt, aber alles, was ich tun konnte, ist der 90-Minuten-Schema-Compiler.
Eine Möglichkeit, let
zu erweitern, besteht darin, sie als lambda
:
Dann transformiere das in eine Funktion und einen entsprechenden Aufruf in C ++:
%Vor%Also in einem größeren Zusammenhang:
%Vor%wird
%Vor% Es gibt viele weitere Details, wie zum Beispiel den Zugriff auf lexikalische Variablen außerhalb von let
innerhalb von let
. Da C ++ keine lexikalisch verschachtelten Funktionen hat (im Gegensatz zu Pascal zum Beispiel), erfordert dies eine zusätzliche Implementierung.
Ich werde versuchen, einen naiven Ansatz zum Kompilieren von Nested zu erklären
lambda
s. Seit Gregs Erklärung der Erweiterung von let
in ein lambda
ist sehr gut, ich werde let
gar nicht adressieren, ich nehme an, dass let
ist
eine abgeleitete Form oder ein abgeleitetes Makro und wird zu einer lambda
-Form erweitert
Sofort angerufen.
Kompilieren von Lisp- oder Scheme-Funktionen direkt in C- oder C ++ - Funktionen wird schwierig sein wegen der Probleme, die andere Plakate aufgeworfen haben. Es hängt davon ab der Ansatz, das resultierende C oder C ++ wird nicht erkennbar sein (oder auch nicht sehr gut lesbar).
Ich habe einen Lisp-zu-C-Compiler geschrieben, nachdem ich die Struktur und Interpretation von Computerprogrammen beendet hatte (es ist eine der letzten Übungen, und tatsächlich habe ich betrogen und einfach einen Übersetzer vom SICP-Byte-Code nach C geschrieben). Die Teilmenge von C, die es ausgab, verwendete C-Funktionen überhaupt nicht, um mit Lisp-Funktionen umzugehen. Dies ist, weil die Maschinensprache in Kapitel 5 von SICP registrieren ist wirklich niedriger Ebene als C.
Angenommen, Sie haben eine Art von Umgebung, die Namen an Werte bindet, können Sie den Funktionsschlüssel wie folgt definieren: Erweitern Sie die Umgebung, in der die Funktion definiert wurde, mit den formalen Parametern, die an die Argumente gebunden sind, und evaluieren Sie sie der Körper der Funktion in dieser neuen Umgebung.
Im SICP-Compiler wird die Umgebung in einer globalen Variablen gehalten, und es gibt andere globale Variablen, die die Argumentliste für einen Funktionsaufruf enthalten, wie genau wie das Prozedurobjekt, das aufgerufen wird (das Prozedurobjekt enthält einen Zeiger auf die Umgebung, in der es definiert wurde) und eine Sprungmarke, zu der gesprungen wird, wenn eine Funktion zurückkehrt.
Beachten Sie, dass wenn Sie einen lambda
-Ausdruck kompilieren
sind zwei syntaktische Komponenten, die Sie zur Kompilierzeit kennen: das formale
Parameter und der Körper von lambda
.
Wenn eine Funktion kompiliert wird, sieht der ausgegebene Code ungefähr so aus dieser Pseudocode:
%Vor% ... wobei *env*
und *argl*
die globalen Variablen sind, die den
Umgebung und Argumentliste, und extend
ist eine Funktion (dies kann
eine richtige C ++ Funktion sein), die die Umgebung *env*
um erweitert
Namen in *argl*
mit Werten in [formals]
paaren.
Dann, wenn der kompilierte Code ausgeführt wird und es einen Aufruf gibt
lambda
woanders in deinem Code soll die Aufrufkonvention stehen
Wenn Sie die Argumentliste in die Variable *argl*
auswerten, setzen Sie das Rückgabe-Label in die Variable *continue*
und springen Sie dann zu some-label
.
Im Fall von verschachteltem lambda
s würde der ausgegebene Code etwas aussehen
so:
Das ist ein bisschen schwierig zu erklären, und ich bin mir sicher, dass ich ein Durcheinander gemacht habe Job von ihm. Ich kann mir kein anständiges Beispiel vorstellen, bei dem ich das ganze Kapitel 5 der SICP nicht schlampig beschreibe. Da ich die Zeit verbrachte, um diese Antwort zu schreiben, werde ich es veröffentlichen, aber es tut mir sehr leid, wenn es hoffnungslos verwirrend ist.
Ich empfehle dringend SICP und Lisp in kleinen Stücken .
SICP behandelt die metakristalline Interpretation für Anfänger sowie eine Reihe von Varianten des Interpreters und einen Bytecode-Compiler, den ich oben verschleiern und verfälschen konnte. Das sind nur die letzten zwei Kapitel, die ersten drei Kapitel sind genauso gut. Es ist ein wunderbares Buch. Lesen Sie es unbedingt, wenn Sie noch nicht haben.
LiSP enthält eine Reihe von Scheme-Interpretern, einen Compiler für Byte-Code und einen Compiler für C. Ich bin mitten drin und kann mit Bestimmtheit sagen, dass es ein tiefes, reichhaltiges Buch ist, das die Zeit eines jeden Interessierten wert ist in der Implementierung von Lisp. Es mag ein bisschen veraltet sein, aber für einen Anfänger wie mich ist es immer noch wertvoll. Es ist fortgeschrittener als SICP, aber Vorsicht. Es enthält ein Kapitel in der Mitte über die denotationale Semantik, die im Grunde genommen direkt über meinen Kopf ging.
Einige andere Anmerkungen:
Darius Bacons selbst-hosting Lisp zu C-Compiler
Lambda-Lifting , das ist eine fortgeschrittenere Technik, von der ich denke, dass Marc Feeley
verwendetWenn Sie nach Tools suchen, die Sie bei der Übersetzung von Quelltexten unterstützen, empfehle ich ANTLR . Es ist sehr gut.
Sie müssen jedoch darüber nachdenken, wie Sie von einer locker typisierten Sprache (Lisp) in eine weniger lockere Sprache (c) übersetzen können. Zum Beispiel, in Ihrer Frage, was ist der Typ von 10
? Ein Code%? Ein short
? A int
?