Inside Quarkdown: Die Kompilier-Pipeline in sechs Stufen
Teil 10 der Quarkdown-Reihe: Wie aus .qd-Quelltext fertiges HTML wird — Lexing, Parsing, Function-Call-Expansion, Tree-Traversal, Rendering, Post-Rendering und das Live-Preview-Doppelpuffern.
von Jean Pierre Kolb ·
Dieser Teil ist für die Neugierigen. Du musst nichts davon wissen, um Quarkdown zu nutzen — aber wer verstehen will, wie aus einer .qd-Datei am Ende HTML wird, bekommt hier den Blick hinter die Kulissen. Nach dem KI-Teil wird es technisch: Quarkdown verarbeitet deinen Quelltext in einer sequenziellen Pipeline aus sechs Stufen, bei der die Ausgabe jeder Stufe die Eingabe der nächsten ist. (Die Architektur stammt aus der Bachelorarbeit des Autors — hier in Kurzform.)
1. Lexing: Text wird zu Tokens
Wie man einen Satz zuerst in einzelne Wörter zerlegt, scannt der Lexer die Quelle — eine reine Zeichenfolge — und zerteilt sie in Tokens: kleine Stücke mit Typ, Position und Inhalt. Markdown kennt zwei Kategorien: Block-Tokens (Absatz, Liste, Überschrift, Code, Zitat — die äußere Struktur) und Inline-Tokens (fett, kursiv, Links, Bilder — Formatierung innerhalb eines Blocks). Dafür gibt es zwei Lexer; Funktionsaufrufe werden sowohl als Block als auch als Inline erkannt. Zuerst läuft nur der Block-Lexer — die äußeren Blöcke gehen dann an den Parser.
2. Parsing: Tokens werden zum Baum
Der Parser ordnet die Tokens in einen Abstract Syntax Tree (AST) — eine Baumstruktur, deren Elemente Nodes heißen. Aus einer Überschrift, einem Absatz mit fettem Text und einer Liste wird so:
AstRoot
├─ Heading(depth=1) → Text("Titel")
├─ Paragraph → Text("Das ist ") + Strong("fett") + Text(" Text")
└─ UnorderedList → ListItem → Paragraph → Text("Punkt 1")Der Trick ist rekursives Parsing: Für jeden Block-Token stößt der Parser das Lexing auf dessen Innenleben erneut an, parst die inneren Tokens wieder, und so weiter — bis nichts Verschachteltes mehr übrig ist.
3. Function-Call-Expansion: hier passiert die Magie
Unter den Nodes gibt es einen besonderen: den FunctionCallNode. Er ist der einzige veränderliche Node — anfangs ohne Kinder, später vom Function-Call-Expander gefüllt. Quarkdown-Funktionen geben immer einen typgeprüften Value zurück, und ein Value-Node-Mapper übersetzt diesen in einen renderbaren Node: ein StringValue wird Text, ein BooleanValue eine Checkbox, ein DictionaryValue eine Tabelle, eine Collection eine Liste.
Für jeden Aufruf wird die Funktion in den geladenen Bibliotheken gesucht, Argumente an Parameter gebunden und die Funktion ausgeführt. Hier zeigt sich, wie Quarkdowns dynamische Typisierung mit der statisch typisierten Kotlin-Stdlib zusammenkommt: Beim Binden konvertiert eine ValueFactory dynamische Argumente in den statischen Parametertyp — scheitert das, gibt es einen Fehler. Genau das ist die Grenze zwischen „deinem" Markdown-Skript und der kompilierten Engine.
4. Tree-Traversal: das Dokument verstehen
Jetzt wird der fertige Baum einmal tiefendurchlaufen, um Querschnitts-Informationen zu sammeln: die Überschriften-Hierarchie fürs Inhaltsverzeichnis, die Nummerierung jedes Elements und die Bindung von Link-Referenzen an ihre Definitionen. Aus Performance-Gründen gibt es nur einen Durchlauf — jede Aufgabe hängt ihren eigenen Hook an den Iterator, der bei passenden Node-Typen auslöst.
5. Rendering: Baum wird zum Zielformat
Der angereicherte AST wird — wieder tiefendurchlaufen — ins Zielformat übersetzt. Jeder Node erzeugt seine Ausgabe, idealerweise eins zu eins:
<h1>Titel</h1>
<p>Das ist <strong>fett</strong> Text</p>
<ul><li>Punkt 1</li></ul>Damit das skaliert, liegt jedes Renderziel in einem eigenen Modul (quarkdown-html, quarkdown-plaintext), das sich in den Kern einklinkt — so kommen perspektivisch weitere Zielformate dazu.
6. Post-Rendering: das ganze Dokument
Das Renderergebnis ist nur der <body>-Inhalt — Metadaten, Styling und Laufzeit fehlen noch. Der Post-Renderer (für HTML der HtmlDocumentBuilder über die kotlinx.html-DSL) baut das vollständige Dokument drumherum: Inhalt in den <body>, die richtigen Skripte und Stylesheets je nach Dokumenttyp, Titel, Seitenformat und Schriften — und lädt etwa KaTeX nur dann, wenn überhaupt eine Formel vorkommt. Am Ende liefert die Pipeline ein Bündel aus HTML, Stylesheets (global + Layout-Theme + Farb-Theme) und Runtime-Skripten zurück, das die CLI in Dateien schreibt.
Bonus: Wie Live-Preview funktioniert
Die Live-Preview aus Teil 8 ist ein hübsches Stück Engineering. Der eingebaute Webserver hält per WebSocket (/reload) eine Verbindung zum Browser; nach jeder Kompilierung sendet die CLI eine Nachricht, der Server broadcastet sie, der Browser lädt neu. Damit es beim Neuladen nicht flackert, nutzt Quarkdown Double Buffering mit zwei Iframes: Während A sichtbar bleibt, rendert das Update in B; ist es fertig, wird umgeschaltet — inklusive Wiederherstellung der Scroll-Position. Dasselbe Prinzip, mit dem deine GPU Frames zeichnet.
FAQ
Muss ich die Pipeline kennen, um Quarkdown zu nutzen?
Nein, kein bisschen. Dieser Teil ist reines Hintergrundwissen für technisch Interessierte. Für die tägliche Arbeit genügt alles aus den Teilen 1 bis 9.
Warum ist der FunctionCallNode der einzige veränderliche Node?
Weil sein Inhalt erst nach dem Aufbau des Baums entsteht — nämlich durch das Ausführen der Funktion. Alle anderen Nodes stehen mit ihrer Erzeugung fest; der Funktionsaufruf füllt seine Kinder dagegen erst in der Expansionsstufe.
Kann Quarkdown andere Formate als HTML ausgeben?
Ja — Rendering und Post-Rendering liegen in austauschbaren Modulen. Neben HTML existiert bereits ein Plaintext-Renderer; die Architektur ist bewusst darauf ausgelegt, weitere Ziele anzudocken.
Weiterlesen
Den anwendungsnahen Vorgänger bildet Teil 9: Quarkdown & KI. Im letzten Teil dieser Reihe sammle ich Tipps & Tricks aus allen vorherigen Themen — die kleinen Kniffe, die den Alltag mit Quarkdown leichter machen. Die ausführliche Pipeline-Dokumentation steht in der offiziellen Wiki.