Lua - eine Skriptsprache

Transcrição

Lua - eine Skriptsprache
Lua – eine Skriptsprache
Übersicht über eine dynamisch typisierte Skriptsprache
mit erweiterbarer Semantik
Matthias Kühne
TU Dresden
[email protected]
Betreuer: Florian Heidenreich
Abstract
Lua ist eine Skriptsprache die hauptsächlich eingebunden
in eine Hostanwendung vorkommt. Mit ihr ist es möglich
einzelne Funktionalitäten auszulagern und zu kapseln. Der
große Vorteil dieser Skriptsprache ist, dass der relativ
kleine Funktionsumfang erweiterbar ist und es sich somit
viele Programmierparadigmen darauf nachprogrammieren
lassen. Dieser Artikel soll eine Einführung in eingebettete
Skriptsprachen geben, sowie eine Übersicht über die
Besonderheiten von Lua.
1
Statement und sehr wahrscheinlich zu einem Fehler des
Skripts, wodurch beim Debuggen auch auf solche und
ähnliche Fehler zu achten ist.
1.2 Beispiele
Bekanntes Beispiel von einer eingebetteten Skriptsprache
ist PHP, das in der Webseitengestaltung eingesetzt wird und
dort in HTML eingebettet ist. Weitere Beispiele sind
JavaScript und Perl. Als Beispiel für eine eigenständige
Skriptsprache ist Python zu nennen.
1. Skriptsprachen
2. Besonderheiten von Lua
1.1 Eigenschaften
Unter Skriptsprache versteht man eine Programmiersprache
die meist interpretiert (und nicht kompiliert wie z.B. Java)
ausgeführt wird. Darunter gibt es zwei Arten von Skriptsprachen: die eingebetteten und die eigenständigen
Skriptsprachen. Die eigenständigen Skriptsprachen sind
meist nicht mehr als direkte Skriptsprache zu erkennen, da
ihr Funktionsumfang sehr groß ist und sie mitunter weitere
Erweiterungen besitzt, die untypisch für Skriptsprachen
sind. Im Weiteren werden nur die eingebetteten
Skriptsprachen betrachtet, wozu Lua gehört.
Eingebettete Skriptsprachen erzeugen Programme,
sogenannte Skripte, die in eine Hostanwendung
eingebunden werden. Durch ein spezielles Interface können
Daten ausgetauscht werden, sowie die Skripte gestartet
werden. Durch diese Trennung zwischen Hostanwendung
und Skript lassen sich Funktionalitäten einzeln entwickeln
und debuggen, wodurch der gesamte Entwicklungsprozess
beschleunigt wird. Wären diese Funktionalitäten nicht in
Skripten ausgelagert, würde nach jeder Änderung die
gesamte Hostanwendung neu kompiliert werden müssen,
was bei heutigen Anwendungen / Spielen mehrere Stunden
dauern kann.
Die hier betrachteten Skriptsprachen sind klein gehalten
um eine schnelle Interpretation und Ausführung zu
gewährleisten. Aus diesem Grund herrscht bei vielen
Skriptsprachen kein Deklarationszwang von Variablen und
sehr oft auch die vollständige Typenfreiheit. Dadurch
kommt es durch Tippfehler meist zu einem anderem
2.1 Geschwindigkeit und Platzverbrauch
Lua selbst besitzt wenig Features und ist somit mit nur
120kb eine sehr speicherschonende Skriptsprache.
Insgesamt besitzt der Kern von Lua nur 17.000 Codezeilen.
Höhere Funktionalitäten (wie z. B. Objektorientierung)
können per Bibliotheken zur Verfügung gestellt werden2.
Weiterhin besitzt Lua einen “Just-in-Time”-Compiler
namens “LuaJIT”3. Dieser Compiler setzt den Quellcode
erst zur Laufzeit in Bytecode um, was zur Folge hat, das
spezifische Optimierungen, die nur auf bestimmten
Systemen verfügbar sind, durchgeführt werden können und
somit die Ausführungsgeschwindigkeit erhöht werden kann.
LuaJIT ist in der Lage jede Befehlserweiterung der CPU zu
nutzen (z. B. SSE) und erkennt, wenn eine Optimierung
fehl schlägt. Diese wird dann ausgeschaltet und durch eine
andere Art der Optimierung ersetzt.
Dadurch ist der Ausspruch “as fast as lua” unter
interpretierten Skriptsprachen zu einer besonderen Ehrung
avanciert.
Ein großer Vorteil des Konzeptes der Einbettung in eine
große Hostanwendung ist der Vorteil, dass die Skripte an
sich kleine eigenständige Programme sind, die bis auf ein
paar Ausnahmen auf einem extra CPU-Kern ausgeführt
werden können und somit nativ auf Mehrkern- und
Mehrprozessorsystemen beschleunigt laufen.
1
www.lua.org Portugiesisch für “Mond”
2
Eine große Sammlung solcher Bibliotheken ist unter luaforge.net zu finden
3
luajit.org
2.2 Datentypen
Variablen müssen nicht deklariert werden, sondern können
einfach benutzt werden. Weiterhin herrscht Typenfreiheit,
es lassen sich Variablen also einfach umdefinieren.
Zwischen den Typen String und double gibt es eine
automatische Typenumwandlung und alles kann als
boolesche Variable interpretiert werden. Dabei gilt das
“nil” semantisch äquivalent zu “false” ist, was den dem
logischen “false” entspricht. Alle anderen Werte sind
äquivalent zum logischen “true”, was auch den leeren
String “” als auch Zahlen und beliebige Strings umfasst.
Als komplexerer Datentyp besitzt Lua Tabellen die
ähnlich zu Java's “HashTable”4 arbeiten. Jeder Wert in
solch einer Tabele besitzt einen “Key”, dieser kann
entweder double oder ein String sein:
Point = { x = 10, y = 20 }
(1)
In diesem Beispiel besitzt die Tabelle “Point” 2 Einträge
mit den Keys “x” und “y”. Wenn die Definition der Keys
weggelassen wird, werden die Indizes als Keys verwendet
und damit ist diese Tabelle als Array nutzbar.
Point = { 10, 20 }
(2)
In diesem Beispiel sind die Keys deren Indizes, wobei die
Nummerierung bei 1 beginnt. In diesem Beispiel also “1”
und “2”. Intern verwendet Lua verwendet Lua ab 5.0 beide
Arten des Zugriffs auf Tabellen, womit ein Zugriff auf
solche Arrays schneller zu bewerkstelligen ist, als die vor
pre-5.0 benutzte reine Hashtable.
Zugriff auf die Werte dieser Tabellen ist durch folgendes
Konstrukt möglich:
Point[“1”]
(3)
Dies würde den ersten Wert der Tabelle zurückliefern, im
Beispiel also 1. Da die Verwendung von Strings als Keys
sehr häufig vorkommt, gibt es folgende Vereinfachung:
Point.x
(4)
Dies würde nun “nil” zurückliefern, da in (2) die Variable
“Point” neu definiert wurde und “x” nicht als Key darin
vorkommt. Aber grundsätzlich wäre nach (1) dies die
korrekte Syntax und würde “10” zurückliefern.
Als weitere Besonderheit der Tabellen ist zu nennen das
die Werte auch Funktionen sein können:
Point.new = function(x, y)
(5)
return { x = x, y = y } end
Wenn Funktionen als Werte verwendet werden gibt es eine
besondere Methode der Definition:
Point:getx = function(...)
(6)
return self.x end
Durch des Doppelpunktes ist es möglich “self” zu benutzen,
andernfalls ist dieses “self” nicht defniert und auf x müsste
anders zugegriffen werden. Da nun Typenfreiheit herrscht,
können diese Einträge wiederverwendet werden um
Speicherplatz zu sparen:
Point.getx = x
(7)
Zusätzlich zu diesen Tabellen implementiert Lua das
Konzept der “Metatabellen”, damit ist es möglich Tabellen
eine Obertabelle zuzuweisen und eventuell nicht
4
http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html
vorhandene Keys dort zu definieren. Zum Beispiel gibt es
in “Point” keinen Key “z” und bei Aufruf von “Point.z”
käme “nil” als Rückgabe.
AbstractPoint = { z = 0 }
AbstractPoint.__index = AbstractPoint
(8)
setmetatable(Point, AbstractPoint)
Mit diesem Konstrukt besitzt “Point” nun eine Metatabelle
in deren “__index” jetzt all diejenigen Keys gesucht werden
die in “Point” selbst nicht gefunden wurden. “Point.z”
würde nun also 0 zurückliefern.
Mit diesem Konzept ist es möglich eine einfache und
rudimentäre Implementierung von Objektorientierung zu
realisieren:
Person = {}
Person:new = function (name)
local instance = {}
instance.name = name
setmetatable(instance, self)
self.__index = self
return instance
end
(9)
fred = Person:new(“Fred”)
Somit kann “fred” als eine Instanz der Klasse “Person”
angesehen werden.
2.3 Funktionen
Funktionen können global und lokal definiert werden.
Lokale Funktionen sind nur innerhalb eines Skripts aus
erreichbar. Dagegen sind globale Funktionen von überall
aus erreichbar, wodurch sich auch die Semantik bisheriger
Funktionen erweitern oder austauschen lässt:
do
local oldprint = print
function print(str)
if str == “bar” return
(10)
else print(str) end
end
end
Damit wird die bisherige “print” Funktion überschrieben
und wird nun nur noch die alte “print” Funktion ausführen
wenn der übergebene String nicht “bar” ist. Diese neue
“print” Funktion wird nun von allen Skripten die auf
derselben VM laufen benutzt.
Weiterhin ist es möglich mehrere Rückgabewerte für
Funktionen zu definieren:
function foo(a, b, c, d)
(11)
return a+b, c, d end
Bei Aufruf der Funktion können aber auch Parameter
weggelassen werden, diese werden dann mit “nil”
übergeben. Auch die Benutzung von Wildcards bei der
Übergabe ist gestattet:
_, r2 = foo(a, b, c, d)
Damit ist sichergestellt, dass nur “r2” Speicherplatz
verbraucht, die beiden anderen Ergebnisse werden sofort
verworfen. Der dritte Rückgabewert wird hierbei genauso
ignoriert als würde das Wildcard “_” verwendet werden.
Um Funktionen effektiv zu kapseln und überschreiben
zu können gibt es noch eine weitere Besonderheit: die
“vararg” Variable:
function foo2(...)
(12)
return foo(...) end
Damit werden alle Parameter die “foo2” übergeben wurden,
einfach weiter an “foo” übergeben, ohne deren Reihenfolge,
Wert oder andere Eigenschaften zu ändern. Es ist auch
möglich später die einzelnen Variablen die in “...”
gespeichert werden zu erhalten:
function foo3(...)
(13)
a, b, c, d = ...
return foo(a, b, c) end
Ein weiteres Konzept das Lua beherrscht sind die
sogennanten “Closures”. Ein “Closure” ist eine Funktion
die eine lokale Variable (“upvalue”) einer Funktion
bewahrt, so dass weiterhin darauf indirekt zugegriffen
werden kann.
function newCounter()
local i = 0
return function()
– Closure Funktion
i=i+1
(14)
return i
end
end
Die Funktion “newCounter” besitzt eine lokale Variable auf
die nicht zugegriffen wird, und gibt als Rückgabe eine
Funktion die auf dieses “i” zugreift. Würde Lua Closures
nicht beherrschen, wäre “i” out-of-scope und somit “nil”
und somit die gesamte Closure Funktion fehlerhaft, aber so
bewahrt die Closure Funktion dieses “i”, es wird dadurch
zum “upvalue”, und durch diese Funktion lässt sich weiter
auf “i” zugreifen. Dieser Zugriff lässt sich nun durch die
definierten Closure bestimmen, in diesem Beispiel kann “i”
nur inkrementiert werden, im folgenden auch dekrementiert,
zurückgesetzt und angezeigt:
function newCounter()
local i = 0
(15)
return function() i = i + 1 end,
function() i = i -1 end,
function() i = 0 end,
function() return i end
end
inc, dec, res, get = newCounter()
Bei Funktionen wird häufig die “Endrekursion” benutzt:
function f(x)
return g(x)
(16)
end
Bei diesem Beispiel wird als Rückgabe der Funktion f(x)
nur g(x) ausgewertet und danach keine weiteren Statements
ausgeführt. Im nicht optimierten Fall würde nun der
Compiler zur Zeile “return g(x)” gelangen, Die
Rücksprungadresse dieser Zeile auf den Stack legen,
danach g(x) ausführen, zurückspringen auf die Zeile “return
g(x)” und dann erst das Return ausführen.
Da nun aber nur der Rückgabewert von g(x) als
Rückgabe von f(x) benutzt wird, könnte auch das Ergebnis
von g(x) auch direkt als Ergebnis von f(x) zurückgegeben
werden, was zu einer Schonung des Stacks führt und
weiterhin wertvolle CPU-Zeit spart (denn sonst müssten bei
jedem Rücksprung Variablen umgelagert werden). Aus
diesem Grund wird die Rücksprungadresse zu “return g(x)”
nicht auf den Stack geschrieben und somit dieser
überflüssige Extraschritt vermieden.
Eine weiterer Performancevorteil von Lua ist auf seine
Virtual Machine (VM) zurückzuführen. Lua benutzt ab
Version 5.0 keine stack-basierende VM mehr wie z. B. Java
sie besitzt, sondern eine register-basierende VM. Bei
typischen Funktionen unter Java werden alle globalen
Variablen und Parameter auf den Stack geschrieben und
können. Lua's VM besitzt auch einen Stack, aber in diesem
existieren die Register, wobei jede Variable, jeder
Parameter in einem Register sitzt, die frei zugreifbar sind.
Dadurch entfallen eventuelle “pop” und “push” Befehle die
bei stack-basierenden VM's häufiger auftreten wenn Werte
über den Stack geschoben werden. Grundsätzlich wird
damit die Hardware heutiger PC's im Detail nachmodelliert,
wodurch weiterer eventueller Overhead der durch eine
Stack-Programmierung entstehen würde, wegfällt. [1]
3. Lua im Einsatz
3.1 Anwendungen
Lua befindet sich momentan im Einsatz bei vielen
Programmen, als erstes Beispiel ist “Adobe Photoshop
Lightroom”5 zu nennen. “Lightroom” ist eine
professionelle Software zur Verwaltung, Optimierung und
Konvertierung von Digitalfotos und nutzt Lua zur
Implementierung seiner graphischen Benutzeroberfläche.
Ein weiteres Programm in dem Lua einsetzbar ist, ist der
“Apache HTTP Server”6, dort kann nach Installation eines
Modules Lua in jedem Request benutzt werden.
Die eingesetzte Skriptsprache bei der Profilsoftware des
“Logitech G15 Gaming Keyboard”7 ist Lua. Deren LCD
lässt sich mit Programmen erweitern und Daten darstellen.
Die Linux-Distribution “Damn Small Linux”8 benutzt
Lua
um
eine
Desktopumgebung
für
kommandozeilenbasierte Tools zu erstellen.
“Cisco Adaptive Security Appliance”9 benutzt Lua zur
Implementierung der Dynamic Access Policies.
3.2 Spiele
Weiter verbreitet ist Lua jedoch in der Spieleindustrie, wo
gezielt Funktionalitäten in Lua ausgelagert, um diese
gesondert entwickeln und debuggen zu können, da das
Neukompilieren einer Spieleengine mitunter Stunden
dauern kann.
“The Witcher”10 nutzt Lua exklusiv für die alle Skripte
innerhalb vom Spiel und lässt somit auch Modifikationen
von Spielern an den Spielvariablen zu.
Die Möglichkeit Spielinhalte zu modifizieren bieten
auch “Far Cry”11 und “Crysis”12. Beide nutzen dafür Lua
5
http://www.adobe.com/products/photoshoplightroom/
http://httpd.apache.org/
7
http://www.logitech.com/index.cfm/keyboards/keyboard/devices/3498&c
l=us,en
8
http://www.damnsmalllinux.org/
9
http://www.cisco.com/go/asa
10
http://www.thewitcher.com/
6
um dem Spiele ein einfache und doch machtvolle Sprache
zur Hand zu geben.
Dagegen sind in “S.T.A.L.K.E.R.: Shadow of
Chernobyl”13 wiederum alle Spielskripte und -mechaniken
in Lua geschrieben.
3.3 “World of Warcraft”
Eine besonders große Rolle spielt Lua bei dem FantasyMMORPG (massively multiplayer online role-playing
game) “World of Warcraft”14. Das komplette
Benutzerinterface ist in Lua geschrieben und durch
Modifikationen von Spielern austauschbar.
Innerhalb der Spiele-Engine ist durch Lua ein EventSystem implementiert. Bei jeder Aktion des Spielers oder
anderer Spieler / NPCs15 wird eine Nachricht gesendet. Auf
diese Nachrichten können sich Skripte anmelden und
bekommen dann einen Impuls das entsprechendes Event
gefeuert wurde.
Zum Beispiel: wenn ein Gegenstand aufgenommen wird,
wird das Event “BAG_UPDATE” gefeuert, worauf das
Interface für das Inventar des Heldens sich aktualisieren
kann um den neuen Gegenstand darzustellen.
Somit ist es möglich das Standard-Interface vollständig
gegen ein eigen erstelltes Interface auszutauschen und so an
die persönlichen Vorlieben anzupassen. Auf Grund dieser
Möglichkeit haben sich mehrere Communities gebildet die
sich zur Aufgabe gemacht haben, das Interface in einem
guten Programmierstil schnell und effizient zu verbessern.
Die beiden bekanntesten sind “CosmosUI”16 und das
“WoWAce”-Framework17. “CosmosUI” wurde eingestellt,
aber das “WoWAce”-Framework bietet weiterhin eine
große Anzahl gut geschriebener Modifikationen an, sowie
Starthilfen für angehende Lua-Programmierer.
Dieses
Framework
implementiert
rudimentäre
Objektorientierung und erlaubt es somit, häufig benutzte
Funktionalitäten oder Methoden in Bibliotheken
abzuspeichern die nur einmal geladen werden müssen.
Ohne diese Bibliotheken wäre es nötig das jeder
Programmierer diese Funktionen neu schreiben muss und
diese dann auch wieder im Speicher vorliegen.
Dank dieser Modifikationen hat es viele Impulse in
Richtung des Entwicklers von “World of Warcraft”
gegeben, sodass einige Ideen in das Hauptspiel
übernommen wurden, was zu einer enormen Verbesserung
des eigentlichen Spiels geführt hat.
4. Zusammenfassung
Lua ist eine kompakte und schnelle Skriptsprache mit deren
Hilfe Funktionalitäten einer größeren Hostanwendung
schnell und gut getrennt voneinander umgesetzt werden
können. Die Design-Philosophien von Lua sind schnelle
Ausführung und schonender Speicherplatzbedarf. Dies
kommt besonders gut zur Geltung dank des großen
Einsatzes in der Spieleindustrie in der heutige
11
http://www.farcrygame.com/
http://www.ea.com/crysis/
13
http://www.stalker-game.com/en/
14
http://www.worldofwarcraft.com/
15
NPC = non Player Characters, also Charaktere die vom Computer
gesteuert werden
16
http://www.cosmosui.org/
17
http://wowace.com/
12
Computersystem weit ausgereizt werden und jede mögliche
Optimierung Pflicht ist.
5. Quellen
• www.lua.org
• “Programming in Lua” ein Buch von Roberto
Ierusalimschy
• http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/jucs05.pdf
• Erklärung der Änderungen von Lua 5.0 gegenüber
4.0
• www.wikipedia.org
• www.onlamp.com
• “Introducing in Lua” ein Bericht von Keith Fieldhouse
• www.luaforge.net
• Sammelsurium von Erweiterungen für Lua
• www.lua-users.org
• Community der Nutzer von Lua
• www.worldofwarcraft.com
• Homepage von “World of Warcraft”
• www.wowwiki.com
• weitreichende API-Dokumentation zu Lua unter WoW

Documentos relacionados