Quarkdown: Markdown, das rechnet — Funktionen, Variablen, Skripting

Teil 2 der Quarkdown-Reihe: Funktionsaufrufe, Verkettung mit ::, Variablen, eigene Funktionen, Bedingungen und Schleifen. Wie aus Markdown eine echte kleine Programmiersprache wird.

von ·

In Teil 1 habe ich Quarkdown als Markdown-Obermenge mit einer einzigen großen Idee vorgestellt: dem Funktionsaufruf. Das klang harmlos — ein Punkt, ein paar geschweifte Klammern. Tatsächlich steckt dahinter das, was Quarkdown von jedem anderen Markdown-Dialekt trennt: echtes Skripting. Variablen, eigene Funktionen, Bedingungen, Schleifen, Mathematik — alles direkt im Dokument, alles in Markdown-Schreibweise. Genau das meint das Versprechen „Turing-vollständig". In diesem Teil gehe ich der Mechanik auf den Grund.

Die Anatomie eines Funktionsaufrufs

Ein Aufruf beginnt mit einem Punkt; Argumente stehen in geschweiften Klammern. Argumente können positional sein (ihre Bedeutung ergibt sich aus der Reihenfolge) oder benannt (name:{wert}) — Letzteres macht den Aufruf lesbarer:

.multiply {6} by:{3}

Aufrufe lassen sich verschachteln, indem du sie als Argument eines anderen Aufrufs einsetzt:

.multiply {.pow {3} to:{2}} by:{.pi}

Lesbar bleibt das nicht lange. Deshalb gibt es Quarkdowns elegantestes Detail — die Verkettung mit ::. Der Wert links wird zum ersten Argument der Funktion rechts. Aus dem kaum lesbaren

.sum {.subtract {.pow {3} {2}} {1}} {2}

wird die Zeile, die man tatsächlich wie Mathematik liest:

.pow {3} {2}::subtract {1}::sum {2}

Die Regel dahinter ist simpel: .a::b wird zu .b {.a}, .a::b::c zu .c {.b {.a}}. Weitere Argumente kannst du jederzeit anhängen — der verkettete Wert bleibt immer das erste.

Ein zweites wichtiges Konzept ist das Block- oder Body-Argument: der eingerückte Inhalt unter einem Aufruf, der dem letzten Parameter entspricht. Er kann mehrere Zeilen umfassen und selbst wieder Funktionen enthalten:

.row alignment:{center}
    Dieses Dokument stammt von .docauthor

    .column
        Es heißt .docname

Wichtig: Der gesamte Body teilt sich dieselbe Einrückung (mindestens zwei Leerzeichen oder ein Tab). Wer in einer Zeile vier Leerzeichen einrückt, erzeugt aus Versehen einen Code-Block — eine der wenigen Stolperfallen.

Variablen

Eine Variable definierst du mit .var {name} {wert} und greifst sie wie eine parameterlose Funktion ab:

.var {name} {Quarkdown}

Hallo, **.name**!

Neu zuweisen geht jederzeit — auch auf Basis des alten Werts:

.var {zahl} {5}
.zahl {.zahl::sum {1}}

Variablen sind nicht auf einfache Werte beschränkt. Weil ein Body-Argument mehrzeilig sein darf, kann eine Variable einen ganzen Layout-Block speichern und beliebig oft wiederverwenden:

.var {meinerow}
    .row gap:{2cm}
        A

        B

        C

.container background:{teal} padding:{1cm}
    .meinerow

Eigene Funktionen

Das eigentliche Werkzeug gegen Wiederholung ist .function. Sie nimmt einen Namen und einen Body; die Parameter stehen als param1 param2: in der ersten Body-Zeile:

.function {greet}
    to from:
    Hallo, .to von .from!

.greet {Welt} from:{John}

Drei Eigenschaften lohnen das Merken:

  • Optionale Parameter. Ein ? am Parameternamen macht ihn optional; fehlt das Argument, ist sein Wert None. Mit ::otherwise {…} emulierst du einen Default:

    .function {greet}
        to from?:
        Hallo, .to von .from::otherwise {unbekannt}!
  • Kein return. In Quarkdown gibt es keine Return-Anweisung — jede erreichte Anweisung wird Teil der Ausgabe. Eine Funktion kann beliebigen Markdown-Inhalt oder, weil die Sprache schwach typisiert ist, jeden Werttyp zurückgeben:

    .function {area}
        width height:
        .multiply {.width} by:{.height}
    
    Die Fläche beträgt **.area {4} {2}**.
  • Überschreiben. Eine Funktion lässt sich jederzeit neu deklarieren; ab dann gilt die neue Definition. Wer das verhindern will, kompiliert mit --forbid-function-overwriting und bekommt bei Namenskollisionen einen Fehler.

Bedingungen

.if wertet eine Boolesche Bedingung aus und gibt seinen Body nur dann aus. Die Funktion propagiert ihren Inhalt nach oben — du kannst sie also mitten in jeden Ausdruck setzen, etwa um in einem Layout bedingt Inhalt einzustreuen:

.row gap:{1cm}
    A

    .if {.iseven {3}}
        B

    C

Ein else gibt es (noch) nicht — aber mit .ifnot und .let lässt es sich sauber nachbauen:

.let {.iseven {3}}
    condition:
    .if {.condition}
        3 ist gerade!
    .ifnot {.condition}
        3 ist ungerade!

Schleifen

.foreach iteriert über jeden iterierbaren Wert — einen Zahlenbereich, eine Markdown-Liste, eine Variable. Der Clou: Die Funktion verhält sich wie ein map und gibt eine Collection derselben Größe zurück, sodass du sie als Ausdruck verwenden kannst. .1 referenziert implizit das aktuelle Element:

.row alignment:{spacearound}
    .foreach {1..5}
        n:
        .multiply {.n} by:{.n}

.repeat {n} ist die Kurzform für .foreach {1..n}.

Mathematik, lesbar

Spätestens bei Mathematik zahlt sich die Verkettung aus. Die Fläche eines Kreises:

.var {radius} {8}

Die Fläche beträgt
.pow {.radius} to:{2}::multiply {.pi}::truncate {2}.

Dieselbe Rechnung verschachtelt wäre ein Klammerknäuel — verkettet liest sie sich von links nach rechts wie eine natürliche Formel. Die vollständige Liste der Mathe-Funktionen steht in der Standardbibliothek.

FAQ

Ist das nicht zu viel Programmierung für ein Dokument?

Du entscheidest, wie weit du gehst. Für einen einfachen Text brauchst du nichts davon. Skripting zahlt sich erst dort aus, wo Wiederholung oder Dynamik ins Spiel kommt — dieselbe Box zwanzigmal, eine generierte Tabelle, eine Berechnung im Fließtext. Dann ersetzt eine Funktion das Copy-paste.

Warum :: statt verschachtelter Klammern?

Beides ist erlaubt und gleichwertig — .a::b ist exakt .b {.a}. Die Verkettung existiert allein der Lesbarkeit wegen: Sie dreht die Leserichtung von „innen nach außen" zu „links nach rechts", wie man Rechenschritte ohnehin denkt.

Gibt es Datentypen?

Ja, aber schwach typisiert: Text, Zahlen, Booleans, Listen, Dictionaries, Ranges, Farben, Größen und mehr. Werte werden bei Bedarf konvertiert, und der Typ iterierter Elemente bleibt erhalten. Den Typen widme ich später einen eigenen Abschnitt der Reihe.

Weiterlesen

Den Einstieg und die Einordnung liefert Teil 1: Wenn Markdown Textsatz lernt. Im nächsten Teil verlasse ich die Logik-Ebene und gehe zum sichtbaren Ergebnis über: Dokumenttypen, Themes und Layout — wie aus derselben Quelle Webseite, Paper, Buch oder Präsentation wird. Die vollständige Funktionsreferenz findest du jederzeit in der offiziellen Wiki und der Standardbibliothek.