Verity 0.10, optimiert für die Quellcodegröße (1944 Bytes)
Ich habe die Frage ursprünglich falsch verstanden und als Code-Golf interpretiert. Das war wahrscheinlich das Beste, da es unter den Einschränkungen in der Frage viel einfacher ist, eine Quine mit kurzem Quellcode als kurzen Objektcode zu schreiben. Das machte die Frage so einfach, dass ich das Gefühl hatte, eine vernünftige Antwort liefern zu können und als Sprungbrett auf dem Weg zu einer besseren Antwort zu fungieren. Es veranlasste mich auch, eine höhere Sprache für die Eingabe zu verwenden, was bedeutet, dass ich im Programm selbst weniger ausdrücken müsste. Ich habe Verity nicht als Golfsprache für Hardware erstellt (ich wurde tatsächlich vor einiger Zeit beauftragt, es in einem völlig anderen Kontext zu erstellen), aber es gibt dort eine ziemliche Reminiszenz (z. B. ist es wesentlich höher als ein typisches HDL, und das ist es auch hat viel weniger Boilerplate; es ist auch viel tragbarer als das typische HDL).
Ich bin mir ziemlich sicher, dass die richtige Lösung für kurzen Objektcode darin besteht, die Daten in einer Baumstruktur zu speichern, da die Frage die Verwendung des Block-ROMs verbietet, in dem Sie sie normalerweise in einem praktischen Programm speichern würden. Ich könnte versuchen, irgendwann ein Programm zu schreiben, das dieses Prinzip verwendet (nicht sicher, welche Sprache, vielleicht Verity, vielleicht Verilog; VHDL hat zu viel Boilerplate, um für diese Art von Problem wahrscheinlich optimal zu sein). Das würde bedeuten, dass Sie nicht jedes Bit des Quellcodes an jedes Bit Ihres "manuell erstellten ROM" übergeben müssen. Der Verity-Compiler synthetisiert derzeit jedoch die Struktur der Ausgabe basierend auf der Priorität und Assoziativität der Eingabe, was bedeutet, dass er den Befehlszeiger (also den Index zur Nachschlagetabelle) effektiv in unär darstellt.
Das Programm selbst:
import <print>new x:=0$1296in(\p.\z.\a.new y:=(-a 5-a 1-a 1-a 2-a 4-a 2-a 3-a 2-a 6-a 2-a 0-a 3-a 0-a 4-a 4-a 7-a 4-a 2-a 6-a 2-a 5-a 1-a 2-a 2-a 0-a 3-a 6-a 7-a 2-a 2-a 1-a 1-a 3-a 3-a 0-a 4-a 4-a 3-a 2-a 7-a 5-a 7-a 0-a 6-a 4-a 4-a 1-a 6-a 2-a 6-a 1-a 7-a 6-a 6-a 5-a 1-a 2-a 2-a 0-a 5-a 0-a 0-a 4-a 2-a 6-a 5-a 0-a 0-a 6-a 3-a 6-a 5-a 0-a 0-a 5-a 0-a 6-a 5-a 2-a 2-a 1-a 1-a 3-a 3-a 0-a 4-a 5-a 3-a 2-a 7-a 5-a 7-a 0-a 5-a 5-a 5-a 1-a 4-a 4-a 3-a 1-a 5-a 5-a 1-a 2-a 2-a 0-a 4-a 3-a 3-a 4-a 1-a 5-a 1-a 0-a 2-a 1-a 1-a 1-a 4-a 4-a 3-a 6-a 7-a 0-a 6-a 0-a 1-a 3-a 2-a 0-a 5-a 4-a 2-a 0-a 5-a 5-a 1-a 2-a 1-a 0-a 4-a 6-a 3-a 4-a 7-a 3-a 6-a 2-a 6-a 0-a 3-a 4-a 1-a 1-a 1-a 2-a 2-a 0-a 4-a 6-a 3-a 3-a 5-a 1-a 7-a 2-a 6-a 1-a 1-a 0-a 2-a 7-a 2-a 1-a 1-a 0-a 4-a 6-a 3-a 1-a 5-a 3-a 7-a 5-a 1-a 2-a 1-a 0-a 4-a 6-a 3-a 5-a 7-a 5-a 7-a 4-a 6-a 5-a 6-a 0-a 3-a 4-a 1-a 1-a 1-a 2-a 2-a 0-a 4-a 3-a 3-a 4-a 1-a 5-a 1-a 0-a 2-a 1-a 1-a 1-a 4-a 5-a 3-a 6-a 7-a 0-a 6-a 0-a 1-a 3-a 2-a 0-a 5-a 4-a 2-a 0-a 4-a 1-a 7-a 7-a 6-a 3-a 7-a 4-a 2-a 0-a 4-a 3-a 6-a 2-a 6-a 3-a 7-a 4-a 2-a 0-a 5-a 4-a 6-a 0-a 7-a 2-a 0-a 1-a 4-a 5-a 3-a 4-a 4-a 4-a 4-a 3-a 6-a 4-a 4-a 4-a 4-a 3-a 6-a 2-a 6-a 1-a 5-a 3-a 7-a 4-a 2-a 0-a 4-a 4-a 6-a 5-a 6-a 3-a 7-a 5-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 5-a 3-a 6-a 7-a 6-a 7-a 3-a 6-a 1-a 5-a 1-a 1-a 0-a 2-a 7-a 2-a 1-a 1-a 0-a 4-a 7-a 2-a 7-a 1-a 5-a 1-a 4-a 2-a 3-a 7-a 4-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 4-a 3-a 6-a 7-a 6-a 7-a 6-a 6-a 1-a 5-a 1-a 5-a 4-a 2-a 6-a 2-a 5-a 1-a 2-a 2-a 0-a 3-a 0-a 5-a 1-a 4-a 4-a 3-a 4-a 4-a 4-a 4-a 6-a 6-a 4-a 4-a 4-a 4-a 3-a 6-a 2-a 6-a 1-a 5-a 0-a 5-a 0-a 0-a 0-a 1-a 6-a 5-a 4-a 3-a 2-a 7-a 5-a 7-a 1-a 4-a 4-a 3-a 6-a 7-a 6-a 7-a 3-a 6-a 2-a 0-a 0-a 1-a 4-a 7-a 4-a 7-a 1-a 6-a 2-a 6-a 1-a 7-a 3-a 6-a 3-a 7-a 0-a 6-a 1-a 5-!x)in while!x>0do(p(if z<32then z+92else z);if z==45then while!y>0do(p 97;p 32;p(48^!y$$3$$32);p 45;y:=!y>>3)else skip;x:=!x>>6))print(!x$$6$$32)(\d.x:=!x>>3^d<<1293;0)
Besser lesbar:
import <print>
new x := 0$1296 in
(\p.\z.\a.
new y := (-a 5-a 1-
# a ton of calls to a() omitted...
-a 1-a 5-!x) in
while !x>0 do (
p(if z<32 then z+92 else z);
if z==45
then while !y>0 do (
p 97;
p 32;
p(48^!y$$3$$32);
p 45;
y:=!y>>3 )
else skip;
x:=!x>>6
)
)(print)(!x$$6$$32)(\d.x:=!x>>3^d<<1293;0)
Die Grundidee ist, dass wir die gesamten Daten in der Variablen speichern x
. (Wie bei einem Quine üblich, haben wir einen Codeabschnitt und einen Datenabschnitt. Die Daten codieren den Text des Codes und können auch zum Neuerstellen des Textes der Daten verwendet werden.) Leider lässt Verity derzeit keine sehr großen Daten zu Konstanten, die in den Quellcode geschrieben werden sollen (es werden OCaml-Ganzzahlen während der Kompilierung verwendet, um Ganzzahlen in der Quelle darzustellen, was in einer Sprache, die beliebig breite Ganzzahltypen unterstützt, eindeutig nicht korrekt ist) - und außerdem erlaubt es keine Konstanten in oktal angegeben - also generieren wir den Wert von x
zur Laufzeit durch wiederholte Aufrufe einer Funktiona
. Wir könnten eine void-Funktion erstellen und sie wiederholt als separate Anweisungen aufrufen, aber das würde es schwierig machen zu identifizieren, wo mit der Ausgabe des Textes des Datenabschnitts begonnen werden soll. Also habe ich stattdessen a
eine Ganzzahl zurückgegeben und die Daten mit Arithmetik gespeichert (Verity garantiert, dass die Arithmetik von links nach rechts ausgewertet wird). Der Datenabschnitt wird x
mit einem einzelnen -
Vorzeichen codiert . Wenn dies zur Laufzeit auftritt, wird es -a 5-a 1-
über die Verwendung von vollständig usw. erweitert y
.
Das Initialisieren y
als Kopie von x
ist hier ziemlich subtil. Da a
speziell Null zurückgegeben wird, ist der größte Teil der Summe nur Null minus Null minus… und bricht sich selbst ab. Wir enden mit !x
(dh "dem Wert von x
"; in Verity wie in OCaml funktioniert der Name einer Variablen eher wie ein Zeiger als alles andere, und Sie müssen ihn explizit dereferenzieren, um den Wert der Variablen zu erhalten). Veritys Regeln für unäres Minus sind ein wenig komplex - das unäre Minus von v
wird als geschrieben (-v)
- (-0-0-0-!x)
analysiert also als (-(0-0-0-!x))
, was gleich ist !x
, und wir werden am Ende y
als Kopie von initialisiert x
. (Es ist auch erwähnenswert, dass Verity nicht istCall-by-Value, sondern ermöglicht es Funktionen und Operatoren, die Reihenfolge zu wählen, in der sie die Dinge bewerten. -
bewertet das linke Argument vor dem rechten Argument, und insbesondere wenn das linke Argument Nebenwirkungen hat, werden diese sichtbar, wenn das rechte Argument ausgewertet wird.)
Jedes Zeichen des Quellcodes wird mit zwei Oktalziffern dargestellt. Dies bedeutet, dass der Quellcode auf 64 verschiedene Zeichen beschränkt ist, sodass ich meine eigene Codepage für den internen Gebrauch erstellen musste. Die Ausgabe erfolgt in ASCII, daher musste ich intern konvertieren. Dafür ist das da (if z<32 then z+92 else z)
. Hier ist der Zeichensatz, den ich in der internen Darstellung verwendet habe, in numerischer Reihenfolge (dh \
hat Codepunkt 0, ?
hat Codepunkt 63):
\]^_`abcdefghijklmnopqrstuvwxyz{ !"#$%&'()*+,-./0123456789:;<=>?
Dieser Zeichensatz gibt uns die meisten für Verity wichtigen Zeichen. Bemerkenswerte fehlende Zeichen sind }
(was bedeutet, dass wir keinen Block mit erstellen können {}
, aber zum Glück sind alle Anweisungen Ausdrücke, sodass wir ()
stattdessen verwenden können); und |
(aus diesem Grund musste ich beim Erstellen des Werts von ein exklusives statt einschließendes ODER verwenden x
, was bedeutet, dass ich es auf 0 initialisieren muss; ich musste jedoch angeben, wie groß es trotzdem war). Einige der kritischen Zeichen, die ich sicherstellen wollte, befanden sich im Zeichensatz: <>
(für Importe, auch Verschiebungen) ()
(sehr schwer zu schreiben, ein Programm, das ohne diese analysiert werden kann), $
(für alles, was mit Bitbreite zu tun hat) und \
( Für Lambdas könnten wir theoretisch damit umgehenlet…in
aber es wäre viel ausführlicher).
Um das Programm etwas kürzer zu machen, habe ich Abkürzungen für print
und für !x$$6$$32
(dh "die unteren 6 Bits von !x
, die für die print
Bibliothek verwendbar sind ) erstellt , indem ich sie vorübergehend an Lambda-Argumente gebunden habe.
Schließlich gibt es das Problem der Ausgabe. Verity bietet eine print
Bibliothek, die für die Debug-Ausgabe vorgesehen ist. Auf einem Simulator werden die ASCII-Codes auf die Standardausgabe gedruckt, die perfekt zum Testen des Programms verwendet werden kann. Auf einer physischen Leiterplatte hängt es davon ab, dass eine print
Bibliothek für den bestimmten Chip und die Platine geschrieben wurde, die sie umgeben. print
In der Verity-Distribution befindet sich eine Bibliothek für eine Evaluierungskarte, auf die ich Zugriff hatte und die die Ausgabe auf Sieben-Segment-Displays druckt. Angesichts der Tatsache, dass die Bibliothek am Ende Platz auf der resultierenden Leiterplatte beansprucht, kann es sinnvoll sein, eine andere Sprache für eine optimierte Lösung dieses Problems zu verwenden, damit wir die Bits der Ausgabe direkt auf Drähten ausgeben können.
Übrigens ist dieses Programm auf der Hardware O (n²), was bedeutet, dass es auf einem Simulator viel schlimmer ist (ich vermute O (n⁴); ich bin mir zwar nicht sicher, aber es war schwer genug zu simulieren, dass es unwahrscheinlich ist, dass es überhaupt kubisch ist und basierend darauf, wie die Zeit auf meine Änderungen reagierte, als ich das Programm schrieb, scheint die Funktion tatsächlich sehr schnell zu wachsen. Der Verity-Compiler benötigte 436 Optimierungsdurchläufe (was viel, viel mehr ist als normalerweise), um das Programm zu optimieren, und selbst danach war es für meinen Laptop sehr schwierig, es zu simulieren. Der vollständige Kompilierungs- und Simulationslauf dauerte folgende Zeit:
real 112m6.096s
user 105m25.136s
sys 0m14.080s
und erreichte einen Höchstwert von 2740232 Kibibyte Speicher. Das Programm benötigt insgesamt 213646 Taktzyklen. Es funktioniert aber!
Wie auch immer, diese Antwort erfüllt die Frage nicht wirklich, da ich für das Falsche optimiert habe, aber da es noch keine anderen Antworten gibt, ist dies standardmäßig die beste (und es ist schön zu sehen, wie eine Golf-Quine aussehen würde eine Hardwaresprache). Ich bin mir derzeit nicht sicher, ob ich an einem Programm arbeiten werde, das darauf abzielt, einen optimierten Ausgang auf dem Chip zu erzeugen. (Es wäre wahrscheinlich viel größer in Bezug auf die Quelle, da eine O (n) -Datencodierung ziemlich komplexer wäre als die hier gezeigte.)