Die kurze Antwort lautet: Sie haben Recht, Sie benötigen immer entweder einen anderen in X geschriebenen Dolmetscher oder einen Compiler von Y in eine andere Sprache, für die Sie bereits einen Dolmetscher haben. Interpreter werden ausgeführt, Compiler übersetzen nur von einer Sprache in eine andere. Irgendwann in Ihrem System muss es einen Interpreter geben. Auch wenn es sich nur um die CPU handelt.
Unabhängig davon, wie viele neue Dolmetscher Sie in der Sprache Y schreiben , müssen Sie immer den ersten in X geschriebenen Dolmetscher verwenden, um die nachfolgenden Dolmetscher zu interpretieren. Dies scheint ein Problem zu sein, allein aufgrund der Art der Dolmetscher.
Richtig. Sie können einen Compiler von Y nach X schreiben (oder eine andere Sprache, für die Sie einen Interpreter haben), und das können Sie sogar in Y tun . Dann können Sie Ihren Lauf Y - Compiler geschrieben in Y auf der Y - Interpreter in schriftlich X (oder auf der Y geschrieben Dolmetschern in Y auf dem Lauf Y - Interpreter in geschrieben X oder auf den Y - Interpreter in geschrieben Y auf der laufenden Y - Interpreter geschrieben in Y läuft auf dem YInterpreter in X oder… ad infinitum), um Ihren in Y bis X geschriebenen Y- Interpreter zu kompilieren , damit Sie ihn dann auf einem X- Interpreter ausführen können . Auf diese Weise haben Sie Ihren in X geschriebenen Y- Interpreter losgeworden , aber jetzt benötigen Sie den X- Interpreter (wir wissen, dass wir jedoch bereits einen haben, da wir sonst den in Y geschriebenen X- Interpreter nicht ausführen könnten ) und Sie musste zuerst einen Y- to- X- Compiler schreiben .
Auf der anderen Seite geht es in dem Wikipedia-Artikel über Dolmetscher jedoch tatsächlich um selbsthostende Dolmetscher. Hier ist ein kleiner Auszug, der relevant ist:
Ein Selbstinterpreter ist ein Programmierspracheninterpreter, der in einer Programmiersprache geschrieben ist, die sich selbst interpretieren kann. Ein Beispiel ist ein in BASIC geschriebener BASIC-Interpreter. Selbstdolmetscher sind mit selbsthostenden Compilern verwandt.
Wenn für die zu interpretierende Sprache kein Compiler vorhanden ist, muss zum Erstellen eines Selbstinterpreters die Sprache in einer Host-Sprache (die möglicherweise eine andere Programmiersprache oder ein Assembler ist) implementiert werden. Mit einem solchen ersten Interpreter wird das System gebootet und neue Versionen des Interpreters können in der Sprache selbst entwickelt werden
Es ist mir jedoch immer noch nicht klar, wie genau dies geschehen würde. Es scheint, dass Sie immer die erste Version Ihres Dolmetschers verwenden müssen, die in der Host-Sprache geschrieben ist.
Richtig. Beachten Sie, dass der Wikipedia-Artikel ausdrücklich besagt, dass Sie eine zweite Implementierung Ihrer Sprache benötigen und nicht, dass Sie die erste entfernen können.
Der oben erwähnte Artikel verweist nun auf einen anderen Artikel, in dem Wikipedia einige Beispiele für vermeintliche selbsthostende Dolmetscher enthält. Bei näherer Betrachtung scheint es jedoch so zu sein, dass der Hauptteil der "Dolmetscher" vieler dieser selbsthostenden Dolmetscher (insbesondere einiger der gebräuchlicheren wie PyPy oder Rubinius) tatsächlich in anderen Sprachen wie C ++ oder C geschrieben ist.
Wieder richtig. Das sind wirklich schlechte Beispiele. Nehmen wir zum Beispiel Rubinius. Ja, es ist wahr, dass der Ruby-Teil von Rubinius selbst gehostet wird, aber er ist ein Compiler und kein Interpreter: Er kompiliert Ruby-Quellcode in Rubinius-Bytecode. Der Interpreter-Teil OTOH ist nicht selbst gehostet: Er interpretiert den Rubinius-Bytecode, ist jedoch in C ++ geschrieben. Rubinius als "selbst gehosteten Interpreter" zu bezeichnen, ist also falsch: Der selbst gehostete Teil ist kein Interpreter , und der Interpreter- Teil ist nicht selbst gehostet .
PyPy ist ähnlich, aber noch falscher: Es ist nicht einmal in Python geschrieben, sondern in RPython, einer anderen Sprache. Es ist syntaktisch Python ähnlich, semantisch eine "erweiterte Untermenge", aber es ist tatsächlich eine statisch typisierte Sprache, die ungefähr auf derselben Abstraktionsebene wie Java liegt, und seine Implementierung ist ein Compiler mit mehreren Backends, der RPython zu C-Quellcode, ECMAScript, kompiliert Quellcode, CIL-Bytecode, JVM-Bytecode oder Python-Quellcode.
Also ist das, was ich oben beschreibe, möglich? Kann ein Self-Host-Interpreter unabhängig von seinem ursprünglichen Host sein? Wenn ja, wie genau würde das geschehen?
Nein, nicht alleine. Sie müssten entweder den Originalinterpreter behalten oder einen Compiler schreiben und Ihren Selbstinterpreter kompilieren.
Es gibt einige Meta-Circular-VMs wie Klein (geschrieben in Self ) und Maxine (geschrieben in Java). Beachten Sie jedoch, dass die Definition von "Meta-Circular" hier noch anders ist: Diese VMs sind nicht in der Sprache geschrieben, die sie ausführen: Klein führt Self-Bytecode aus, ist jedoch in Self geschrieben, Maxine führt JVM-Bytecode aus, ist jedoch in Java geschrieben. Der Self / Java-Quellcode der VM wird jedoch tatsächlich zu Self / JVM-Bytecode kompiliert und dann von der VM ausgeführt. Wenn die VM ausgeführt wird, ist sie also in der Sprache, die sie ausführt. Puh.
Beachten Sie auch, dass sich dies von VMs wie der SqueakVM und der Jikes RVM unterscheidet . Jikes ist in Java geschrieben, und SqueakVM ist in Slang geschrieben (eine statisch typisierte syntaktische und semantische Teilmenge von Smalltalk, die ungefähr auf derselben Abstraktionsebene wie ein Assembler auf hoher Ebene liegt). Beide werden vor der Ausführung statisch zu nativem Code kompiliert. Sie rennen nicht in sich hinein. Sie können sie jedoch auf sich selbst (oder auf einer anderen Smalltalk VM / JVM) ausführen . Aber das ist in diesem Sinne nicht "meta-rund".
Maxine und Klein, OTOH doin sich laufen; Sie führen ihren eigenen Bytecode mit ihrer eigenen Implementierung aus. Das ist wirklich aufregend! Dies bietet einige interessante Optimierungsmöglichkeiten, zum Beispiel, da die VM sich selbst zusammen mit dem Benutzerprogramm ausführt, Inline-Aufrufe vom Benutzerprogramm an die VM und umgekehrt, z. B. Aufrufe an den Garbage Collector, oder der Speicherzuordner können in den Benutzer eingebunden werden Code und reflektierende Rückrufe im Benutzercode können in die VM eingefügt werden. Bei all den cleveren Optimierungs-Tricks moderner VMs, bei denen sie das ausführende Programm beobachten und es in Abhängigkeit von der tatsächlichen Arbeitslast und den Daten optimieren, kann die VM dieselben Tricks auf sich selbst anwenden, während sie das Benutzerprogramm während des Benutzerprogramms ausführt führt die spezifische Arbeitslast aus. Mit anderen Worten, spezialisierte sich die VM hoch selbst für diebestimmtes Programm, das diese bestimmte Arbeitslast ausführt.
Beachten Sie jedoch, dass ich die Verwendung des Wortes "Interpreter" oben umgangen und immer "Ausführen" verwendet habe? Diese VMs basieren nicht auf Interpreter, sondern auf JIT-Compilern. Es wurde später ein Interpreter zu Maxine hinzugefügt, aber Sie benötigen immer den Compiler: Sie müssen die VM einmal über einer anderen VM ausführen (z. B. Oracle HotSpot im Fall von Maxine), damit sich die VM selbst kompilieren kann (JIT). Im Fall von Maxine kompiliert JIT seine eigene Boot-Phase, serialisiert dann den kompilierten nativen Code in ein Bootstrap-VM-Image und klebt einen sehr einfachen Bootloader voran (die einzige in C geschriebene Komponente der VM, obwohl dies nur der Einfachheit halber dient könnte es auch in Java sein). Jetzt können Sie Maxine verwenden, um sich selbst auszuführen.
Sie haben Recht, wenn Sie feststellen, dass ein selbsthostender Interpreter immer noch einen Interpreter benötigt, um sich selbst auszuführen, und nicht im gleichen Sinne wie ein Compiler gebootet werden kann.
Eine selbst gehostete Sprache ist jedoch nicht dasselbe wie ein selbst gehosteter Dolmetscher. Das Erstellen eines Interpreters ist normalerweise einfacher als das Erstellen eines Compilers. Um eine neue Sprache zu implementieren, müssen Sie zunächst einen Interpreter in einer anderen Sprache implementieren. Dann können wir diesen Interpreter verwenden, um einen Compiler für unsere Sprache zu entwickeln. Die Sprache wird dann selbst gehostet, da der Compiler interpretiert wird. Der Compiler kann sich dann selbst kompilieren und als vollständig gebootet betrachtet werden.
Ein Sonderfall ist eine selbsthostende JIT-Kompilierungslaufzeit. Es kann mit einem Interpreter in einer Hostsprache beginnen, der dann die neue Sprache verwendet, um die JIT-Kompilierung zu implementieren, wonach sich der JIT-Compiler selbst kompilieren kann. Dies fühlt sich an wie ein sich selbst hostender Dolmetscher, umgeht jedoch das Problem der unendlichen Dolmetscher. Dieser Ansatz wird verwendet, ist aber noch nicht sehr verbreitet.
Eine andere verwandte Technik ist ein erweiterbarer Interpreter, mit dem wir Erweiterungen in der zu interpretierenden Sprache erstellen können. Zum Beispiel könnten wir neue Opcodes in der Sprache implementieren. Dies kann einen Bare-Bones-Interpreter in einen funktionsreichen Interpreter verwandeln, solange wir zirkuläre Abhängigkeiten vermeiden.
Ein Fall, der tatsächlich ziemlich häufig auftritt, ist die Fähigkeit der Sprache, ihre eigene Analyse zu beeinflussen, z. B. als Makros, die zur Analysezeit ausgewertet werden. Da die Makrosprache mit der verarbeiteten Sprache identisch ist, ist sie in der Regel wesentlich funktionsreicher als dedizierte oder eingeschränkte Makrosprachen. Es ist jedoch richtig zu beachten, dass die Sprache, in der die Erweiterung ausgeführt wird, sich geringfügig von der Sprache nach der Erweiterung unterscheidet.
Wenn „echte“ selbst gehostete Dolmetscher verwendet werden, geschieht dies normalerweise aus Bildungs- oder Forschungsgründen. Beispielsweise ist die Implementierung eines Interpreters für Scheme inside Scheme eine coole Methode, um Programmiersprachen zu unterrichten (siehe SICP).
quelle