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