Lua: Grundlagen


< Gesamtübersicht (Start)
Gesamtübersicht (Start) / Seminarthemen WS 2009/10

Übersicht


Was ist Lua

Die Entwicklung von Lua wurde 1993 von Roberto Ierusalimschy, Luiz Henrique de Figueiredo und Waldemar Celes an der Päpstlichen Katholischen Universität von Rio de Janeiro in Brasilien begonnen. Lua wurde so entworfen, dass es leicht in andere Sprachen (vorallem C) integriert werden kann. Neben den Integrationsmöglichkeiten in C, gibt es bereits zahlreiche Anbindungen an andere Sprachen, wie Java, C#, Ruby, Perl und viele mehr. Es ist natürlich auch möglich Lua alleine mit dem Standalone Interpretierer zu nutzen.
Lua wird auch als "Glue Language" (Klebesprache) bezeichnet. Die Idee hinter diesem Begriff ist die, dass eine Anwendung aus Komponenten besteht, die miteinander kombiniert werden. Diese Komponenten, welche laufzeitkritisch, speicherintensiv oder besonders hardwarenah sind, werden dabei in C geschrieben. Eine wichtige Eigenschaft dieser Komponenten ist ihre lange Beständigkeit. Sind sie einmal entwickelt, so werden sie sich nur noch sehr selten ändern. Lua hat nun die Aufgabe diese Komponenten miteinander zu kombinieren und so die Logik in der Anwendung zu erzeugen.
Eingesetzt wird Lua besonders im Bereich der Computerspiele. Dort wird es vorallem für die künstliche Intelligenz und Oberflächengestaltung eingesetzt. Zwei bekanntere Spiele, die Lua nutzen sind Crysis und World of Warcraft. Aber auch bei anderen Anwendungen wird Lua eingesetzt. So nutzt z.B. der VLC media player Lua um unbekannte Playlistformate öffnen zu können. Aber auch z.B. Adobe Photoshop Lightroom ist zu 40% in Lua geschrieben. Es gibt auch Ansätze Lua für Webanwendungen zu nutzen, aber Lua hat in diesem Bereich noch keine nennenswerte Verbreitung erfahren. In diesem Zusammenhang ist auch das Keplerprojekt zu nennen, in dem Module für diesen Bereich erstellt werden.

Eigenschaften von Lua

Lua ist dynamisch Typisiert und besitzt eine automatische Speicherverwaltung. Vor der Ausführung von Lua-Code wird dieser zunächst in einen Bytecode kompiliert, der anschließend interpretiert wird.
Es gibt gute Erweiterungsmöglichkeiten für Lua, indem Lua mit Hilfe von in C geschrieben Komponenten angereichert wird (mehr dazu später, wenn wir zum Einbetten von Lua in C kommen). Desweiteren ist Lua eine einfache Sprache. Dies bezieht sich sowohl auf die Syntax, die an Pascal angelehnt ist, als auch auf den Funktionsumfang, der sich nur auf das Nötigste beschränkt. Er kann aber natürlich, wie eben erwähnt, leicht erweitert werden. Besonders geschätzt wird Lua aber auch, da es sehr geringe Anforderungen an die Hardware stellt und eine im Vergleich zu anderen Skriptsprachen hohe Geschwindigkeit aufweist. Die hohe Portabilität von Lua ist eine weitere wichtige Eigenschaft. Da Lua in ANSI-C geschrieben ist, ist Lua auf jeder Plattform verfügbar, für die es einen ANSI-C Kompiler gibt. Folglich ist Lua auf jeder bedeutsamen Plattform verfügbar.

Werte, Variablen und Datentypen

Variablen werden in Lua nicht explizit deklariert. Stattdessen werden sie durch ihre Benutzung implizit deklariert. Solange ihnen kein anderer Wert zugewiesen wurde, haben sie den Wert nil. Wird einer Variablen nil zugewiesen wird sie "gelöscht". Sie hat dann den Status, als wenn sie nie benutzt worden wäre.
Variablen haben keinen Typ, sondern die Werte, die in ihnen gespeichert werden, weil Lua eine dynamisch typisierte Sprache ist. Zwar erfolgt die Typumwandlung in der Regel implizit, aber in gewissen Situationen kann man auch eine explizite Umwandlung anwenden. Funktionen hierfür sind tostring oder tonumber.
Bezeichner in Lua können aus Buchstaben, Zahlen und Unterstrichen bestehen. Sie sollten jedoch am Besten nur mit einem Buchstaben beginnen. Zudem ist zu beachten, dass Groß- und Kleinschreibung relevant ist.
Um lokale Variablen zu erzeugen, die nur innerhalb eines Blocks (z.B. einer Funktion) gültig sind, muss vor dem Bezeichner bei der ersten Benutzung das Schlüsselwort local angegeben werden.
x = 1        -- globale Variable
local y = 2  -- lokale Variable

Obwohl nie explizit Typen angegeben werden, verwaltet Lua intern folgende Typen.

DatentypBeschreibungBeispielwerte
nilDieser Wert bedeutet in etwa nichts. Dies ist der voreingestellte Wert für Variablen. Wird nil einer Variablen zugewiesen wird die Variable "gelöscht". Sie hat dann den Zustand, als wenn sie nie benutzt worden wäre.nil
booleanMit diesem Datentyp wird zwischen wahr und falsch unterschieden.true   false
numberDieser Typ repräsentiert alle Zahlen in Lua. Dies schließt sowohl Ganzzahlen, als auch Gleitkommazahlen ein. Er wird standardmäßig auf ein double abgebildet.5   8.75   7e3   785e-2   0x84
stringStrings sind wie üblich eine Folge von Zeichen. Im Gegensatz zu Strings in C kann jedes Zeichen genutzt werden, also auch binär Null. Desweiteren lassen sich Strings nicht verändern und sie sind "einzigartig". Das bedeutet, dass zwei gleiche Strings intern nur nur einmal gespeichert werden und nur mit Referenzen auf diesem internen Wert gearbeitet wird."Text"   'Text'   [[Text]]   [==[Text]==]
tableTabellen sind die einzige Möglichkeit in Lua, um Daten zu strukturieren. Es sind assoziative Arrays bei denen beliebige Typen als Schlüssel genutzt werden können.
functionFunktionen sind normale Werte, die diesen Typ haben.print
userdataWerte, die benutzerdefinierte Typen aus C darstellen, haben in Lua diesen Typ.
threadNebenläufigkeit wird in Lua durch sogenannte Koroutinen realisiert. Koroutinen haben diesen Typ.

Ein Lua-Wert wird in der Maschine in einer Struktur gespeichert, die als Tagged Union bezeichnet wird. Die in Lua zurzeit verwendete Struktur sieht wie folgt aus:
typedef struct {
  int t;    /* Hier wird der Typ gespeichert */
  Value v;  /* Der Wert */
} TObject;

typedef union {
  GCObject *gc;  /* Werte, die unter Kontrolle des Garbage Collectors stehen */
  void *p;       /* Light-Userdata */
  lua_Number n;  /* Zahlen */
  int b;         /* Boolean */
} Value;
Hier belegt jeder Wert mindestens 12 Byte Speicher (auf einer 32-bit Maschine). Also selbst ein boolscher Wert benötigt diese 12 Byte! Dies soll zeigen, dass man Lua nicht für jede Aufgabe nutzen sollte. Beispielsweise sollte Bildverarbeitung in C gemacht werden, da der Speicher dort besser genutzt werden kann.
Nun kann man sich natürlich fragen, warum in dem Struct das ganzes Union aufgenommen wurde und nicht eine Referenz auf den konkreten Wert. Die Antwort ist ganz einfach: Geschwindigkeit. Es sollte diese zusätzliche Indirektstufe vermieden werden.
Da die Werte nun jedoch so groß sind, hat man sich bei der virtuellen Maschine von Lua für eine registerbasierte entschieden. Registerbasierte virtuelle Maschinen haben gegenüber den eher üblichen stackbasierten den Vorteil, dass eine Reihe von Push und Pop Operationen gespart werden können. Somit reduziert man das teuere Kopieren der großen Werte und als Ergebnis steigt die Ausführungsgeschwindigkeit.

Tabellen

Wie bereits erwähnt sind Tabellen von besonderer Bedeutung in Lua. Sie realisieren ein assoziatives Array bei dem jeder Typ (außer nil) als Schlüssel genutzt werden kann. Tabellen werden mit Hilfe eines Konstruktorausdrucks erzeugt und können zur Laufzeit beliebig wachsen. Da sie ein Objekt darstellen, werden bei Zuweisungen nur die Referenzen kopiert und nicht die ganze Tabelle.
m = {} -- einfachster Konstruktorausdruck
m[1] = "Hallo"
m["moin"] = "Welt"
print(m[1])       --> Hallo
print(m["moin"])  --> Welt

a = m
a[1] = "Test"

print(a[1])  --> Test
print(m[1])  --> Test
Folgende Konstruktorausdrücke und Zugriffsmöglichkeiten gibt es für Tabellen.

Operatoren

Lua besitzt einen klassischen Satz an Operatoren, die in der folgenden Tabelle aufgelistet sind.

PrioritätOperator(en)Assoziativität
1.^rechts
2.not   #   - (unär)links
3.*   /   %links
4.+   - (binär)links
5...rechts
6.<   >   <=   >=   ~=   ==links
7.andlinks
8.orlinks

Die meisten dieser Operatoren sollten Ihnen bekannt sein. Einige weniger geläufige Operatoren möchte ich hier aber noch kurz erläutern.
Der # Operator dient dazu die Anzahl der Elemente in einem Array zu ermitteln. Hat das Array jedoch "Löcher" in Form von nil Werten, so ermittelt er nur die Anzahl bis zum ersten "Loch".
Der .. Operator dient der Konkatenation von Strings. Dabei wird ein neuer String erzeugt, da Strings wie bereits erwähnt unveränderlich sind. Da dieses Vorgehen (wie z.B. auch in Java) nicht sehr effizient ist, gibt es noch eine andere Möglichkeit um Strings zu konkatenieren. Hier möchte ich auf die Funktion table.concat verweisen.
Die Ungleichheit von zwei Werten wird mit dem ~= Operator ermittelt.

< Gesamtübersicht (Start)
Zum Seitenanfang