Warum Menschenlesbarkeit mehr ist als Einrückung und Klammern
Wer Konfigurationen oder Datendateien von Hand pflegen muss, landet fast automatisch bei YAML, JSON oder XML. Alle drei sind etablierte Standards – und alle drei sind aus der Maschine heraus gedacht.
YAML:
- whitespace-sensitiv
- je nach Parser überraschend „magisch“
JSON:
- Kommentare verboten
- unflexibel für Menschen
XML:
- überstrukturierte Tag-Landschaften
Der Anspruch lautet „menschenlesbar“, aber das Ergebnis ist oft das Gegenteil: viel Syntax bei wenig mentalem Modell.
Dabei gibt es eine Alternative, die fast niemand mehr auf dem Schirm hat.
Eine kleine, klar definierte DSL – implementiert als normaler Code in Tcl.
Keine Parserhölle, keine Bibliotheken, keine Überraschungen. Stattdessen: explizite Semantik und ein minimales, aber robustes Format.
Eine Domain-Specific Language (DSL) ist eine kleine, klar abgegrenzte Sprache, die auf einen spezifischen Anwendungsbereich zugeschnitten ist – z. B. Buchhaltung, Build-Prozesse, Infrastrukturdefinition oder Automatisierungsregeln.
Im Gegensatz zu universellen Formaten wie YAML oder JSON ist eine DSL:
- klein (nur die benötigten Konstrukte),
- präzise (weniger Interpretationsspielraum),
- auditierbar (Regeln sind explizit definiert),
- robust (keine komplexen Parser notwendig).
DSLs reduzieren Komplexität, indem sie nur das ausdrücken, was in der Domäne erlaubt ist.
Warum Tcl ideal für kleine DSLs ist
Tcl (Tool Command Language) ist eine kompakte, interpretierte Sprache mit einem äußerst einfachen Kern: Alles besteht aus Befehlen und Argumenten. Der Interpreter ist klein, stabil und gut in C-Programme einbettbar – Faktoren, die Tcl bis heute in vielen Systemen präsent halten.
Wichtig für diesen Artikel: Tcl benötigt keine eigene Grammatik und kaum Syntax. Diese Reduktion macht die Sprache zu einem natürlichen Fundament für kleine, klar definierte DSLs.
Unter den heute gebräuchlichen Sprachen ist Tcl das einzige mir bekannte Beispiel, in dem sich DSLs mit extrem wenig Aufwand realisieren lassen – nicht weil Tcl „besser" wäre, sondern weil sein Sprachdesign Mini-Sprachen nahezu natürlich begünstigt.
Tcl ist radikal einfach gestaltet. Der Kern der Sprache passt auf eine Postkarte:
- Jede Zeile ist ein Befehl.
- Ein Befehl besteht aus Wörtern.
- Das erste Wort ist der Name.
- Der Rest sind Argumente.
Dadurch entsteht eine bemerkenswerte Eigenschaft:
Eine DSL sieht aus wie Konfiguration – ist aber gültiger Tcl-Code.
Beispiel:
user "alice"
port 8080
enable-feature caching
Drei einfache Prozeduren – und schon existiert eine kleine Sprache.
- Keine Grammatik
- Kein Parsergenerator
- Kein AST
- Kein Overhead
Eine DSL entsteht, indem man einfach Befehle definiert, die danach wie Syntax aussehen.
Das ist menschenlesbar. Das ist auditierbar. Und es ist meilenweit entfernt vom YAML-Mikado.
Mini-DSL für Buchhaltung: Konten und Buchungen
Bleiben wir bei einem praktischen Beispiel: einer kleinen DSL für Buchungen im Sinne der doppelten Buchführung.
Ziel:
- Konten definieren
- Buchungen erfassen
- am Ende eine einfache EÜR berechnen
DSL-Datei (buchungen.dsl):
# Konten
account Kasse asset
account Bank asset
account Erlöse_19 income
account Miete expense
account Telefon expense
# Buchungen (Soll an Haben)
booking 2025-01-03 Kasse Erlöse_19 119.00 "Barverkauf"
booking 2025-01-04 Miete Bank 800.00 "Büromiete"
booking 2025-01-10 Telefon Bank 45.00 "Telefonrechnung"
Das liest sich wie eine Protokolldatei – ist aber komplett ausführbar.
Implementieren der DSL: zwei Befehle, kein Parser
array set ::accountType {}
set ::bookings {}
proc account {name type} {
set ::accountType($name) $type
}
proc booking {date debit credit amount args} {
set desc [join $args " "]
lappend ::bookings [list $date $debit $credit $amount $desc]
}
Mehr braucht es nicht. Jede DSL-Zeile entspricht exakt einem Funktionsaufruf.
EÜR daraus erzeugen
proc compute_eur {} {
array set eur {income 0.0 expense 0.0}
foreach b $::bookings {
lassign $b date debit credit amount desc
if {[info exists ::accountType($debit)]
&& $::accountType($debit) eq "expense"} {
set eur(expense) [expr {$eur(expense) + $amount}]
}
if {[info exists ::accountType($credit)]
&& $::accountType($credit) eq "income"} {
set eur(income) [expr {$eur(income) + $amount}]
}
}
puts "Einnahmen: $eur(income)"
puts "Ausgaben: $eur(expense)"
puts "Ergebnis: [expr {$eur(income) - $eur(expense)}]"
}
Die Einnahmen-Überschuss-Rechnung (EÜR) ist ein einfaches Verfahren zur Ermittlung des steuerlichen Gewinns. Sie stellt lediglich Einnahmen und Ausgaben eines Jahres gegenüber:
- Einnahmen (z. B. Erlöse, Honorare)
- Ausgaben (z. B. Miete, Telefon, Material)
Der Gewinn ergibt sich als einfache Differenz:
Gewinn = Einnahmen – Ausgaben
Die EÜR eignet sich für kleinere Unternehmen und Selbständige, weil sie ohne Bilanzierung auskommt und auf tatsächlichen Zahlungsflüssen basiert.
Aber Moment: „Das ist doch Code? Ist das nicht gefährlich?“
Die Frage ist berechtigt – aber genau hier glänzt Tcl.
Tcl bietet einen eingebauten Safe Interpreter:
Eine Sandbox, in der nur explizit erlaubte Befehle existieren. Alles andere ist verboten.
Keine Systembefehle, kein Dateizugriff, kein Netzwerk, keine Nebenwirkungen.
Damit verwandelt sich die DSL in ein kontrolliertes Datenformat, auch wenn sie formal Code ist.
Sichere Ausführung im Safe Interpreter
Safe Interpreter erstellen
set safe [interp create -safe]
Nur unsere DSL-Befehle erlauben
interp alias $safe account {} account
interp alias $safe booking {} booking
DSL-Datei sicher ausführen
interp eval $safe [read [open $file]]
Was passiert, wenn jemand in die Datei schreibt:
exec rm -rf /
Antwort: Der Befehl existiert im Safe Interpreter nicht. Er ist schlicht nicht vorhanden.
Keine Angriffsmöglichkeit – keine Magie.
Lauffähiges Beispiel
TCL-Datei (calc.tcl):
#!/usr/bin/env tclsh
# DSL-Daten
array set ::accountType {}
set ::bookings {}
proc account {name type} {
set ::accountType($name) $type
}
proc booking {date debit credit amount args} {
lappend ::bookings [list $date $debit $credit $amount [join $args " "]]
}
proc compute_eur {} {
array set eur {income 0.0 expense 0.0}
foreach b $::bookings {
lassign $b date debit credit amount desc
if {[info exists ::accountType($debit)] && $::accountType($debit) eq "expense"} {
set eur(expense) [expr {$eur(expense) + $amount}]
}
if {[info exists ::accountType($credit)] && $::accountType($credit) eq "income"} {
set eur(income) [expr {$eur(income) + $amount}]
}
}
puts "Einnahmen: $eur(income)"
puts "Ausgaben: $eur(expense)"
puts "Ergebnis: [expr {$eur(income) - $eur(expense)}]"
}
# Safe Interpreter
set safe [interp create -safe]
interp alias $safe account {} account
interp alias $safe booking {} booking
# Datei laden
set file [lindex $argv 0]
interp eval $safe [read [open $file]]
compute_eur
Test
$ ./calc.tcl buchungen.dsl
Einnahmen: 119.0
Ausgaben: 845.0
Ergebnis: -726.0
Vergleich: gleiche Daten in YAML, JSON, XML
| Format | Lesbarkeit | Werkzeugbedarf | Überraschungen | Kommentar |
|---|---|---|---|---|
| YAML | ✖ mittel | hoch | hoch (Whitespace, Typen) | komplex, versteckte Magie |
| JSON | ✖ gering | hoch | mittel | keine Kommentare, formal strikt |
| XML | ✖ gering | hoch | gering | überstrukturiert |
| Tcl-DSL | ✔ hoch | minimal | sehr gering | Befehle definieren, fertig |
Die DSL gewinnt, weil sie weniger Syntax und mehr Semantik hat.
Wann DSLs bessere Datenformate sind
- Wenn Menschen die Dateien wirklich lesen und schreiben sollen
- Wenn Semantik wichtiger ist als Struktur
- Wenn Auditierbarkeit entscheidend ist
- Wenn Parserabhängigkeiten vermieden werden sollen
- Wenn Konfigurationen eher „Sätze“ als „Objekte“ sind
Fazit: Kleine DSLs als echte Alternative zu YAML & Co.
Mini-DSLs erzeugen Formate, die explizit, knapp und auditierbar sind – ohne die Komplexität gängiger Standardformate. Tcl dient hier lediglich als Beispiel; entscheidend ist das Prinzip:
Eine DSL definiert Semantik direkt – ohne Parser-Schichten, ohne implizite Typen, ohne Überraschungen.
Wer Transparenz und Robustheit ernst nimmt, sollte DSLs wieder als ernsthafte Option betrachten.
Tcl spielt seine Stärken dort aus, wo Einfachheit, Klarheit und Verlässlichkeit wichtiger sind als modische Spracheigenschaften:
- In eingebetteten Umgebungen: Tcl fügt sich nahtlos in C-Programme ein und eignet sich hervorragend als integrierte Skript- oder Konfigurationsschicht.
- In langlebigen Systemen: Die Sprache ist außergewöhnlich stabil, vorhersehbar und frei von syntaktischen Überraschungen.
- In schlanken Architekturen: Tcl benötigt keine Parser-Stacks, keine Generatoren, keine Zusatzwerkzeuge – Befehle sind die Sprache.
- In Umgebungen mit begrenzten Ressourcen: Der Interpreter bleibt kompakt, portabel und trägt kaum Overhead in Software- oder Hardware-Budgets ein.
- Wenn Konfiguration und Logik nicht getrennt werden sollen: DSLs können sowohl deklarativ als auch operational sein – ohne zusätzliche Template-Systeme oder Zwischenformate.
- Wenn Auditierbarkeit wichtiger ist als syntaktische Mode: Tcl-DSLs bleiben transparent, linear und gut nachvollziehbar.
Kurz: Tcl glänzt überall dort, wo Robustheit, Einbettbarkeit und Einfachheit echten Wert haben.
Der gezeigte Ansatz ist bewusst minimalistisch:
- keine Kontenrahmenlogik
- keine Verprobung
- keine Periodenfilter
- keine Belege
Es geht um das Prinzip: Wie man mit minimalem Aufwand ein lesbares und auditierbares Datenformat erhält – ohne YAML-Magie.
So entstehen robuste, transparente Strukturen – wenn man sie bewusst gestaltet.