Austauschbar, aber anders: Lua und Tcl im DSL-Vergleich

Austauschbar, aber anders: Lua und Tcl im DSL-Vergleich
By Matthias Petermann / on 30.11.2025

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

ℹ️ Lua in zwei Sätzen
Lua wurde 1993 an der PUC-Rio entwickelt, um eine einbettbare, leichtgewichtige, vollständig in ANSI-C implementierte Skriptsprache zu schaffen. Im Fokus: Minimalismus, Einfachheit, Integration in bestehende Systeme.

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.

💡 Weiterführendes zu Lua

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
🚀 Persönliche Einordnung aus der Praxis

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.