17 Aug, 2011 von Matthias Matousek 0
Whitespace
In einigen Vorlesungen der Uni Ulm ist es üblich Programmieraufgaben der Übungen online an ein Framework zu submitten, welches die Programmierlösung automatisch bewertet. Im letzten Semester war bei einer dieser Veranstaltungen die Abgabe in Whitespace erlaubt.
Whitespace ist eine esoterische Programmiersprache. Das Außergewöhnliche an ihr ist, dass ihr Quelltext ausschließlich aus Whitespace (also Leerzeichen u.ä.) besteht. Um genau zu sein aus den drei Zeichen „Leerzeichen“, „Tabulator“ und „Zeilenumbruch“. Jegliche lesbaren Zeichen werden ignoriert. Damit lassen sich interessante Dinge anstellen, wie zum Beispiel Quelltext in anderen Texten zu verstecken.
Und wenn es erlaubt ist Aufgaben in einer so interessanten Sprache abzugeben, dann kann ich es mir natürlich nicht nehmen lassen dies auch zu tun. Ich begann also Whitespace zu lernen und war erstaunt, wie schnell man sich gute Kenntnisse in dieser Programmiersprache aneignen kann. Wer schon einmal mit einer Assembler-Sprache gearbeitet hat, sollte mit Whitespace keine Probleme haben. Innerhalb weniger Stunden hatte ich bereits einen Whitespace Disassembler (whdisasm) geschrieben gehabt, der mir Whitespace Quelltext in lesbare Mnemonics übersetzt. Disassembler ist natürlich eigentlich keine korrekte Bezeichnung für ein Programm mit dieser Funktionalität. Aber der Name klingt meiner Meinung nach einfach besser als „Whitespace-zu-Mnemonics-Konverter“ oder so. Mit dem whdisasm konnte ich die im Web existierenden Beispiele leichter analysieren und damit lernen, wie man die Sprache verwendet.
Ich schrieb einige kurze Programme. Aber das Coden mit Whitespace ist doch nicht gerade einfach (obwohl für viele Editoren Syntax Highlighting möglich ist). Also machte ich mich daran noch einen Assembler, den whasm, zu entwickeln. Auch whasm war in kurzer Zeit lauffähig. In nur zwei Tagen hatte ich also eine neue Sprache gelernt, einen Disassembler und einen Assembler, sowie einige Libraryfunktionen, geschrieben und meine Programmieraufgabe in Whitespace abgegeben.
Viele esoterische Programmiersprachen sind zwar auf den ersten Blick furchtbar abschreckend aber in Wirklichkeit sehr einfache Konstrukte. Whitespace hat zudem eine ausgezeichnete Dokumentation, die meine einzige Informationsquelle für whasm und whdisasm gewesen ist.
Aber wie funktioniert denn jetzt Whitespace?
Ich verwende ab jetzt für Leerzeichen die Abkürzung [space], für Tabulatoren [tab] und für Zeilenumbrüche [lf]. Diese Darstellung wird auch im Whitespace Tutorial so verwendet.
Ich habe bereits erwähnt, dass Whitespace eine Art Assemblersprache ist. Der Whitespace Quelltext (also die Leerzeichen, Tabs und so) werden üblicherweise interpretiert. Im Netz sind dafür leicht verschiedene Interpreter zu finden. Bestimmt gibt es auch irgendwo einen Compiler (und wenn nicht, ist das hier vielleicht ein Anstoß dafür einen zu schreiben).
Whitespace kennt zwei Arten von Speicher: Stack und Heap. Für den Stack gibt es Operationen, wie „auf den Stack pushen„, „vom Stack poppen“, „duplizieren des obersten Elements“ und einige weitere. Im Heap können Daten an beliebigen Adressen gespeichert und gelesen werden. An dieser Stelle passt es vielleicht zu erwähnen, dass Whitespace keine Datentypen kennt, wie sie sonst üblich sind. Die einzige Unterscheidung ist „Zahl“ oder „(ASCII-) Zeichen“. Druckbare Zeichen haben dabei 8 Bit. Zahlen können beliebig lang(!) sein. Eine positive Zahl beginnt mit einem [space] und eine negative mit einem [tab]. Ansonsten ist ein [space] die binäre 0 und ein [tab] die binäre 1.
Hier ein Beispielprogramm, dass zwei Zahlen aus dem Heap liest, diese addiert und das Ergebnis auf dem Bildschirm anzeigt. Ich habe für bessere Lesbarkeit statt echten Leer-/Tab-/Zeilenumbruchszeichen sichtbare Abkürzungen verwendet ;)
// Achtung! Zeilenumbrüche sind nur da, wo [lf] steht. Ich habe die Zeilen im Beispiel
// lediglich für bessere Lesbarkeit umgebrochen.
// Zuerst wollen wir zwei Zahlen vom Heap auf den Stack laden.
// Um eine Zahl zu laden, pushen wir zunächst ihre Adresse auf den Stack
[space] // das erste [space] bedeutet, dass wir den Stack manipulieren wollen
[space] // das zweite [space] sagt, dass wir die Zahl nach dem Befehl auf den Stack pushen wollen
[space][tab][tab][tab][lf]
// [space][tab][tab][tab] ist die binäre 0111 (also 7)
// das [lf] markiert das Ende des Zahlenparameters
[tab][tab] // [space] sagt, dass wir aus dem Heap lesen wollen (an der Adresse im Stack)
[space] // [tab][tab] bedeutet, dass wir auf den Heap zugreifen
// Das Selbe nochmal mit der zweiten Zahl:
[space][space][space][tab][space][space][space][lf] // diesmal die Adresse 8 (01000)
[tab][tab][space]
[tab][space] // leitet eine arithmetischen Befehl ein
[space][space] // Addition der zwei obersten Werte auf dem Stack
[tab][lf] // beginnt eine I/O Operation
[space][tab] // Bildschirmausgabe der Zahl oben auf dem Stack
[lf][lf][lf] // beendet das Programm
Das selbe Programm würde in (mit whasm übersetzbaren) Mnemonics so aussehen:
push 7
retrieve
push 8
retrieve
add
printnum
end
Fazit
Esoterische Programmiersprachen machen Spaß! Das ist zumindest meine Meinung. Ich habe in der Zeit, in der ich mich damit beschäftigt habe, einiges gelernt. Nicht nur Whitespace selbst, sondern den Umgang mit stackbasierten Sprachen oder auch, wie leicht sich solche Konstrukte parsen lassen.
Der Code für whasm und whdisasm sind auf github verfügbar. Dort habe ich auch begonnen Libraryfunktionen zu implementieren, die mit whasm in Whitespace übersetzt werden können.