Rückblick: Wo wir herkommen
Im vorherigen Artikel DSLs mit Tcl - oder warum YAML nicht automatisch menschenlesbar ist habe ich dargelegt, warum Tcl für DSLs so überzeugend ist:
- kein Parser
- keine Grammatik
- Befehle sind die Sprache
- sicherer Interpreter inklusive
Die These lautete:
Für kleine, explizite DSLs gibt es fast nichts, das so unmittelbar funktioniert wie Tcl.
Das gilt weiterhin.
Doch während Tcl Ende der 80er entstand, entwickelte sich fast zeitgleich eine weitere Skriptsprache mit vergleichbarer Ambition: Lua (1993). Die beiden Sprachen teilen zentrale Designideen – doch Lua ist syntaktisch flexibler. Dieser Artikel beleuchtet diese Parallele.
Lua – die zweite große Minimal-Skriptsprache der 90er
Heute ist Lua in Milliarden von Geräten eingebettet:
- Cisco-Router
- Nginx-Skripting
- Redis
- Adobe Lightroom
- Spiele wie Luanti (Minetest), WoW, Roblox, Factorio
Aber für diesen Artikel wichtiger:
Lua ist – wie Tcl – eine radikal vereinfachte Sprache mit klarer Semantik und kleinem Kern.
Das zentrale Feature: Lua erlaubt klammerlose Funktionsaufrufe und verfügt mit Tabellenliteralen über eine eingebaute, literale Datenstruktur. Diese Kombination ermöglicht DSLs, die visuell deutlich datennäher wirken als typische Tcl-DSLs.
- Offizielle Website: https://www.lua.org
- Dokumentation: https://www.lua.org/manual/5.4/
- FAQ: https://www.lua.org/faq.html
Warum Lua für DSLs so attraktiv ist
Die Gründe ähneln stark denen von Tcl – aber mit syntaktischer Variation:
- minimale Syntax
- Funktionen können wie Variablen behandelt werden
- Tabellen als Universal-Datenstruktur
- Interpreter < 300 KB
- leicht einbettbar
- sichere Ausführung über getrennte Environments
- sehr einfache, lineare Semantik
- klammerlose Funktionsaufrufe möglich
Diese Kombination ergibt:
Lua eignet sich hervorragend für DSLs, die wie Konfiguration aussehen sollen – nicht wie Programmcode.
Die Besonderheit: Funktionsaufrufe ohne runde Klammern
Lua erlaubt:
say "Hallo"
statt:
say("Hallo")
Und Tabellenliterale erlauben blockartige Strukturen:
buchung {
date = "2025-01-10",
amount = 45,
}
Dadurch entsteht eine DSL, die sich visuell an YAML/HCL/TOML anlehnt – aber ohne Parserlogik auskommt.
Beispiel: Eine klammerlose DSL für Konten & Buchungen
So könnte eine DSL-Datei aussehen, die Menschen intuitiv verstehen:
Datei buchungen.dsl:
account "Kasse" "asset"
account "Bank" "asset"
account "Erlöse_19" "income"
account "Miete" "expense"
account "Telefon" "expense"
buchung {
date = "2025-01-03",
debit = "Kasse",
credit = "Erlöse_19",
amount = 119.00,
desc = "Barverkauf"
}
buchung {
date = "2025-01-04",
debit = "Miete",
credit = "Bank",
amount = 800.00,
desc = "Büromiete"
}
buchung {
date = "2025-01-10",
debit = "Telefon",
credit = "Bank",
amount = 45.00,
desc = "Telefonrechnung"
}
Keine runden Klammern. Keine „Programmcode-Optik“. Sehr nahe an menschenlesbarer Konfiguration.
Umsetzung in Lua: kompakt, klar, robust
local Konten = {}
local Buchung = {}
function account(name)
return function(type)
Konten[name] = type
end
end
function buchung(tbl)
table.insert(Buchung, tbl)
end
Jede Zeile der DSL löst einfach einen Funktionsaufruf aus – ohne Parser, ohne Grammatik.
Semantik statt Struktur: einfache EÜR
local function compute_eur()
local eur = { income = 0.0, expense = 0.0 }
for _, b in ipairs(Buchung) do
if Konten[b.debit] == "expense" then
eur.expense = eur.expense + b.amount
end
if Konten[b.credit] == "income" then
eur.income = eur.income + b.amount
end
end
print("Einnahmen:", eur.income)
print("Ausgaben:", eur.expense)
print("Ergebnis:", eur.income - eur.expense)
end
Sicherer Interpreter: Lua-Sandbox ohne großen Aufwand
Analog zu Tcl kann Lua in einem streng kontrollierten Environment ausgeführt werden:
local env = {
account = account,
buchung = buchung
}
setmetatable(env, { __index = function() return nil end })
local chunk = assert(loadfile("buchungen.dsl", "t", env))
chunk()
compute_eur()
Damit sind alle Standardfunktionen wie os.execute, io.open, debug usw. unsichtbar.
Lua-DSLs werden so zu kontrollierten, auditierbaren Datenformaten.
Tcl vs. Lua für DSLs – ein nüchterner Vergleich
| Kriterium | Tcl | Lua |
|---|---|---|
| Syntaxstil | befehlsorientiert | datennah, konfigurationsähnlich |
| Syntaxkomplexität | minimal | minimal + klammerlos möglich |
| Haupt-Datenstruktur | Listen | Tabellen (assoziativ + sequentiell) |
| „Look & Feel“ einer DSL | Shell-artig | Konfigurationsartig |
| Parserbedarf | keiner | keiner |
| Safe Interpreter | eingebaut | über Environments trivial |
| Einbettbarkeit | sehr gut | sehr gut |
| Interpretergröße | ~1 MB | 250–300 KB |
| Lernkurve für Nutzer | sehr niedrig | sehr niedrig |
Interpretation:
Tcl ist ideal für befehlsartige DSLs. Lua ist ideal für datennahe DSLs.
Beide verfolgen ähnliche Prinzipien, aber mit unterschiedlichem syntaktischen Ausdruck.
Wann Lua die bessere Wahl ist
Lua gewinnt, wenn:
- DSLs fast wie Konfigurationsdateien aussehen sollen
- Tabellen als natürliche Datenstrukturen wichtig sind
- niedriger syntaktischer Lärm entscheidend ist
- der Interpreter extrem klein sein soll
- klammerlose Lesbarkeit gewünscht ist
- Einbettung in C, Go, Rust oder eingebettete Systeme wichtig ist
Kurz gesagt:
Wenn eine DSL „wie Daten“ aussehen soll, ist Lua oft die angenehmere Wahl.
Fazit: Zwei Sprachen, ein Prinzip – unterschiedliche Ausdrucksformen
Tcl und Lua entstanden unabhängig voneinander, verfolgen aber beide die gleiche Idee:
- Minimalismus
- Klarheit
- Einbettbarkeit
- explizite Semantik
- keine Parserketten
Der Unterschied liegt im Stil:
- Tcl wirkt wie Kommandozeile
- Lua wirkt wie Konfiguration
Was Lua für mich besonders attraktiv macht, ist nicht nur die Syntax oder der kleine Kern – sondern die Vielfalt an Interpreter-Implementierungen, die sich extrem tief in andere Host-Sprachen einbetten lassen.
Neben der Standard-VM existieren u.a.:
- LuaJIT (JIT-optimiert, sehr schnell)
- Native Go-Implementierungen wie gopher-lua
- weitere spezialisierte VMs für Embedded- und IoT-Umgebungen
Diese Varianten machen Lua zu einer Sprache, die sich nahtlos in bestehende Systeme integrieren lässt. Ich nutze Lua u.a. in:
- Lunaria – einer experimentellen, in Go implementierten Low-Code-Plattform
- regelgetriebenen Microservices, bei denen komplexe Business-Logik als Konfiguration formuliert wird (z.B. Dokumentverarbeitungsregeln)
Für diese Anwendungsfälle würde ich heute bewusst Lua gegenüber anderen DSL-Ansätzen bevorzugen: leichtgewichtig, auditierbar, portabel – und tief einbettbar.