Klein, schlank und schnell – mit diesen Attributen eroberte Lua die Herzen der Spielehersteller. Aber auch in ernsteren Anwendungsbereichen beweist die Skriptsprache, deren Name auf Portugiesisch Mond bedeutet, ihre Leistungsfähigkeit – und dies nicht nur durch den Einsatz einiger ungewöhnlicher Konzepte.
Als Bret Mogilefsky 1996 eine kleine Skriptsprache aus Brasilien auffiel, programmierte er bei Lucas Arts gerade an dem Adventure “Grim Fandango” (Abbildung 1). Die neu entdeckte Sprache mit dem Namen Lua gefiel ihm so gut, dass er sie umgehend zur Steuerung der Spielszenen einbaute. Dies bedeutete gleichzeitig den Durchbruch für den brasilianischen Export. Immer mehr Spielehersteller entdeckten die Vorzüge der kleinen und schnellen Sprache. Endlich konnten sie die künstliche Intelligenz in Skripte auslagern und noch zur Laufzeit modifizieren.

Abbildung 1: “Grim Fandango” ist das erste größere Programm, das Lua einsetzt – wenn auch leider nur unter Windows.
Aber auch in ernsthaften Anwendungen, in denen Skripte Abläufe oder Konfigurationen steuern, kam und kommt Lua immer häufiger zum Einsatz: Angefangen bei Simulationen, über Netzwerksoftware wie Ethereal bis hin zu Adobes neuem Fotoprogramm Lightroom.
Seinen Ursprung hat Lua an der Pontifical Catholic University in Rio de Janeiro, wo 1993 drei Mitarbeiter begannen eine neue Skriptsprache zu entwickeln. Roberto Ierusalimschy, Luiz Henrique de Figueiredo und Waldemar Celes hatten sich zuvor schon zwei einfachere Sprachen zur Datenauswertung und zur Konfiguration eines Report-Generators ausgedacht.
Deren klare Strukturen bohrten sie nun zu einer vollwertigen Sprache auf und tauften das Ergebnis auf den portugiesischen Namen für Mond: Lua. Die neue Skriptsprache konzentriert sich lediglich auf einige wenige, einfache Konzepte. Damit bleibt die Syntax zwar schön kurz und knackig, komplexe Konstrukte aus anderen Sprachen, beispielsweise eine Objektorientierung, muss man jedoch mit den vorhandenen Mitteln simulieren.
Mikro-Interpreter
Parallel zur Sprachsyntax entwickelten die drei Programmierer gleich den passenden Interpreter. Er ist extrem schnell, hochgradig portabel und lässt sich zudem leicht in C-Programme einbetten. Obwohl er nur wenige Kilobyte umfasst, passt noch eine vollständige Garbage Collection hinein, die anfallenden Datenmüll automatisch aus dem Speicher wirft. Das Ergebnis veröffentlichten die drei Kollegen zunächst unter einer eigenen, BSD-ähnlichen Lizenz. Seit Version 5 steht Lua unter der ebenfalls freien MIT-Lizenz. In beiden Fällen ist der Lizenzkosten-freie Einsatz auch in kommerziellen Projekten erlaubt.
Statt eines allzu trivialen Hello World soll ein komplexeres Beispiel die Lua-Syntax etwas näher vorstellen, nämlich die Berechnung des größten gemeinsamen Teilers (Listing 1).
|
Listing 1: Größter |
|---|
01 -- Berechne den größten gemeinsamen Teiler von a und b 02 -- Kommentare beginnen mit zwei Minuszeichen 03 function ggT(a,b) 04 if (b == 0) then 05 return a 06 elseif a > b then 07 return ggT(a-b, b) 08 else return ggT(a, b-a) 09 end 10 end 11 12 -- Hauptschleife, berechne ein paar ggTs 13 x=1 14 while(x < 10) 15 do 16 print (ggT(x, 27)) 17 x = x + 1 18 end |
Lua bietet eine durchgehende dynamische Typbindung, eine Variable darf also zu jedem Zeitpunkt beliebige Daten aufnehmen. Doch damit nicht genug der einfachen Zuweisungen: Da es erlaubt ist, statt »a=1; b=2;« auch »a, b = 1,2« zu schreiben, vertauscht »x, y = y, x« den Inhalt der beiden Variablen.
Auch wenn der Programmierer nur selten mit ihnen in Berührung kommt, unterscheidet Lua unter anderem zwischen folgenden Basistypen: Nil (die Variable hat keinen Wert), Boolean (wahr oder falsch), Number (Fließkommazahlen mit doppelter Genauigkeit), String (Zeichenketten).
Hashes mit Tabellen
Lua kennt mit den so genannten Tabellen nur eine einzige komplexe Datenstruktur, die es dafür aber in sich hat. Konkret handelt es sich um assoziative Arrays, die in der freien Wildbahn auch als Dictionaries ihr Unwesen treiben. Ganz ihrem Namen folgend, darf man sie sich wie eine Tabelle mit zwei Spalten vorstellen, die automatisch mitwächst, sobald neue Elemente hinzukommen. Zusätzlich betrachtet Lua sie als (spezielle) Objekte, womit sie sogar in Variablen gespeichert werden können:
a = {} -- Tabelle anlegen
a[1]=1
a["hallo"]=3
Wie von einem klassischen Dictionary gewöhnt, dürfen als Index beliebige Werte herhalten. Die geschweiften Klammern bezeichnet Lua als Constructor, über den Programmierer bei Bedarf die Tabelle initialisieren:
a = {"Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"}
Auf den Tabellen beruhen alle von anderen Sprachen bekannten Datenstrukturen. Dienen beispielsweise nur Zahlen als Indizes, ist das Ergebnis ein herkömmliches Array. Über verschachtelte Tabellen lassen sich Matrizen oder zweidimensionale Arrays erzeugen. Letzteres sieht in anderen Sprachen dank einer speziellen Notation zwar eleganter aus, dafür bietet Lua bei spärlich besetzten oder nicht quadratischen Matrizen (wie einer Dreiecksmatrix) Speichervorteile: Da die Tabellen dynamisch wachsen, belegen sie immer nur so viel Speicher wie nötig. Um weitere Sprachelemente auf die Tabellen abbilden zu können, führten die Lua-Erfinder noch eine Kurzschreibweise ein. Danach ist »a.[“name”]=1« gleichbedeutend mit »a.name=1«.
Funktionen
Die Berechnung des größten gemeinsamen Teilers aus Listing 1 liefert bereits ein Beispiel für die Definition einer Funktion. Im Gegensatz zu Sprachen wie C oder Java, können sie unter Lua auch mehrere Werte zurückliefern:
function foo(x,y) return x*2, y*5 end
a,b=foo(1,2)
Funktionen betrachtet Lua als so genannte First Class. Das bedeutet, dass der Entwickler mit ihnen alles anstellen darf, was auch mit Zahlen und Strings gestattet wäre. So kann er sie ruhigen Gewissens als Argumente an eine andere Funktion übergeben oder in Variablen verstauen. Tatsächlich ist
function foo (x) return 2*x end
nur eine andere Schreibweise für:
foo = function (x) return 2*x end
Die Definition einer Funktion wird damit zu einer einfachen Zuweisung.
Das Spiel lässt sich aber noch weiter treiben, etwa eine Funktion in einer Tabelle ablegen:
tabelle = {}
tabelle.add = function (a,b) return a + b end
Lua ist von Natur aus keine objektorientierte Sprache. Alle von diesen bekannten Konzepte simuliert sie mit Hilfe der Tabellen:
obj = {a = 0}
function obj.foo (x)
obj.a = obj.a + x
end
Dieser Code erzeugt ein Objekt »obj«, das eine Methode »foo« und das Attribut »a« enthält. Intern belegen beide jeweils einen Eintrag in der Tabelle. Allerdings verwendet die Methode im Beispiel explizit die Tabelle »obj«, womit Folgendes zu einem Fehler führt:
b = obj obj = nil b.foo(4)
Abhilfe schafft »self«, eine spezielle Referenz auf die eigene Tabelle, in anderen Sprachen auch als »this« bekannt:
function obj.foo (self, x) self.a = self.a +x end
Um diesen Parameter nicht immer ausschreiben zu müssen, gibt es dafür auch diese Schreibweise:
function obj:foo (x) self.a = self.a + x end
|
Nebenläufigkeit |
|---|
|
Der Ansi-C-Standard umfasst leider keine Behandlung von Threads. Damit Lua nicht ihre Plattformunabhängigkeit verliert, verzichtet die Skriptsprache auf eine direkte Unterstützung. Als Kompensation bietet sie so genannte Coroutines. Solche Funktionen können ihre Bearbeitung unterbrechen und auf Kommando zu einem späteren Zeitpunkt wieder fortsetzen. Die dafür benötigten Hilfsfunktionen liegen in der Tabelle »coroutine« (Listing 2). In diesem Fall stoppt die Koroutine am Befehl »yield()« (Zeile 4). Eine andere Funktion stößt dann die Weiterverarbeitung über »resume()« wieder an. |
|
Listing 2: |
|---|
01 co = coroutine.create(function () 02 for i=1,10 do 03 print(i) 04 coroutine.yield() 05 end 06 end) |
Der Aufruf erfolgt per »b:foo(4)«, der Doppelpunkt steht also für ein verstecktes »self«. Alle anderen Konzepte aus der Objektorientierung lassen sich auf einem ähnlichen Weg nachbilden, wenngleich sie schon größere Verrenkungen erfordern (siehe Kasten “Klassisches mit Metatables”).
|
Klassisches mit |
|---|
|
Um Objektorientierung einigermaßen vollständig zu simulieren, bedarf es eines Blicks hinter die Kulissen: In Lua besitzt jede Tabelle eine so genannte Metatabelle. In ihr ist genau verzeichnet, wie sie sich in welcher Situation verhält. Addiert die Anweisung »a+b« beispielsweise zwei Tabellen, schaut Lua nach, ob es in einer der beiden Metatabellen einen Eintrag mit dem Namen »__add« gibt. Wenn dies der Fall ist, führt Lua die dort abgelegte Funktion (die so genannte Metamethode) aus. Mit Hilfe dieser Metatabellen realisiert der Entwickler das Konzept der Klassen, wobei das Schlüsselwort »local« den Gültigkeitsbereich einschränkt und somit private Elemente simuliert:
meineKlasse = {}
meineKlasse_mt = { __index = meinKlasse }
-- Erzeuge Objekt:
function meineKlasse:create()
local objneu = {}
-- Alle Instanzen mit gleicher Metatabelle:
setmetatable( objneu, meineKlasse_mt )
return objneu
end
|
Sanft gebettet
Lua ist von Anfang an als eingebettete Skriptsprache ausgelegt. Daher verwundert es nicht, dass der Interpreter aus einer kleinen C-Bibliothek besteht. Sie bietet eine recht einfache Schnittstelle, über die der Entwickler einerseits Lua-Skripte um neue Funktionen erweitern, aber auch umgekehrt das C-Programm aus Lua fernsteuern kann.
Um aus einem C-Programm ein Lua-Skript zu starten, dürfen zunächst die passenden Header nicht fehlen: »lua.h«, »lualib.h« und »lauxlib.h«. C++-Programmierer dürfen dabei das »extern “C”« nicht vergessen. In der Main-Funktion erzeugt die folgende Anweisung dann einen Interpreter:
lua_State *interp = luaL_newstate();
In Lua-Versionen vor 5.1 geschah dies übrigens noch über »lua_open()«.
Der im Beispiel erzeugte Interpreter umfasst nur das Nötigste, um Lua-Skripte auszuführen, sogar das »print« fehlt. Alle derartigen Standardfunktionen stecken in eigenen Bibliotheken, die der Programmierer nach Bedarf explizit hinzuladen muss. Auch hier richtet sich die Vorgehensweise nach der verwendeten Lua-Version.
Bis zur Ausgabe 5.0 mussten alle benötigten Bibliotheken über Funktionsaufrufe wie »luaopen_base(interp)«, »luaopen_table(interp)« oder »luaopen_io(interp)« in den Interpreter geladen werden. Nun genügt der Aufruf »luaL_openlibs(interp)«, wobei Version 5.1 erwartet, dass das Lua-Skript selbst diese Aufgabe übernimmt.
Ein Lua-Skript lässt sich über den eingebetteten Interpreter aufrufen, indem »luaL_dostring(interp, Lua-Befehl)« einen String mit dem Lua-Code übergibt. Alternativ lädt »luaL_dofile(interp, “./ggt.lua”)« die Anweisungen direkt aus einer Datei. Die allfälligen Aufräumarbeiten übernimmt »lua_close(interp)«.
Übersetzung in zwei Phasen
Nachdem der Interpreter das Lua-Skript geladen hat, übersetzt er den darin enthaltenen Quellcode zunächst in eine interne binäre Repräsentation, den so genannten Zwischen- oder Bytecode. Mit dem Lua-Compiler lassen sich die Übersetzungs- und Ausführungsphasen trennen, das erspart beim Laden vieler Skripte Zeit:
luac -o meinskript.luo meinskript.lua
Die Endung der Objektdatei darf – wie die der Lua-Skripte – beliebig gewählt werden. Dem Lua-Interpreter ist es übrigens egal, ob er mit dem Quellcode oder der binären Variante gefüttert wird.
Der Aufruf einer C-Funktion aus Lua ist kaum komplizierter. Dazu meldet man zunächst im C-Programm die entsprechende Funktion beim Interpreter an:
lua_register( interp, "addiere", lf_addiere );
Ab jetzt kennen die darin ausgeführten Skripte die Funktion »lf_addiere()« unter dem Namen »addiere()«.
Der Austausch von Parametern erfolgt recht pfiffig über einen gemeinsamen Stack. Taucht beispielsweise im Lua-Skript die Funktion »addiere(2, 5)« auf, packt Lua nacheinander die beiden Parameter auf den Stack und ruft anschließend die C-Funktion »lf_addiere()« auf. Diese holt dann die beiden Werte wieder vom Stack und wertet sie aus. Alle Funktionen, die von einem Lua-Skript aufgerufen werden, müssen dabei stets der Form »static int name(lua_State*)« folgen. »lf_addiere« sieht demnach so aus wie in Listing 3.
|
Listing 3: C-Funktionen |
|---|
01 static int lf_addiere(lua_State* interp) {
02 double a,b;
03 a=lua_tonumber (interp, -2);
04 b=lua_tonumber (interp, -3);
05 /* ... */
06 return 1; /* Anzahl der Ergebnisse */
07 }
|
Sobald Lua die Funktion aufruft, übergibt sie zunächst einen Zeiger auf den Interpreter. Er ist nötig, um an den Stack zu gelangen. Von ihm holen die beiden »lua_tonumer()«-Aufrufe dann die Werte ab. Sie verlangen als zweites Argument die Position auf dem Stack. An Position »-1« liegt immer der Name der aufgerufenen Funktion. Bei positiven Werten liegt das oberste Element auf der Position »lua_gettop()«).
Neben »lua_tonumber()« gibt es noch für jeden anderen Lua-Datentyp eine Entsprechung, etwa »lua_tostring()«, das einen String vom Stack holt. Der Rückgabewert von »lf_addiere()« teilt dem Lua-Skript mit, wie viele Ergebnisse die C-Funktion auf den Stack gelegt hat.
Mit dem Stack lassen sich noch ein paar andere nette Dinge anstellen. Die globalen Variablen eines Skripts liefert beispielsweise »lua_getglobal(interp, “Variable”)«. Diese Funktion legt alle Variableninhalte auf den Stack, wo die bekannten Funktionen sie abholen. So lässt sich sehr einfach eine Konfigurationsdatei einlesen, die etwa so aussieht:
a=1 b=2
Das ist ein gültiges Lua-Skript. Das C-Programm startet den Lua-Interpreter, führt die Konfigurationsdatei aus und zeigt den Inhalt der Variablen – einfacher geht es kaum. Über den Stack ruft man auch aus einem C-Programm gezielt eine Lua-Funktion auf. Dazu schiebt zunächst »lua_getglobal(interp, “foo”)« den Namen der Funktion auf den Stack. Falls nötig, folgen die Argumente:
lua_pushnumber(interp, x); lua_pushnumber(interp, y);
Analog zu den »lua_get…«-Funktionen gibt es für jeden Datentyp in Lua eine »lua_push…«-Funktion.
Abschließend ruft »lua_pcall()« die so vorbereitete Funktion auf. Als Parameter verlangt sie den Interpreter und die Anzahl der Argumente sowie der zurückgelieferten Werte. Der letzte Parameter regelt das Fehlermanagement. Die Ergebnisse der ausgeführten Funktion respektive der Fehler landen wieder auf dem Stack, wo sie mit der beschriebenen Methode abzuholen sind.
Klein und klug
Lua mischt recht pfiffig imperative mit funktionaler Programmierung und lässt sich leicht in C-Programme integrieren. Die Objektorientierung wirkt aber auch nach dem fünften Sprachaufguss mehr als aufgepfropft. Dafür ist der Interpreter schnell, klein und sehr portabel. Ihre Praxistauglichkeit beweisen zahlreiche Erweiterungspakete, mit denen Lua weitere Funktionen hinzulernt oder an andere Sprachen andockt. Wer auf diese Attribute bei einer Skriptsprache Wert legt, sollte einen Blick riskieren, zumal unzählige Internetseiten und ein neues deutschsprachiges Buch mit Rat und Tat beim Einstieg helfen. (ofr)
|
Infos |
|---|
|
[1] Lua: [http://www.lua.org] [2] Gravit: [http://gravit.slowchop.com] |

![Abbildung 2: Der farbenprächtige Schwerkraftsimulator Gravit [2] verwendet Lua zusammen mit OpenGL.](https://www.linux-magazin.de/wp-content/uploads/2007/01/abb2_jpg-12-300x240.jpg)




